Repository navigation
feat(cli): fail the i18n gate on slot drift and untranslated copy - #888
developerz-ai[bot] merged 1 commit into
Conversation
The `i18n` step of `x verify`, and `x i18n check` beside it, now compare every locale's catalog against `en`: the two must interpolate the same slots for a shared key, and a value copied verbatim is an untranslated copy only above 24 characters. Two CLI-owned codes carry the findings. The rules are the ones `examples/dummy` already asserts for itself; the framework gate is where an app cannot forget them. Dz-Task-Id: tsk_5ef5e5254792ee3a01eb91b8b08f4dae
|
Reject interpolation-slot drift and long untranslated Spanish copy in verify i18n
Work landed on #888 — watching it toward merge. 7h58m · 1140 tool calls · 669 messages Sessions: coding run 🤖 developerz.ai, an automated agent, not a human — hv-dedicated-ci-2 (developerz-ai[bot]) · model |
🟡 Reviewed2 actionable comment(s) · 1 suppressed (anti-noise) · grounded on your code ⏱ 1m 37s wall clock · 🤖 developerz.ai — automated review, running on your model and your box. |
There was a problem hiding this comment.
Review summary — 10 file(s), 2 finding(s).
Critical 0 · Major 0 · Minor 2 · Nit 0
[bot] completed: Adds shared locale-parity checks to the i18n command and verify gate. Multiline catalog values break the single-line diagnostic contract, and the command-failure regression assertion is masked by registration errors.
2 non-blocking findings (2 minor; not posted inline)
packages/cli/src/i18n-locale-parity.test.ts:255test [bot] This fixture already fails registration because its temporary app cannot resolve @ultimat3/i18n, so result.ok stays false even if parity stops failing the command. Use a successfully registered fixture and assert a passing baseline before introducing parity drift (kb:review/learnings).packages/cli/src/i18n-locale-parity.ts:28bug [bot] An identical catalog value containing a newline within its first 60 characters embeds that newline in the untranslated finding's cause, breaking the single-line diagnostic contract. Escape control characters in shownValue before capping the displayed value.
🤖 developerz.ai review — automated, running on your model and your box. What is this? · Reviewed by router/gpt-6.1-sol on box hv-dedicated-ci-0 — 153.4k in / 742 out over 2 calls; cost unknown: no price on record for router/gpt-6.1-sol.
What
The
i18nstep ofx verify, andx i18n checkbeside it, now compareevery locale's catalog against
en: the two must interpolate the sameslots for a shared key, and a value copied verbatim is an untranslated
copy only above 24 characters. Two CLI-owned codes carry the findings.
The rules are the ones
examples/dummyalready asserts for itself; theframework gate is where an app cannot forget them.
Acceptance
x verifyi18nstep reports a finding when two locales define the same key with different interpolation slots — one for a slot only the locale has, one for a slot only the reference hasesvalue exactly equal to itsenvalue is a finding only when it is longer than 24 characters; an identical value of 24 characters or less is notpackages/i18n/catalogs/<locale>.jsoni18nstep:VERIFY_STEP_NAMESis unchanged and noscripts/step is added⟦key⟧marker is still reported as X_CATALOG_MISSING_KEYSx i18n checkandx verify --only i18nreport the same locale-parity findings from one implementationx errors explain X_CATALOG_SLOT_MISMATCHandx errors explain X_CATALOG_UNTRANSLATEDeach answer with a runnable command, and both codes have a row in wiki/Error-Codes.mdcli), so themanifeststep reports no driftexamples/dummy's en/es catalogs produce no locale-parity finding — its own packages/i18n/src/catalog.test.ts already asserts both rules over the same catalogsChanges
packages/cli/src/i18n-locale-parity.ts(create) — New pure module: compare every locale's catalog against theenone and return one Finding per defect. For every key BOTH define, the two values must interpolate the same SET of{name}slots (order does not matter —interpolatesubstitutes by name); a locale value exactly equal to theenvalue is an untranslated copy only when it is LONGER than 24 characters. Read slots withplaceholdersOffrom '@ultimat3/i18n' — the framework's one definition of a slot — never a second regex. Codes: X_CATALOG_SLOT_MISMATCH, X_CATALOG_UNTRANSLATED. Each finding names the locale, the catalog [trimmed]packages/cli/src/i18n-locale-parity.test.ts(create) — Regression coverage for the new rule: matching slots, a missing slot, an extra slot, the same slots in another order (NO finding — interpolation is by name), and an identical value of exactly 24 characters (NO finding) and of 25 (a finding). Plus the two callers:catalogFindings(whatx verify --only i18nreports) andx i18n checkmust both return the new findings. Import modules by relative path, never the '@ultimat3/cli' barrel.packages/cli/src/i18n-registration.ts(edit) —catalogFindings— the compositionx verify'si18nstep reports — must include the locale-parity findings, so the gate step fails on a slot mismatch or an untranslated copy. Its signature and the existing missing-key and registration findings stay exactly as they are.packages/cli/src/cmd-i18n.ts(edit) —x i18n checkmust report the same locale-parity findings as the gate (one answer, two callers) and its summary must name them instead of printing '0 missing key(s) across 0 locale(s)';okis false when a parity finding exists. The existing missing-key, sync and add behaviour is untouched.packages/cli/src/messages.ts(edit) — One new key for thex i18n checksummary when locale-parity findings exist, rendered throughmsg(). The existing 'cli.i18n.ok' and 'cli.i18n.gaps' keys keep their current wording and parameters — cmd-i18n.test.ts asserts those summaries verbatim.packages/cli/src/error-codes.ts(edit) — Register the two new CLI-owned codes — X_CATALOG_SLOT_MISMATCH and X_CATALOG_UNTRANSLATED — in CLI_OWNED_ERROR_CODES, and give each a one-line title in CLI_ERROR_TITLES. The repo's own one-edit path isbun run scripts/new-error-code.ts <CODE> --package cli --title '…' --cause '…' --fix '…' --section 'i18n, money, time'(a tier-5 package takes neither --status nor --off-socket); it writes this file, the CLI_FIXES row and the wiki row together.packages/cli/src/mcp-errors.ts(edit) — One CLI_FIXES row per new code — a runnable command (no angle-bracket placeholder in the runnable half), sox errors explainanswers each with an instruction and not a shrug.x i18n check --jsonis the command the neighbouring X_PACKAGE_DUPLICATED row already uses.wiki/Error-Codes.md(edit) — One row per new code in the## i18n, money, timetable, beside the other X_CATALOG_* rows: what the code means, what usually causes it, and the command that fixes it. The Fix cell must be a command that runs (scripts/doc-fixes.ts holds this page's Fix column to that rule).framework.manifest.json(edit) — Regenerate withbun run manifest(never hand-edit): the generated list of every declared X_* code must carry X_CATALOG_SLOT_MISMATCH and X_CATALOG_UNTRANSLATED with ownercli, declared at packages/cli/src/error-codes.ts, or themanifeststep reports drift.wiki/I18n.md(edit) — [docs] The page enumerates whatx i18n checkreports and says only the missing-key report fails the command (line 117check reports three separate things, and only one of them fails the command; the command row at :113; the reports table at :119-123). This diff adds two more failing reports — the locale-parity findings (cmd-i18n.ts:174ok = report.ok && registration.ok && parity.length === 0) — and the Errors table (:183-188) names neither X_CATALOG_SLOT_MISMATCH nor X_CATALOG_UNTRANSLATED. Add both parity reports to the tables and both codes to the Errors table.Verification
bun run verifypassed (exit 0) in 25m 33s.Notes
Follow-up work this change creates outside its diff:
packages/i18n/src/interpolate.ts— the CLI's new locale-parity check becomes the first shipped reader ofplaceholdersOf, whose docstring still says "used by the extractor to flag arity drift" — packages/i18n/src/extract.ts never used it; the comment names a reader that does not exist, so a reader hunting for the arity-drift check lands in a file that has none🤖 Opened by developerz.ai for task
tsk_5ef5e5254792ee3a01eb91b8b08f4daeon developerz-ai/ultimate.Raised automatically by the
raw_task_triagelane.