Skip to content

fix(#687): triage the I3 literal tail — 21 sites, 7 files on the ratchet - #751

Merged
Weegy merged 3 commits into
mainfrom
fix/687-literal-triage
Aug 20, 2026
Merged

fix(#687): triage the I3 literal tail — 21 sites, 7 files on the ratchet#751
Weegy merged 3 commits into
mainfrom
fix/687-literal-triage

Conversation

@Weegy

@Weegy Weegy commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Follow-on to #747, which made the I3 category measurable. This spends that measurement: translate goes from 40 → 19, and the per-file ratchet grows from 1 → 7 files.

Two scanner rules, learned from the triage

Neither was guessed — both come from looking at what the first pass actually returned.

1. Version / phase / build stamps are diagnostic, not prose.

site literal
store/page.tsx:229 omadia · v1
store/[id]/page.tsx:464 omadia · v1 · Slice 1.1
store/builder/page.tsx:181 B.0 Draft-Store
Workspace.tsx:2070 Phase B.5 (Workspace-UI)

They sit in product footers and read like text, but localising them changes an identifier an operator quotes back in a bug report. One of them is telling: store/builder/page.tsx already renders {t('footer.phaseLabel')} and then the hardcoded value right beside it — the label was translated, the value was not.

Checked in the dangerous direction: the diagnostic bucket contains exactly those four and nothing legitimate.

Whether internal roadmap vocabulary (Slice 1.1, Phase B.5) belongs in a shipped footer at all is a product question. Flagged, not answered.

2. Every placeholder= is a judgement call, not only the one-word ones.

#747 forced single-token placeholders to placeholder and left multi-word ones in translate. The triage showed that was the wrong cut — of six hits, five were example values that must not be translated (Release sign-off, Release approver, id-1, id-2, …, email | uri | date-time | uuid) and one was a real instruction (Reason (optional)). Nothing in the syntax separates an example value from an instruction, so multi-word placeholders now land in review — the bucket that means "a human decides", which is exactly true here.

The translations

27 keys across six files, all of which reach zero actionable hits and join SWEPT_COMPONENTS.

The judgement calls worth naming, since that is what review exists to force:

  • block / boost (kg-priorities action select) → German. GLOSSARY.md puts descriptions of what is happening in German and keeps product nouns English; these are actions an operator picks, so blockieren / hervorheben. The value attributes stay block / boost — the wire contract is untouched.
  • Vault, Bucket → stay English (product noun, established loanword) but are now catalogue keys with explicit brand / loanword entries in i18n-identical-allowlist.json, rather than implicit exemptions.
  • Status → identical in both locales, allowlisted as loanword.
  • ResolutionBehebung. Not Auflösung, which in German software reads as screen resolution.

On that allowlist point: these five would have passed silently either way. i18n-validate exempts short capitalised nouns from the identical-value gate by design — a gap its own comment names as the follow-up. Listing them anyway puts the decision where a reader looks instead of relying on a rule that happens not to fire.

Verification

  • npm run i18n:check — OK, 3760 keys
  • tsc --noEmit clean; eslint clean on every changed file
  • npm test742/742 across 88 files
  • Mutation check: restoring one literal (label={t('prefix')}label="Prefix") turns exactly the new per-file ratchet red; reverting returns 15/15.

What is left, and why

19 translate hits remain. Most are sentences split across JSX elements — the scanner sees rendered markdown (, Stream live ·, · last activity, s tool-time, User · Tenant — where the fix is an ICU message with placeholders, not a 1:1 key. Restructuring those changes markup rather than just extracting strings, so it belongs in its own pass rather than being smuggled into a translation PR.

The rest are taglines (an Agentic OS) and format names (OpenAPI 3 (JSON)) that need a brand decision before a key.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Weegy added 2 commits August 19, 2026 17:26
Follow-on to #747, which made the category measurable. This spends that
measurement: it works the `translate` list down from 40 to 19 and puts six
more files on the per-file ratchet.

Two scanner rules, both learned from the triage rather than guessed:

1. Version, phase and build stamps are `diagnostic`, not prose. `omadia · v1`,
   `B.0 Draft-Store`, `Phase B.5 (Workspace-UI)` and `omadia · v1 · Slice 1.1`
   sit in product footers and read like text, but localising them changes an
   identifier an operator quotes in a bug report. (One of them already had a
   translated LABEL — `t('footer.phaseLabel')` — with the value left hardcoded
   beside it.) Whether internal roadmap vocabulary belongs in a shipped footer
   at all is a product question, flagged rather than answered here.

2. Every `placeholder=` is a judgement call, not only the one-word ones. Of six
   hits, five were example VALUES that must not be translated (`Release
   sign-off`, `Release approver`, `id-1, id-2, …`, `email | uri | date-time |
   uuid`) and one was a real instruction (`Reason (optional)`). Nothing in the
   syntax separates them, so multi-word placeholders now land in `review`.

The `diagnostic` bucket was checked in the dangerous direction: it contains
exactly those four stamps and nothing legitimate.

27 keys across six files. The judgement calls worth naming: `block`/`boost` are
actions an operator picks, so by GLOSSARY.md they are German
(`blockieren`/`hervorheben`) while their `value` attributes stay; `Vault` and
`Bucket` are product/loan nouns that stay English but are now catalogue keys
with explicit allowlist reasons rather than implicit exemptions. They would
have passed silently either way — `i18n-validate` exempts short capitalised
nouns by design, a gap its own comment names as a follow-up.

Verification: i18n:check OK (3760 keys), tsc clean, eslint clean, 742/742
vitest. Mutation check: restoring one literal turns exactly the new per-file
ratchet red.

Remaining: 19 `translate`, most of them sentences split across JSX elements
(`rendered markdown (`, `Stream live ·`, `s tool-time`) that need ICU
restructuring rather than a 1:1 key — deliberately left for a separate pass.
…ings were invisible

Extending the scan to the shapes it could not parse found a hardcoded GERMAN
tooltip and two German badge labels, all in `chat/page.tsx`, all shipped.

Three gaps, each a construct the first version simply did not visit:

1. Template literals with substitutions. `title={`Triage-Klassifizierer:
   ${a} → ${b}`}` is the shape every interpolated status line uses, and
   `stringValueOf` handled only plain string literals. Note this one also slips
   past the repo's own `git diff | grep '[äöüÄÖÜß]'` self-check in
   `web-ui/CLAUDE.md` — "Triage-Klassifizierer" has no umlaut.

2. Strings in plain objects rendered later. `const verdict = { simple: { label:
   'einfach' } }` then `<span>{v.label}</span>` — neither JSX text nor a JSX
   prop. Two German labels lived there. Restricted to the same closed prop
   list, so the only false positive it can produce is a `label` on an API
   payload; 24 hits repo-wide, no explosion.

3. My own over-exemption. #747 skipped ALL of `chat/page.tsx` because
   `web-ui/CLAUDE.md` exempts `MOCK_KG_WALK` — but the docs exempt a SYMBOL,
   not a file, and the fixture is a const at line 79 while the German tooltip
   sat at 1251. Exemptions are now scoped to the declaration. An exemption must
   be no wider than the thing it excuses.

Two smaller corrections that fell out of this:

- Rendering a template as `{}` per substitution collided with the placeholder
  rule (`\{[^}]*\}`), which would have marked every interpolated string exempt
  and silently re-hidden the class this change exists to reveal. Classification
  now runs on the literal parts; only the display form carries `{}`.
- Text containing umlauts or ß is `translate` outright — German in a component
  is the severest class in the repo rules, so it outranks every exemption
  below. Precision over recall: it cannot false-positive, and it cannot catch
  umlaut-free German, which still surfaces as `review`.

The widened scan immediately turned the ratchet red on `ListView.tsx` — a
`title={`User: ${user}`}` that had been invisible when the file was declared
swept. That is the ratchet working: it is content-based, so widening the
question re-asks it of every swept file.

Verification: i18n:check OK (3766 keys), tsc clean, eslint clean, 742/742
vitest. Mutation check: restoring `label: 'einfach'` makes the object-label
detector report it again; reverting returns 0.
@Weegy

Weegy commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a second commit that widens the scan — and it found three shipped German strings that the first version structurally could not see.

Three blind spots, each a construct the parser never visited

1. Template literals with substitutions. stringValueOf handled plain string literals only, so this was invisible:

title={`Triage-Klassifizierer: ${routing.classifierModel}${routing.bucket}${routing.model}`}

A hardcoded German tooltip in chat/page.tsx. Worth noting it also slips past the repo's own self-check in web-ui/CLAUDE.mdgit diff | grep '[äöüÄÖÜß]' finds nothing, because "Triage-Klassifizierer" has no umlaut.

2. Strings in plain objects rendered later.

const verdict = { simple: { label: 'einfach' }, complex: { label: 'komplex' },};

<span>{v.label}</span>

Neither JSX text nor a JSX prop. Two more German labels, same file. Restricted to the same closed prop list, so the only false positive it can produce is a label on an API payload — 24 hits repo-wide, 10 of them actionable, no explosion.

3. My own over-exemption in the first commit. It skipped all of chat/page.tsx because web-ui/CLAUDE.md exempts MOCK_KG_WALK. But the docs exempt a symbol, not a file — the fixture is a const at line 79, and the German tooltip sat at 1251. Exemptions are now scoped to the declaration. An exemption must be no wider than the thing it excuses, and that is the same over-broad-blessing failure this whole issue is about.

Two corrections that fell out

  • Rendering a template as {} per substitution collided with the placeholder rule (\{[^}]*\}), which would have marked every interpolated string exempt and silently re-hidden the class the change exists to reveal. Classification now runs on the literal parts; only the display form carries {}.
  • Text with umlauts or ß is translate outright — German in a component is the severest class in the repo rules, so it outranks every exemption below. Precision over recall on purpose: it cannot false-positive, and it cannot catch umlaut-free German, which still lands in review. That is the honest limit; no cheap test separates the two languages, and guessing would put real English labels in the wrong bucket.

The ratchet earned its keep

Widening the scan immediately turned ListView.tsx red — a title={User: ${user}} that was invisible when the file was declared swept in #747. That is the point of making the guard content-based rather than a list of strings: widening the question re-asks it of every swept file, instead of leaving old files blessed under the old, narrower question.

Verification

i18n:check OK (3766 keys) · tsc --noEmit clean · eslint clean · 742/742 vitest. Mutation check: restoring label: 'einfach' makes the object-label detector report it again; reverting returns 0.

@Weegy
Weegy enabled auto-merge (squash) August 20, 2026 05:27
@Weegy
Weegy merged commit 825dc71 into main Aug 20, 2026
9 checks passed
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.

1 participant