feat(i18n): complete the locale backfill — all ten packs reach full key parity (#2872) - #2909
Merged
Merged
Conversation
…ey parity (#2872) Translates the remaining 275 keys x 8 packs = 2,200 strings, closing #2872. Largest namespaces: `grid` (101, mostly the import wizard), `gantt` (58), `dashboard` (25), plus a long tail across list/auth/fields/marketplace/ capability and nine others. Every pack is now at parity with en: 2,495 of 2,499 keys, zero keys en lacks. The four-key remainder is the outbound-message set, absent by design so t() falls through to English and the cloud confirm gate keeps recognising it. P3 is now enforceable. The four-namespace ratchet from #2905 is replaced by all-locales-key-parity.test.ts, asserting: every pack defines every en key; no pack defines a key en lacks (#2872 part b was 74 keys of exactly this); and placeholders match en per string — both `{{count}}` and the single-brace `{count}` form that two gantt.autoScheduleDlg keys use because their call site does a literal .replace('{count}', …) instead of i18next interpolation. All three assertions were mutation-tested, single-brace form included. A bug the test suite could not have caught: the first merge pass produced DUPLICATE keys in four packs. The key list is the union of what is missing across all eight, but the insert ran unconditionally, so packs that already had detail.created/detail.updated got a second copy. Every test still passed — at runtime the later property wins, so the parity check saw a consistent object. tsc caught it as TS1117 during `turbo build`; ESLint does not flag it and a runtime test cannot, since the duplicate is collapsed before JS sees the object. The merge script now filters per pack. Translations are model-generated, and dense domain terminology (Gantt dependency types, the import wizard's upsert/match-field vocabulary) is where that is weakest. Shipping as a reviewable first draft, not a finished localization. Mechanically verified: parity both directions, placeholder shape per string, and that no outbound agent message was translated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 28, 2026 07:49
This was referenced Jul 28, 2026
Closed
os-zhuang
added a commit
that referenced
this pull request
Jul 28, 2026
… (#2912) CloudAiModelStatus rendered `report.summary` verbatim — the most prominent line on the panel, in English for every locale. Reading objectstack-ai/cloud settled how to fix it. The server CANNOT localize that string as currently built: service-ai/src/effective-model.ts:117 assembles it as a hard-coded English template literal with no locale parameter, and routes/ai-routes.ts:395 declares `handler: async () => …` — taking no request argument, so it cannot read Accept-Language even though createAuthenticatedFetch has been sending it since #1319. But no server change is needed: every ingredient of the sentence is already in the structured payload (conversational.model/source, structured.model/pinned, routing.free/paid). The issue proposed "return structured data instead of a sentence" as the better fix — the server was already doing that, the client just wasn't using it. The panel now composes the line itself. sourceLabel() already produced exactly the clauses the server hand-rolls ("pinned by X", "code default (no env override)", "same as build/ask"), so no new source vocabulary was needed. A dropped diagnostic, not just untranslated text: the client's EffectiveModelReport never declared `routing`, which the server has always sent conditionally. Its only appearance anywhere was inside the English summary, so non-English admins could not see the plan→model routing policy at all. Now declared and surfaced. Also fixed: attributeSource emits the bare token 'unknown' when the adapter cannot report a model, and sourceLabel fell through to rendering it raw. Four keys added to all ten packs, so the full-parity guard from #2909 stays green. The panel had no test coverage at all; it now has five, mutation-tested by restoring `<p>{report.summary}</p>` — which fails four of them. Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2872.
Translates the remaining 275 keys × 8 packs = 2,200 strings. Largest namespaces:
grid(101, mostly the import wizard),gantt(58),dashboard(25), plus a long tail acrosslist,auth,fields,marketplace,capabilityand nine others.enlacksThe four-key remainder is the outbound-message set, absent by design so
t()falls through to English and the cloud confirm gate keeps recognising it —outbound-agent-messages.test.tsowns that invariant, and this PR does not touch it.P3 is now enforceable
The four-namespace ratchet from #2905 existed only because full parity would have been a permanently red build. That restriction is obsolete, so it's replaced by
all-locales-key-parity.test.ts:enkey;enlacks — 语言包 key 缺口:8 个语言各缺 357–359 键,74 键只存在于 zh.ts,且没有任何 parity 测试 #2872 part (b) was 74 keys of exactly this, hidden behind a component-private fallback that made English "happen to" render;enper string, covering both{{count}}and the single-brace{count}form. Twogantt.autoScheduleDlg.*keys use single braces on purpose: their call site does a literal.replace('{count}', …)rather than i18next interpolation. A dropped placeholder renders a sentence with a hole in it and no error, so this is checked mechanically rather than by eye.All three assertions mutation-tested, single-brace form included.
A bug the test suite could not have caught
The first merge pass produced duplicate keys in four packs. The key list is the union of what's missing across all eight, but the insert ran unconditionally — so packs that already had
detail.created/detail.updatedgot a second copy.Every test still passed. At runtime the later property simply wins, so the parity check saw a perfectly consistent object.
tsccaught it asTS1117duringturbo build. ESLint does not flag it, and a runtime test cannot — the duplicate is collapsed before JS ever sees the object. The compiler is the only possible guard here, and CI runs it. The merge script now filters per pack against what that pack actually defines.Worth recording because it's the second time in this issue that
buildcaught somethingtype-check+ tests did not (the first was the unterminated doc comment in #2900).How the 2,200 strings were produced safely
The merge script refuses to write until it has proven it understands each file:
'developer-tools'and values wrapping to the next line);Verification
turbo build: 44/44 — the check that caught the duplicate-key bug.Important
Translation quality. These are model-generated, and dense domain terminology — Gantt dependency types (
Finish → Start), the import wizard's upsert / match-field / type-coercion vocabulary — is exactly where that is weakest. I raised this before starting and recommended human or TMS translation for this batch specifically; the work was requested anyway, so it ships as a reviewable first draft, not a finished localization. Native review is still worthwhile before treating any of it as final.What is mechanically verified: key parity in both directions, placeholder shape per string, and that no outbound agent message was translated.
Generated by Claude Code