Translate the labels the extractor could not see #159

Closed
opened 2026-08-31 20:20:56 +00:00 by jcoffey-dev · 0 comments
Owner

Reported: the calendar's Day / Week / Month / Agenda buttons render English in every language.

They did, and so did two other things. All three built user-visible English out of an expression rather than writing it as JSX text, so the extraction codemod never saw them — and neither did npm run i18n:coverage, which reports 100%.

The calendar switcher

{v[0]!.toUpperCase() + v.slice(1)}

Correct English, untranslatable anywhere else. The galling part is that Day, Week, Month and Agenda were already in all nine catalogues — they are used by the "Default view" setting — sitting there unused because the buttons never asked for them. Nothing needed translating; the buttons needed to look.

They now read from a Record<View, () => string>, so TypeScript makes the map exhaustive: adding a fifth view will not compile until it has a label. The entries are functions rather than values because a module-level object would capture whichever language loaded first and then keep it for the session.

The other two

These needed new source strings, added to all nine catalogues (7 keys × 9 = 63 entries):

  • Composer.tsx — an untitled draft's window title fell back to the literal "New message", and the status line beside it was "Sending…", "Saving…", "Error", `Saved ${…}`, "Unsaved", all raw
  • CalendarView.tsx — the agenda view's own toolbar title, `Agenda from ${…}`

Verified

In a visible tab, in German: Tag / Woche / Monat / Agenda, the agenda title reading Agenda ab 31. Aug., and the composer opening as Neue Nachricht with Nicht gespeichert beside it.

npx tsc --noEmit, catalogue check (all nine at 788/803, level with each other), 109 node tests, 463 web tests, npm run build.

What this does not fix

A sweep for the same shape turns up roughly a hundred more strings that the extractor cannot see — template literals, toast.error(...)/toast.success(...) arguments, and confirmDialog({ title, confirmLabel }) props. Examples:

toast.success("Folder deleted");
confirmDialog({ title: "Delete this event?", confirmLabel: "Delete", danger: true });
toast.error(`Could not save filters: ${(err as Error).message}`);

Mostly toasts and confirmation dialogs, so a user in German hits them at the moment something goes wrong — which is the worst moment to switch languages on somebody. That is a second extraction pass, not a fix, and it wants its own PR: ~100 new source strings across nine catalogues, plus a check that stops the class coming back, since i18n:coverage reporting 100% is exactly what let these three sit in production.

Merged 2026-08-31 as coffey-labs/ihasmail@a4f7d386a6

Rebuilt from: git history, session transcript.

Reported: the calendar's Day / Week / Month / Agenda buttons render English in every language. They did, and so did two other things. All three built user-visible English out of an expression rather than writing it as JSX text, so the extraction codemod never saw them — and neither did `npm run i18n:coverage`, which reports 100%. ### The calendar switcher ```js {v[0]!.toUpperCase() + v.slice(1)} ``` Correct English, untranslatable anywhere else. The galling part is that **Day**, **Week**, **Month** and **Agenda** were already in all nine catalogues — they are used by the "Default view" setting — sitting there unused because the buttons never asked for them. Nothing needed translating; the buttons needed to look. They now read from a `Record<View, () => string>`, so TypeScript makes the map exhaustive: adding a fifth view will not compile until it has a label. The entries are functions rather than values because a module-level object would capture whichever language loaded first and then keep it for the session. ### The other two These needed new source strings, added to all nine catalogues (7 keys × 9 = 63 entries): - `Composer.tsx` — an untitled draft's window title fell back to the literal `"New message"`, and the status line beside it was `"Sending…"`, `"Saving…"`, `"Error"`, `` `Saved ${…}` ``, `"Unsaved"`, all raw - `CalendarView.tsx` — the agenda view's own toolbar title, `` `Agenda from ${…}` `` ### Verified In a visible tab, in German: **Tag / Woche / Monat / Agenda**, the agenda title reading *Agenda ab 31. Aug.*, and the composer opening as *Neue Nachricht* with *Nicht gespeichert* beside it. `npx tsc --noEmit`, catalogue check (all nine at 788/803, level with each other), 109 node tests, 463 web tests, `npm run build`. ### What this does not fix A sweep for the same shape turns up roughly **a hundred more** strings that the extractor cannot see — template literals, `toast.error(...)`/`toast.success(...)` arguments, and `confirmDialog({ title, confirmLabel })` props. Examples: ```js toast.success("Folder deleted"); confirmDialog({ title: "Delete this event?", confirmLabel: "Delete", danger: true }); toast.error(`Could not save filters: ${(err as Error).message}`); ``` Mostly toasts and confirmation dialogs, so a user in German hits them at the moment something goes wrong — which is the worst moment to switch languages on somebody. That is a second extraction pass, not a fix, and it wants its own PR: ~100 new source strings across nine catalogues, plus a check that stops the class coming back, since `i18n:coverage` reporting 100% is exactly what let these three sit in production. **Merged** 2026-08-31 as coffey-labs/ihasmail@a4f7d386a601 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.