fix(i18n): Correct zh-TW translations to match Traditional Chinese conventions#4129
Conversation
…nventions Fix ~131 lines of Traditional Chinese (zh-TW) translations that used Simplified Chinese character forms instead of standard Traditional Chinese usage. Changes: - 文件 → 檔案 (47 occurrences) - 爲 → 為 (45 occurrences) - 啓 → 啟 (44 occurrences) - 曆史 → 歷史 (6 occurrences) - 鏈接 → 連結 (4 occurrences) - 菜單 → 選單 (3 occurrences)
There was a problem hiding this comment.
LGTM! ✅ The 6 pattern corrections (文件→檔案, 爲→為, 啓→啟, 曆史→歷史, 鏈接→連結, 菜單→選單) are applied correctly and consistently across all ~131 affected entries.
One minor note for a future pass: 服務器 (mainland usage) is used in ~15 unchanged lines while 伺服器 (Taiwan standard for server) appears only once. This pattern was not part of the current fix, but could be addressed in a follow-up to further align with Traditional Chinese conventions.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
Align with Traditional Chinese convention where 伺服器 is the standard term for 'server' in computing contexts.
wenshao
left a comment
There was a problem hiding this comment.
Suggestions for future-proofing (non-blocking, all translations correct ✅):
[1] Misleading "Auto-generated" header comment (line 7-8 of zh-TW.js): The file header says "Auto-generated: structure from en.js, values from zh-TW (manual) or opencc(zh.js s2t)" — but no generation script exists in the repo, and OpenCC's standard s2t cannot handle Taiwan-specific vocabulary (檔案 vs 文件, 伺服器 vs 服務器, 選單 vs 菜單). A future maintainer seeing this comment might regenerate the entire file with raw OpenCC output, silently reverting all the manual corrections in this PR. Consider updating to:
// Traditional Chinese (zh-TW) translations for Qwen Code CLI
// Bootstrapped from en.js structure with opencc(zh.js s2t),
// then extensively hand-corrected for Taiwan vocabulary conventions.
// This file is the authoritative source — do not overwrite with auto-generated output.[2] Regression testing — No tests detect Simplified Chinese characters creeping back into zh-TW.js. Existing tests only verify key existence, not character-level correctness. Consider adding a CI check (e.g., a grep on known Simplified-only codepoints) to prevent the very regression this PR is fixing from recurring.
[3] New-key workflow for zh-TW — strictParity: true in languages.ts means CI blocks merges when new English keys lack zh-TW translations. Non-Chinese-speaking contributors may naturally reach for OpenCC s2t from zh.js — but standard OpenCC doesn't handle Taiwan-specific vocabulary. Consider documenting zh-TW-specific vocabulary requirements in the i18n maintenance docs to prevent terminology drift over time.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
…rite Clarify that the file is the authoritative source and should not be overwritten with auto-generated output, to prevent future maintainers from regenerating with raw OpenCC and losing manual corrections.
|
Thanks for the review and suggestions, @wenshao! |
Addresses reviewer feedback on PR QwenLM#4129 (points 2 and 3): - scripts/check-i18n.ts: Iterate over parsed zh-TW translation values (not raw file content) and report the offending key. Replace the earlier substring list with ZH_TW_FORBIDDEN_PATTERNS, which targets the three real regression categories: variant Traditional characters produced by OpenCC s2t (爲, 啓), Mainland-Chinese vocabulary (服務器, 菜單, 鏈接), and pure Simplified characters. Excludes 禁用 / 配置 / 文件 / 打開 to avoid false positives on Taiwan-valid usage. - scripts/tests/check-i18n.test.ts: Cover the new check, including negative cases for Taiwan-valid vocabulary. - docs/users/features/language.md: Document zh-TW maintenance — the vocabulary table, why raw OpenCC s2t output is not acceptable, and where the CI-enforced list lives. Co-Authored-By: Claude Opus 4.7 <[email protected]>
| | 菜單 / 菜单 | 選單 | Taiwan term for "menu" | | ||
| | 鏈接 / 链接 | 連結 | Taiwan term for "link" | | ||
| | 打開 | 開啟 | Taiwan-preferred verb for "open" (UI) | | ||
| | 爲 / 啓 / 曆 / 鏈接 | 為 / 啟 / 歷 / 連結 | Variant Traditional forms from raw OpenCC s2t — Taiwan uses the right-hand forms | |
There was a problem hiding this comment.
[Critical] The table row 爲 / 啓 / 曆 / 鏈接 → 為 / 啟 / 歷 / 連結 presents 曆 as a variant character that should be unconditionally replaced with 歷. However, 曆 is context-dependent: it is correct in calendar terms (日曆, 農曆, 西曆), while 歷 is used for "history" (歷史, 經歷).
The code (ZH_TW_FORBIDDEN_PATTERNS) correctly only flags the bigram 曆史, not standalone 曆. But a future maintainer reading this table could globally replace all 曆 with 歷, corrupting correct calendar translations.
| | 爲 / 啓 / 曆 / 鏈接 | 為 / 啟 / 歷 / 連結 | Variant Traditional forms from raw OpenCC s2t — Taiwan uses the right-hand forms | | |
| | 爲 / 啓 / 歷史 / 鏈接 | 為 / 啟 / 歷史 / 連結 | Variant Traditional forms from raw OpenCC s2t — Taiwan uses the right-hand forms. 註:「歷」vs「曆」為上下文相關(日曆 用「曆」);CI 僅檢查 `曆史` 雙字詞。| |
— DeepSeek/deepseek-v4-pro via Qwen Code /review
| @@ -433,6 +507,19 @@ export async function checkI18n( | |||
| } | |||
There was a problem hiding this comment.
[Critical] The CI check has no escape hatch or allowlist mechanism. findForbiddenZhTwPatterns() returns findings that are unconditionally pushed as blocking errors. If a legitimate translation value needs to contain a forbidden substring (e.g., 链 as part of a system name, or 为 in a quoted phrase), CI will permanently fail until someone modifies the script itself.
This is the most likely production-breaking scenario: a contributor adds an innocent translation → CI blocks all merges → oncall must reverse-engineer check-i18n under time pressure.
| } | |
| // Add an allowlist before the for loop: | |
| const ZH_TW_ALLOWED_EXCEPTIONS: ReadonlySet<string> = new Set([ | |
| // Add keys here that legitimately need forbidden substrings | |
| ]); | |
| // Inside checkI18n, before iterating findings: | |
| for (const { key, pattern, preferred } of findForbiddenZhTwPatterns(zhTWTranslations)) { | |
| if (ZH_TW_ALLOWED_EXCEPTIONS.has(key)) continue; | |
| errors.push(...); | |
| } |
— DeepSeek/deepseek-v4-pro via Qwen Code /review
| } | ||
| } | ||
|
|
||
| // Check zh-TW.js for Taiwan-vocabulary regressions (raw OpenCC output, |
There was a problem hiding this comment.
[Suggestion] The zh-TW check is silently skipped if zh-TW is not in SUPPORTED_LANGUAGES or if loadTranslationsFile fails for zh-TW.js. There is no warning that the vocabulary protection is deactivated. This hidden coupling means a future refactor of SUPPORTED_LANGUAGES could unwittingly disable the check for months.
| // Check zh-TW.js for Taiwan-vocabulary regressions (raw OpenCC output, | |
| if (zhTWTranslations) { | |
| // ... existing check ... | |
| } else if (supportedLanguages.some(l => l.code === 'zh-TW')) { | |
| errors.push( | |
| 'zh-TW locale is configured but failed to load; Taiwan vocabulary check is NOT active', | |
| ); | |
| } |
— DeepSeek/deepseek-v4-pro via Qwen Code /review
| * lets us report the offending key, and avoids matching characters inside | ||
| * file-level comments or JS syntax. | ||
| */ | ||
| export function findForbiddenZhTwPatterns( |
There was a problem hiding this comment.
[Suggestion] Hierarchical patterns cause duplicate CI errors: 历史 (Simplified) matches both { pattern: '历史' } (vocabulary-level) and { pattern: '历' } (character-level). Same for 链接 matching 链接 and 链. This produces noisy CI output that may confuse developers about what exactly to fix.
| export function findForbiddenZhTwPatterns( | |
| // After matching a pattern, break to avoid redundant matches: | |
| for (const { pattern, preferred } of ZH_TW_FORBIDDEN_PATTERNS) { | |
| if (candidate.includes(pattern)) { | |
| findings.push({ key, pattern, preferred }); | |
| break; // Stop after first (longest/most specific) match | |
| } | |
| } |
Alternatively, order ZH_TW_FORBIDDEN_PATTERNS with longer (more specific) patterns first, then break on first match.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
|
|
||
| | Avoid | Use instead | Reason | | ||
| | ------------------- | ------------------- | -------------------------------------------------------------------------------- | | ||
| | 文件 (file) | 檔案 | Taiwan term for filesystem files | |
There was a problem hiding this comment.
[Suggestion] The conventions table says "CI enforces a subset of them automatically" but doesn't indicate which rows are CI-enforced vs style suggestions. 文件 and 打開 are listed as "Avoid" but are deliberately excluded from CI (code comments explain why). Contributors may waste time investigating why their 打開 usage passes CI, or reviewers may block PRs for 文件 that CI intentionally allows.
| | 文件 (file) | 檔案 | Taiwan term for filesystem files | | |
| | Avoid | Use instead | CI enforced? | Reason | | |
| | ------------------- | ------------------- | ------------ | ------ | | |
| | 文件 (file) | 檔案 | No | … | |
Or add a footnote column marking non-enforced rows with an asterisk.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
- check-i18n.ts: Sort ZH_TW_FORBIDDEN_PATTERNS longest-first and break on first match so e.g. `历史` reports the specific bigram instead of also firing the bare `历` rule (no duplicate CI errors). - check-i18n.ts: Add ZH_TW_ALLOWED_EXCEPTIONS escape hatch so a future legitimate translation (e.g. 區塊鏈 in a UI string) can opt out by key without weakening the global pattern list. - docs/users/features/language.md: Add a "CI enforced?" column so contributors can tell which rows block CI vs. which are review-only style guidance. Replace bare `曆` in the table with the `曆史` bigram and note that `曆` is correct in calendar terms (日曆, 農曆, 西曆) — prevents a future maintainer from globally replacing 曆→歷. - Tests: Cover the dedup behavior on overlapping patterns. Co-Authored-By: Claude Opus 4.7 <[email protected]>
| if (ZH_TW_ALLOWED_EXCEPTIONS.has(key)) continue; | ||
| const candidates = Array.isArray(value) ? value : [value]; | ||
| for (const candidate of candidates) { | ||
| if (typeof candidate !== 'string') continue; |
There was a problem hiding this comment.
[Suggestion] includes() matching can produce false positives for bigram forbidden patterns across Chinese word boundaries. For example, 區塊鏈接口 (blockchain interface — legitimate Taiwan Chinese) contains 鏈 + 接 adjacently, which triggers the 鏈接 pattern. The code already acknowledges this limitation for single characters (bare 鏈 is deliberately excluded because it's valid in 區塊鏈), but bigram patterns like 鏈接 and 服務器 have the same vulnerability. Consider documenting this known limitation in the ZH_TW_FORBIDDEN_PATTERNS_RAW JSDoc or in language.md so contributors can quickly recognize it when they encounter a surprising CI failure.
| if (typeof candidate !== 'string') continue; | |
| // Note: includes() matching does not respect Chinese word boundaries. | |
| // Bigram patterns like 鏈接 may match across word boundaries | |
| // (e.g. 區塊鏈接口 = 區塊鏈 + 接口), causing false positives. | |
| // Affected keys should be added to ZH_TW_ALLOWED_EXCEPTIONS. |
— DeepSeek/deepseek-v4-pro via Qwen Code /review
Document the known limitation that `includes()`-based pattern matching does not respect Chinese word boundaries — a bigram like `鏈接` will false-positive on `區塊鏈接口` (區塊鏈 + 接口). Direct contributors to `ZH_TW_ALLOWED_EXCEPTIONS` when this happens instead of weakening the pattern list. Co-Authored-By: Claude Opus 4.7 <[email protected]>
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— DeepSeek/deepseek-v4-pro via Qwen Code /review
tanzhenxin
left a comment
There was a problem hiding this comment.
Approved. The zh-TW locale changes are scoped to translation strings, and the added i18n guard checks parsed translation values so it can report offending keys without matching comments or syntax. The focused script test and full i18n check both pass locally.
…nventions (QwenLM#4129) * fix(i18n): Correct zh-TW translations to match Traditional Chinese conventions Fix ~131 lines of Traditional Chinese (zh-TW) translations that used Simplified Chinese character forms instead of standard Traditional Chinese usage. Changes: - 文件 → 檔案 (47 occurrences) - 爲 → 為 (45 occurrences) - 啓 → 啟 (44 occurrences) - 曆史 → 歷史 (6 occurrences) - 鏈接 → 連結 (4 occurrences) - 菜單 → 選單 (3 occurrences) * fix(i18n): Replace 服務器 with 伺服器 (15 occurrences) Align with Traditional Chinese convention where 伺服器 is the standard term for 'server' in computing contexts. * fix(i18n): Update zh-TW.js header comment to prevent accidental overwrite Clarify that the file is the authoritative source and should not be overwritten with auto-generated output, to prevent future maintainers from regenerating with raw OpenCC and losing manual corrections. * fix(i18n): Add zh-TW regression check and maintenance docs Addresses reviewer feedback on PR QwenLM#4129 (points 2 and 3): - scripts/check-i18n.ts: Iterate over parsed zh-TW translation values (not raw file content) and report the offending key. Replace the earlier substring list with ZH_TW_FORBIDDEN_PATTERNS, which targets the three real regression categories: variant Traditional characters produced by OpenCC s2t (爲, 啓), Mainland-Chinese vocabulary (服務器, 菜單, 鏈接), and pure Simplified characters. Excludes 禁用 / 配置 / 文件 / 打開 to avoid false positives on Taiwan-valid usage. - scripts/tests/check-i18n.test.ts: Cover the new check, including negative cases for Taiwan-valid vocabulary. - docs/users/features/language.md: Document zh-TW maintenance — the vocabulary table, why raw OpenCC s2t output is not acceptable, and where the CI-enforced list lives. Co-Authored-By: Claude Opus 4.7 <[email protected]> * fix(i18n): Address review feedback on zh-TW check (QwenLM#4129) - check-i18n.ts: Sort ZH_TW_FORBIDDEN_PATTERNS longest-first and break on first match so e.g. `历史` reports the specific bigram instead of also firing the bare `历` rule (no duplicate CI errors). - check-i18n.ts: Add ZH_TW_ALLOWED_EXCEPTIONS escape hatch so a future legitimate translation (e.g. 區塊鏈 in a UI string) can opt out by key without weakening the global pattern list. - docs/users/features/language.md: Add a "CI enforced?" column so contributors can tell which rows block CI vs. which are review-only style guidance. Replace bare `曆` in the table with the `曆史` bigram and note that `曆` is correct in calendar terms (日曆, 農曆, 西曆) — prevents a future maintainer from globally replacing 曆→歷. - Tests: Cover the dedup behavior on overlapping patterns. Co-Authored-By: Claude Opus 4.7 <[email protected]> * docs(i18n): Note word-boundary limitation of zh-TW substring check Document the known limitation that `includes()`-based pattern matching does not respect Chinese word boundaries — a bigram like `鏈接` will false-positive on `區塊鏈接口` (區塊鏈 + 接口). Direct contributors to `ZH_TW_ALLOWED_EXCEPTIONS` when this happens instead of weakening the pattern list. Co-Authored-By: Claude Opus 4.7 <[email protected]> --------- Co-authored-by: Claude Opus 4.7 <[email protected]> (cherry picked from commit 02a65f9)
This PR fixes ~131 lines of Traditional Chinese (zh-TW) translations in
packages/cli/src/i18n/locales/zh-TW.jsthat used Simplified Chinese character forms instead of standard Traditional Chinese usage.Changes
The following categories of corrections were applied:
文件→檔案爲→為啓→啟曆史→歷史鏈接→連結菜單→選單Rationale
These corrections align the zh-TW locale with standard Traditional Chinese usage:
Testing
npm run devto verify UI displays correctly with updated translations.