Skip to content

feat(i18n): ship the platform's 11 language tags, resolved by script - #189

Open
objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/issue-182-eleven-locales
Open

objectstack-fleet[bot] wants to merge 2 commits into
mainfrom
claude/issue-182-eleven-locales

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #182

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:手机原生页面(选服务器、确认、加载、离线页)从控制台的 10 种语言改为平台统一的 11 种语言;新增繁體中文,zh 改名 zh-Hans、pt 改名 pt-BR,iOS 相机/相册/面容 ID 权限弹窗的繁体也有了自己的文案。
  • 为什么改:平台裁决 F(objectstack#29349)定了全平台只用一张 11 种语言的表,维护者原话「mobile 也需要支持这些语言」;此前台湾、香港、澳门用户看到的是简体。
  • 风险与回滚:只影响原生页面和 iOS 权限弹窗的语言,控制台内部不受影响;繁体文本是机器转换的,可能残留大陆用词,不影响功能;回滚就是 revert 本 PR。
  • 先看:lib/i18n.ts 的 resolveLanguageTag(脚本优先于地区的判定)、locales/zh-Hant.json(机器转换文本)、app.config.ts 的 PERMISSION_LOCALES(葡语写成不带地区的 pt)。
  • 两分钟验证:npx jest --ci __tests__/lib/i18n.test.ts,预期 Tests: 90 passed, 90 total。
  • 需要你做的:请一位繁体母语者拿一台切到「繁體中文(台灣)」的手机,审一遍原生页面和相机权限弹窗的文案(候选改词见 Acceptance notes),同一台手机再切到「Português (Brasil)」确认显示葡语;这项真机检查尚未做过。

What changes

The shell's native strings move from the Console's ten short codes to the platform's one list of 11 tags (objectstack-ai/objectstack#29428, ruling F of #29349): en zh-Hans zh-Hant es ar fr pt-BR ru de ja ko, in the table's order.

  • Bundles. locales/zh.json → zh-Hans.json and locales/pt.json → pt-BR.json, both pure renames (git similarity 100%). New locales/zh-Hant.json, converted from zh-Hans.json (see Acceptance notes for the recipe). Every key on main carries over to all 11, and the key-parity test now covers all 11.
  • Resolution by script. matchLanguageTag, the port of the Console's exact-then-base rule that collapsed both scripts to zh, is removed (nothing else imported it). resolveLanguageTag(tag) replaces it.
  • OS-level strings. app.config.ts PERMISSION_LOCALES gives zh-Hant.lproj the zh-Hant bundle's camera, photo-library and Face ID text instead of the Simplified text. Portuguese stays written as the unqualified pt lproj, now holding the pt-BR text, so every Portuguese variant falls back to it on iOS, matching the shell's pt-* → pt-BR rule.
  • A locale with no languageTag. detectDeviceLanguage builds the tag from languageCode + languageScriptCode + regionCode, so the script is not lost (zh + Hant + TW → zh-Hant-TW).
  • Docs. docs/HYBRID-SHELL.md (Language, the camera-permission paragraph, the file map) and docs/design/connection-flow.md (Languages and layout) say 11 and name the table.

The contract of resolveLanguageTag(tag): SupportedLanguage | null

One BCP 47 tag in, one bundled language or null out. Subtags compare case-insensitively. Only the language, script and region subtags are read, and scanning stops at the first singleton (-u-, -x-), so an extension value never reads as a region.

Input Result
zh with script Hant (zh-Hant, zh-Hant-TW, zh-Hant-CN) zh-Hant
zh with script Hans (zh-Hans, zh-Hans-CN, zh-Hans-HK) zh-Hans
zh with no script and region TW, HK or MO zh-Hant
any other zh (zh, zh-CN, zh-SG) zh-Hans
any pt (pt, pt-PT, pt-BR, pt-AO) pt-BR
any other tag whose primary subtag is bundled (en-US, es-MX) the primary subtag
anything else (it-IT, yue-HK, empty) null

resolveLanguage / detectDeviceLanguage walk the phone's preferred languages in order, return the first non-null result, and otherwise return en.

Tests

All three CI families were run locally at a4a903a; their commands are the same as ci.yml and lint-typecheck.yml.

  • npx tsc --noEmit: exit 0. --listFiles includes lib/i18n.ts, app.config.ts, locales/zh-Hant.json and both changed __tests__/lib files.
  • npx eslint . --ext .ts,.tsx: exit 0, no output.
  • npx jest --ci --coverage: exit 0, Test Suites: 49 passed, 49 total, Tests: 1070 passed, 1070 total; lib/i18n.ts 92.06% statements.
  • No component test file is new, so the cold variant does not apply.

New or changed pins in __tests__/lib/i18n.test.ts:

  • each read alias #29428 declares;
  • the card's acceptance cases: zh-Hans-CN/zh-CN/zh-SG → zh-Hans, zh-TW/zh-HK/zh-Hant → zh-Hant, pt-PT/pt-BR → pt-BR, fall-through to the next preferred language, empty → en;
  • script over region;
  • case folding;
  • extension subtags;
  • a locale without a languageTag;
  • translation through the zh-Hant and pt-BR bundles.

__tests__/lib/app-config.test.ts pins that each Chinese script carries its own bundle's text, that the zh-Hant text differs from the zh-Hans text, and that the pt lproj carries the pt-BR text.

Ablation: map zh-TW to zh-Hans

The implementation was committed first. The mutation went through scripts/ablation-replace.mjs (objectstack): new Set(["tw", "hk", "mo"]) → new Set(["hk", "mo"]) in lib/i18n.ts, anchor x1 → x0, blob 3da779ed305e → c60ec6d6b0be. Jest reads the source directly; there is no dist/ in this repo.

Tests:       3 failed, 96 passed, 99 total
  ● detectDeviceLanguage › resolves Traditional Chinese zh-TW to zh-Hant
  ● resolveLanguageTag — the platform's alias rule, by script › compares subtags case-insensitively, as BCP 47 defines them
  ● resolveLanguageTag — the platform's alias rule, by script › resolves the platform alias zh-TW to zh-Hant
ablation-replace: ok restored: blob == HEAD (3da779ed305e) and `git diff HEAD` is empty

The tests went red as expected. zh-Hant-TW stayed green, correctly, because its script subtag decides before the region is read.

Acceptance notes

  • Base. The branch starts from 13b7e32, the origin/main at worktree creation, not from 427cb49 as dispatched. 13b7e32 contains 427cb49, and the five commits in between (feat(eas): link the app to the EAS project so CI can build it #185) touch app.config.ts, so branching from the newer head avoids a conflict there.
  • #29428 against the card: no disagreement. #29428's alias list is in its body, under Phases, item 1, second bullet: zh, zh-CN, zh-SG → zh-Hans; zh-TW, zh-HK, zh-MO → zh-Hant; ja-JP → ja; es-ES → es; pt → pt-BR; en-US → en. The card's rule maps every one of those the same way, and a test pins each. The card's rule also covers inputs that #29428 does not list (pt-PT, zh-Hant-TW, zh-Hans-HK, es-MX). #29428 is silent on those, so they are not conflicts.
  • No merged table on objectstack main yet. At 4b71a82b76, git grep -n PLATFORM_LOCALES origin/main -- packages/spec/src ui/packages/i18n/src finds nothing. The control word BUILT_IN_LANGUAGE_CODES hits in ui/packages/i18n/src, so the empty result is not a broken grep. When phase 1 lands, this shell's list and rule should be re-read against the exported table.
  • The Console still ships ten short codes. The shell never hands its language to the Console (it does not set Accept-Language; see HYBRID-SHELL "Language"), so the tag rename reaches only native screens.
  • How zh-Hant was made. The card's precedent, objectstack-ai/www.objectos.ai scripts/gen-zh-hant.mjs, was read at 4ed1dfe through git (the REST API refuses that repo in this session). It converts with opencc-js 1.3.1, preset s2twp (Simplified to Taiwan, phrase conversion), and its src/lib/zhconvert.ts places eight phrase overrides at the head of the TWPhrases group: 權限, 實例, 全域, 擴充, 配接器, 控制項, 映像, 腳本. The bundle here was converted with the same version, preset and overrides, values only, keys untouched. A second run reproduces locales/zh-Hant.json byte for byte. Both locative 里 came out as 裡 (哪裡, 這裡).
  • No generator was committed. Committing one would add opencc-js to package.json and a scripts/ file, both outside this card's file surface. So when zh-Hans.json changes, zh-Hant.json is reconverted by the recipe above. HYBRID-SHELL and CONTRIBUTING now say so, and the key-parity test catches a missing key, though not stale text.
  • Native-speaker review candidates. These are mainland terms the converter keeps. They are not applied here, because the file is kept byte-exact to the converter output: 二維碼 (QR Code / 二維條碼), 證書 (憑證), 響應 (回應), 郵箱 (電子郵件), 賬號 (帳號), 面容 ID / 觸控 ID (Apple's Taiwan names are Face ID / Touch ID), 應用 (App), 伺服器地址 (位址), 鎖屏 (螢幕鎖定), 錄屏 (螢幕錄影).
  • File surface: two additions beyond the claim, with reasons. Both lines would be false after this PR, so they are corrected here:
    • docs/DEVICE-TESTING.md:78 said zh-TW shows Simplified Chinese. It now carries the 繁體中文 (Taiwan) and Português (Brasil) device check.
    • docs/CONTRIBUTING.md:26 named locales/{en,zh,ar}.json, and zh.json no longer exists.
  • Left as is. ROADMAP.md:23 "i18n (en / zh / ar)" is a historical status line, not a file reference.
  • Concurrency. MDM lock: while lockServer is set, the connection switcher shows only the managed connection #187 (MDM lock scope) also edits docs/HYBRID-SHELL.md, in a different section. Whichever PR lands second reconciles.

Verification

  • Local: tsc, eslint and jest, as above, at a4a903a.
  • Device check: not verified. This needs a phone set to 繁體中文 (Taiwan) and to Português (Brasil). See 需要你做的.

Generated by Claude Code

Rename zh -> zh-Hans and pt -> pt-BR, add a zh-Hant bundle converted from
zh-Hans (opencc-js s2twp with the www.objectos.ai phrase overrides), and
replace the Console's exact-then-base matcher with resolveLanguageTag:
Hant/Hans script first, TW/HK/MO -> zh-Hant, other zh -> zh-Hans,
pt-* -> pt-BR, else the primary subtag. zh-Hant gets its own iOS
permission text.

Claude-Session: https://claude.ai/code/session_01QSeKYnW1Xf6HDSWz1eGAqv
Co-authored-by: Claude <noreply@anthropic.com>
HYBRID-SHELL and connection-flow say 11 and name the table; the device
check and the contributing note stop naming zh.json and stop saying zh-TW
shows Simplified Chinese. Changeset for the locale move.

Claude-Session: https://claude.ai/code/session_01QSeKYnW1Xf6HDSWz1eGAqv
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

Seat verdict: ACCEPT, maintainer merges. Seat landing is suspended because EXPO_TOKEN is set (see #160). Head-sha: a4a903a6e41e815fd919244044414f8d3b6dae68

维护者速读

  • 改了什么: 原生界面从 10 种语言代码改为平台统一的 11 个标签:en zh-Hans zh-Hant es ar fr pt-BR ru de ja ko。
    • zh.json 改名为 zh-Hans.json,pt.json 改名为 pt-BR.json,两个都是纯改名。
    • 新增繁体包 zh-Hant.json。它用 opencc-js s2twp 加上 www.objectos.ai 的 8 条短语修正,从简体生成。
    • 繁体手机(zh-Hant、zh-TW、zh-HK、zh-MO)从此显示繁体中文。
    • iOS 相机和相册的权限提示也有了繁体版本。
  • 为什么改: 平台已裁定 objectstack#29349 选 F,全平台统一使用 11 种语言。
  • 风险与回滚: 繁体文案是机器转换的,需要人工过一遍。回滚就是 revert 本 PR。
  • 先看: lib/i18n.ts 的 resolveLanguageTag(正文里有规则表)和 locales/zh-Hant.json。
  • 两分钟验证: npx jest --ci __tests__/lib/i18n.test.ts __tests__/lib/app-config.test.ts
  • 需要你做的:
    1. 请懂繁体的同事过一遍 zh-Hant.json。
    2. 手机分别设为「繁體中文(台灣)」和「Português (Brasil)」,确认原生页面跟着切换。
    3. 审批并合并。和 feat(mdm): list only the managed connection while the server is locked #188 先合哪个都行,席位已经试合过,没有冲突。

席位核对

  1. Shape: draft → main, first line Fixes #182. Clause-②: no and Metadata-protocol: none, plus a patch changeset. Size +421 / −135.
  2. Premise: Platform locale table (ruling F of #29349): one list of 11 languages in spec, complete on every surface objectstack#29428 is open. Every alias it names maps identically and is pinned by a test, so there is no disagreement with the card. No merged table exists yet on objectstack main.
  3. Gates:
    • check-runs on head a4a903a: Code Quality success, Lint & Type Check success;
    • mergeable_state: clean;
    • local jest --ci --coverage: 49/49 suites, 1070 tests.
  4. Ablation: dropping tw from the Traditional regions turns 3 tests red.
  5. Cross-PR check: the seat merged main + feat(mdm): list only the managed connection while the server is locked #188 + feat(i18n): ship the platform's 11 language tags, resolved by script #189 in a scratch worktree. The result is clean, and no changeLanguage("zh") call is left in the merged tests.
  6. Accepted deviations:
    • docs/DEVICE-TESTING.md and docs/CONTRIBUTING.md are corrected, because the change made them false.
    • The zh-Hant generator is not committed (it would need a new dependency); the recipe is documented.
  7. Out of scope: no drift test yet ties zh-Hant to zh-Hans. Recorded in Acceptance notes.
  8. Landing: the maintainer merges; the seat does not enable auto-merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-user-decision Blocked on a maintainer decision — do not dispatch; on a PR: awaiting the maintainer's review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Languages: move from the Console's ten codes to the platform's 11 tags (split zh into zh-Hans / zh-Hant, pt → pt-BR)

1 participant