Skip to content

feat(i18n): add English and Simplified Chinese localization - #168

Closed
squarepots wants to merge 111 commits into
Lampese:mainfrom
squarepots:feat/i18n-foundation
Closed

squarepots wants to merge 111 commits into
Lampese:mainfrom
squarepots:feat/i18n-foundation

Conversation

@squarepots

@squarepots squarepots commented Sep 18, 2026 •

Copy link
Copy Markdown

中文

实现状态

本 PR 完成 Codex Switcher 的双语本地化基础,并继续使用现有的 #168 分支与 PR。

  • 桌面语言偏好由 AppSettings.ui_language_preference 作为唯一持久化 authority,支持 System default、English、简体中文;解析后的 en-US / zh-CN 同步到主窗口、托盘 webview、原生应用菜单和原生托盘菜单。
  • 浏览器语言偏好只属于当前浏览器,支持 Browser default、English、简体中文。当前 JS session 的 in-memory preference 是唯一 semantic authority;localStorage 只做尽力持久化。显式选择先更新内存,即使 storage 不可用,React、设置 picker 和 web-secret prompt 也保持一致;Browser default 才按 navigator.languages 的第一个受支持语言解析,且不会写入桌面设置。
  • Desktop 安装身份只由 initialize_app_settings() 决定:新安装持久化 System,已有 pre-localization 安装及缺少语言字段的 legacy settings 持久化 English,显式偏好和 legacy language 字段保持不变。普通 runtime read 只读取 AppSettings;缺失 settings 时固定使用 English safe fallback,绝不再依据 accounts.json 重新推断语言。设置写入继续复用 locked/atomic persistence。
  • 明确区分简体与繁体环境,覆盖 zh-CN、zh_CN、zh-Hans、zh-Hans-*、zh-SG,并让 zh-TW、zh-HK、zh-MO、zh-Hant*、裸 zh 回退到 English。
  • 使用显式 React render-time translation、双 JSON catalog、已迁移 surfaces 的语义化文案和 i18next 插值/复数;不使用 DOM observer 或 DOM 重写。
  • 原生应用菜单和托盘菜单使用稳定的语义化 native text keys,再由统一映射提供英文与简体中文,避免把英文显示文案当作状态标识。
  • 补齐设置、备份、浏览器 secret、强制关闭、reset credits、usage stats、可访问名称和 native surface 文案;保持原始账号、错误、路径、provider 值及日期/数字/时区格式不变。
  • 语言写入成功后返回 authoritative DisplaySettings,先尽力投影 native menu/tray,再通过现有 app-settings-changed 机制通知 webview;投影失败不会伪报持久化失败。
  • 初始化与读取失败只记录日志,不阻塞 Desktop 启动;原生应用菜单、托盘和 React 首屏使用 English safe fallback,并可在 storage 恢复后通过既有 settings 流程 reconciliation。document.documentElement.lang 仅是已解析语言的 projection,不是 browser authority。
  • 分支按当前 fix(web): bound request concurrency #167(2b85209e)、feat(backup): add user-passphrase encryption #170(30a6d18c)、fix(auth): make activation a single transaction #171(ad31b476)、fix(auth): align OAuth refresh policy with Codex #172(26ab3448)和 feat(warmup): persist host-owned scheduling policy #173(fa97f003)验证,保留 web boundary、passphrase backup、auth transaction、OAuth refresh 和 Rust-owned warm-up scheduler 的既有行为。

验证

  • pnpm test:i18n:8 项通过
  • pnpm test:desktop-reopen:7 项通过
  • pnpm test:close-preference:3 项通过
  • pnpm test:reset-credits:3 项通过
  • pnpm test:warmup-policy:1 项通过
  • pnpm test:web-boundary:1 项通过
  • pnpm test:backup-passphrase:3 项通过
  • pnpm build:通过
  • cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check:通过
  • cargo check --locked --manifest-path src-tauri/Cargo.toml:通过
  • cargo test --locked --manifest-path src-tauri/Cargo.toml:92 项通过
  • git diff --check:通过

Closes #157


English

Implementation status

This PR completes the bilingual localization foundation for Codex Switcher while continuing the existing #168 branch and pull request.

  • Desktop language preference has one persisted authority, AppSettings.ui_language_preference, with System default, English, and 简体中文. The resolved en-US / zh-CN language is projected to the main window, tray webview, native application menu, and native tray menu.
  • Browser language preference belongs only to the current browser, with Browser default, English, and 简体中文. The current JS session in-memory preference is the single semantic authority; localStorage is best-effort persistence only. Explicit choices update memory first, so React, the settings picker, and the web-secret prompt stay aligned even when storage is unavailable. Only Browser default resolves the first supported entry from navigator.languages, and it never writes desktop settings.
  • Desktop installation identity is decided only by initialize_app_settings(): new installs persist System; existing pre-localization installs and legacy settings without language persist English; explicit preferences and the legacy language field survive. Ordinary runtime reads only read AppSettings; a missing settings file always uses the English safe fallback and never re-infers language from accounts.json. Settings writes reuse locked/atomic persistence.
  • Simplified and Traditional Chinese are separated. The resolver covers zh-CN, zh_CN, zh-Hans, zh-Hans-*, and zh-SG; zh-TW, zh-HK, zh-MO, zh-Hant*, and bare zh fall back to English.
  • Translation uses explicit React render-time rendering, two JSON catalogs, semantic copy for the migrated surfaces, and i18next interpolation/pluralization. No DOM observer or DOM rewrite is used.
  • Native application and tray menus use stable semantic text keys mapped to English and Simplified Chinese, so translated display strings never become program state.
  • Settings, backup, browser-secret, force-close, reset-credit, usage-statistics, accessibility, and native-surface copy is localized. Raw account, error, path, provider, date, number, and timezone semantics remain unchanged.
  • Successful language writes return authoritative DisplaySettings, attempt native menu/tray projection before notifying webviews through the existing app-settings-changed mechanism, and do not report persistence failure when a projection fails.
  • Initialization and read failures are logged without blocking Desktop startup; native menus, the tray, and the React first screen use the English safe fallback and can reconcile through the existing settings flow when storage becomes usable. document.documentElement.lang is only a projection of the resolved language, never browser authority.
  • The branch was verified against the current behavior in fix(web): bound request concurrency #167 (2b85209e), feat(backup): add user-passphrase encryption #170 (30a6d18c), fix(auth): make activation a single transaction #171 (ad31b476), fix(auth): align OAuth refresh policy with Codex #172 (26ab3448), and feat(warmup): persist host-owned scheduling policy #173 (fa97f003), preserving the existing web boundary, passphrase backup, auth transaction, OAuth refresh, and Rust-owned warm-up scheduler behavior.

Validation

  • pnpm test:i18n: 8 passed
  • pnpm test:desktop-reopen: 7 passed
  • pnpm test:close-preference: 3 passed
  • pnpm test:reset-credits: 3 passed
  • pnpm test:warmup-policy: 1 passed
  • pnpm test:web-boundary: 1 passed
  • pnpm test:backup-passphrase: 3 passed
  • pnpm build: passed
  • cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check: passed
  • cargo check --locked --manifest-path src-tauri/Cargo.toml: passed
  • cargo test --locked --manifest-path src-tauri/Cargo.toml: 92 passed
  • git diff --check: passed

Closes #157

fix(web): isolate slow requests with bounded workers
@squarepots
squarepots marked this pull request as ready for review September 18, 2026 16:27
@squarepots
squarepots marked this pull request as ready for review September 19, 2026 08:33
@squarepots

Copy link
Copy Markdown
Author

@Lampese 这批 #165–#175 我已经完成最后一轮 review 和集成收口。当前所有 PR 都是 Open、非 Draft,GitHub 均显示 mergeable,也没有 unresolved review thread。

这轮重点重新检查了 #168。它之前的集成验证基于较早的 dependency heads,而 #166、#167、#171、#172、#173 后续又有修改。按最终实现重新核对后,我在 #168 中发现并修复了三个会重新引入旧问题的地方:

同时,#168 的 runtime auth reconciliation 已对齐 #171 的最终行为,并补回相关 regression coverage。当前 #168 head 是 2dddc004。

重新核对后,#168 现在保留了这批依赖中的关键行为:

建议按依赖关系处理:

#165 -> #167

#169 -> #172 -> #171 / #173

#170 在 #169 之后。

#166 和 #174 独立。

#175 也独立,建议先批准它的 PR workflow,这样剩余 PR 可以拿到实际 CI 结果。

#168 放在 dependency stack 收口后处理,作为最终 localization / integration PR。

目前剩下的验证项是 CI。#175 的 workflow 已被 GitHub 识别,但 fork PR run 仍停在 action_required,需要 maintainer 手动 Approve and run。在最新 #168 修改之后,我没有远程 CI 结果,因此 merge #168 前请让最终 build/tests 在当前 head 上实际跑一遍。


@Lampese I finished the final review and integration pass for #165–#175. All of the PRs are currently open, non-draft, reported as mergeable by GitHub, and have no unresolved review threads.

The main integration work in this pass was #168. Its earlier validation was based on older dependency heads, while #166, #167, #171, #172, and #173 changed afterward. Rechecking #168 against their final behavior found three regressions that would otherwise have been reintroduced:

Those are now fixed. #168 also uses the final runtime-auth reconciliation behavior from #171 and includes the relevant regression coverage. The current #168 head is 2dddc004.

I rechecked the integration boundaries after these changes. #168 now preserves the relevant behavior from:

Suggested dependency order:

#165 -> #167

#169 -> #172 -> #171 / #173

#170 after #169.

#166 and #174 are independent.

#175 is also independent. Approving its PR workflow early would give the remaining PRs an actual CI signal.

#168 should be handled after the dependency stack as the final localization/integration PR.

The remaining verification step is CI. GitHub recognizes the workflow added by #175, but the fork PR run is still stopped at action_required and needs a maintainer to Approve and run it. The latest #168 changes therefore do not yet have a remote CI result. Please run the final build/tests on the current head before merging #168.

@squarepots squarepots closed this by deleting the head repository Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Simplified Chinese and UI language preferences

1 participant