fix(updater,i18n): 对抗性审查修复(1中 + 3低)
CI / typecheck + test + build (windows) (push) Canceled after 0s
Deploy website to GitHub Pages / deploy (push) Canceled after 0s

- [中] 手动检查不再丢弃「已下载」事实:runCheck('manual') 原来无条件清
  抑制记录,导致下载完成后点「检查更新」会把关于页从「重启安装」退回
  「下载」(包已在磁盘)。改为保留 downloaded 记录,只清 announcement
  记账;测试 §19 补 manual-check 保持 downloaded 的口径(153 断言)
- website 首帧脚本:?lang= 的 decodeURIComponent 包 try/catch(畸形转义
  会在 IIFE 内抛 URIError,跳过 html[lang] 与防闪标记,指定语言被静默
  忽略);zh-Hant-TW/zh-Hant-HK/zh-Hant 按 BCP-47 语义判为繁体
  (normalize 与 i18n-apply.js 双处同步)
- ZmodemOffers 是 App 兄弟节点,语言切换时父级 children bailout 不重渲染:
  订阅 language 并以 tFor 显式翻译(事件回调内的 t() 保留)
- ROADMAP 补录对抗性审查推迟项(周期检查可被设置写入推迟、启动开关
  即时生效语义、下载无整体超时、--channel-only/--skip-gitea 矛盾)
This commit is contained in:
Bill committed 2026-10-10 16:51:10 +08:00
1 parent 7c2044fdf4
commit 2027fa3310
6 files changed
+58 -11

No files matched your search

+11 -5
View File
@@ -323,11 +323,17 @@ async function performCheck(source: CheckSource): Promise<UpdateState> {
*/
export function runCheck(source: CheckSource = 'manual'): Promise<UpdateState> {
// A manual check is an explicit act, so whatever it finds counts as news even
// if this run has already dealt with that version: the suppression record is
// dropped here — before the in-flight guard, so a click that joins a check
// already running gets the same treatment. (A manual DOWNLOAD needs no
// equivalent: its entry point never consults the record.)
if (source === 'manual') actedRelease = undefined
// if this run has already announced that version: the announcement half of the
// record is dropped here — before the in-flight guard, so a click that joins a
// check already running gets the same treatment. The DOWNLOADED half is kept:
// for a release whose package sits on disk, "downloaded" *is* the truthful
// answer, and dropping it made update-available report `available` again —
// folding the About tab's install button back into "download" for a manual
// check on an already-fetched release. (A manual DOWNLOAD needs no equivalent:
// its entry point never consults the record.)
if (source === 'manual') {
actedRelease = actedRelease?.downloaded ? actedRelease : undefined
}
if (checkInFlight) return checkInFlight
const task = performCheck(source)
checkInFlight = task
+10 -5
View File
@@ -1,7 +1,8 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { App as AntdApp, Button, Modal, Space, Typography } from 'antd'
import type { ZmodemOfferEvent, ZmodemResponse } from '@shared/ipc'
import { t } from '@shared/i18n'
import { DEFAULT_LANGUAGE, t, tFor } from '@shared/i18n'
import { useSettingsStore } from '../settings/store'
/**
* M6 ZMODEM offer/done UX: renders one dialog per active zmodem offer.
@@ -15,6 +16,10 @@ import { t } from '@shared/i18n'
*/
export default function ZmodemOffers(): null | React.JSX.Element {
const { message } = AntdApp.useApp()
// Rendered as App's sibling: a language switch re-renders App, but the parent's
// children element is identical, so this subtree bails out and would keep the
// old language. Subscribe here and translate with the explicit language.
const language = useSettingsStore((s) => s.settings.system.language ?? DEFAULT_LANGUAGE)
const [offers, setOffers] = useState<Map<string, ZmodemOfferEvent>>(new Map())
const busyRef = useRef<Set<string>>(new Set())
@@ -117,21 +122,21 @@ export default function ZmodemOffers(): null | React.JSX.Element {
closable={false}
maskClosable={false}
keyboard={false}
title={isReceive ? t('ssh.zmodem.titleReceive') : t('ssh.zmodem.titleSend')}
title={isReceive ? tFor(language, 'ssh.zmodem.titleReceive') : tFor(language, 'ssh.zmodem.titleSend')}
footer={
<Space>
<Button
type="primary"
onClick={() => (isReceive ? void handlePickDir(first.id) : void handlePickFiles(first.id))}
>
{isReceive ? t('ssh.zmodem.pickDir') : t('ssh.zmodem.pickFiles')}
{isReceive ? tFor(language, 'ssh.zmodem.pickDir') : tFor(language, 'ssh.zmodem.pickFiles')}
</Button>
<Button onClick={() => handleCancel(first.id)}>{t('common.cancel')}</Button>
<Button onClick={() => handleCancel(first.id)}>{tFor(language, 'common.cancel')}</Button>
</Space>
}
>
<Typography.Text>
{isReceive ? t('ssh.zmodem.receiveDesc') : t('ssh.zmodem.sendDesc')}
{isReceive ? tFor(language, 'ssh.zmodem.receiveDesc') : tFor(language, 'ssh.zmodem.sendDesc')}
</Typography.Text>
</Modal>
)