Feature/i18n - #138
Merged
Merged
Conversation
CalendarWidget imported Calendar and built momentLocalizer(moment) but never used either — the widget draws its own month, week, and day views through MonthDayCell and getNext7Days. The import, the localizer, and the library's stylesheet were pure weight, and the only other `Calendar` matches in the file are unrelated identifiers (editingCalendar, handleSaveCalendar). Found while sizing i18n (#137): a react-big-calendar instance would have needed its own translated `messages` object, and the momentLocalizer is listed in #120 as a blocker for replacing moment. Neither is real. CalendarWidget chunk 237 KB -> 110 KB (-54%) total client JS 1,803 KB -> 1,676 KB (-127 KB) bundled CSS 18 KB -> 7 KB (react-big-calendar.css gone) node_modules 17 packages removed Verified: client 79/79, build clean, and a browser check confirming the calendar still renders its grid, weekday headers, and 31 demo event chips with zero console errors, including after toggling between month and week views. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sh (#137) First phase of multi-language support. Everything here is complete and working; the remaining widgets still render English and are listed below. Infrastructure - i18next + react-i18next + language detection, set up in client/src/i18n. English is bundled (it is the fallback); other languages and their date locales load on demand, so an English household downloads nothing extra. - Language is per display, stored in localStorage like theme and screensaver settings, so a kitchen tablet and a phone can differ. A display that has never chosen follows the `default_language` server setting if one is set. - Missing keys log a warning in development naming the namespace and key. Date seam (client/src/utils/dateUtils.js) - Every user-visible date now formats through one module, so changing language re-formats the whole UI from a single place, and replacing moment later (#120) touches this file rather than every call site. - Display helpers are separated from machine ones. toDateKey and friends deliberately stay unlocalized: those YYYY-MM-DD strings are map keys, API parameters, and event-matching identifiers, and localizing them would break calendar lookup silently. Explicit week-start setting - Chosen deliberately over deriving it from the language: households disagree with their locale often enough, and it reflows the whole calendar grid. Translated so far - Chore widget end to end: chores, bonus chores, prize store, split spending, quick spend, transfer, snooze, and the redemption celebration. - Admin Panel language and week-start controls. - Day-of-week checkboxes show translated labels while still storing the English key that crontab conversion and the API depend on. Tooling - npm run check:i18n fails if any English key is missing from another language, wired into CI for the frontend job. - docs/guides/translations.md covers adding a language, adding strings, and the traps (machine formats, stored values, user data, server errors). Not yet converted, still English in every language: the rest of the Admin Panel, and the Calendar, Weather, and Photos widgets. Each is a self-contained follow-up — add its namespace to NAMESPACES once its locale files exist. Verified: client 79/79, build clean, translation check 115/115 keys at 100%, and an 8/8 browser pass covering English default, switching to Spanish, no leaked keys, persistence across reload, the html lang attribute, and zero page errors. Spanish ships as separate 1.4-2.8 KB chunks per namespace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Weather widget
- All UI strings move to a weather namespace: the settings dialog, air quality
levels, empty state, and the location errors.
- The three OpenWeatherMap URLs now pass lang. OWM localizes its own condition
text ("clear sky", "few clouds"), so those strings translate server-side for
free instead of needing a lookup table here. The demo snapshot stays English.
Tab icon picker
- Icon labels were stored alongside the icon list as English strings. The list
now carries only the stable `name` identifier and the label is looked up at
render, so what gets persisted on a tab is unchanged.
- The dialog's title and save-button defaults resolve from translations rather
than inline English, so a caller passing nothing still gets localized copy.
Plugins
- Plugin iframes already received theme and device; lang now rides the same
channel. A plugin that ships translations can follow the display's language,
and one that ignores the parameter is unaffected.
Verified: client 79/79, build clean, translation check 172/172 keys at 100%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves the photo widget's strings into a photos namespace: the settings popover (sources, per-view count, size, transition, speed), the source dialog for both Immich and HomeGlow Photos, the empty states, and the confirm/error messages. The size, speed, and transition options keep their stored values (880, 3000, "fade") and translate only the visible label, so existing device settings keep working untouched. Verified: client 79/79, build clean, translation check 211/211 keys at 100%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…locales
Calendar widget
- All 87 strings move to a calendar namespace: the toolbar, day dialog, event
editor, source management for ICS/CalDAV/Apple/Google, and the whole
settings panel.
- Weekday names in the grid header and in the week/month start pickers are now
localized. The stored values stay the English keys ('monday'), so existing
tab settings and the crontab conversion are untouched.
- Display dates route through the date seam; the machine formats it uses as
map keys and API parameters (YYYY-MM-DD and friends) deliberately do not.
Dates now use Intl.DateTimeFormat
The seam originally drove moment's own locale, which did not work: Vite
pre-bundles moment as an optimized dependency, so `import('moment/locale/es')`
registered the locale on a different module instance than the one formatting,
and the UI silently stayed in English while `moment_locale_es.js` loaded fine.
Rather than fight the bundler, display formatting now uses the platform's
Intl.DateTimeFormat. That fixes it, drops a 61 KB locale chunk, gives every
language the browser knows for free, and formats patterns per locale rather
than translating names inside an English-shaped pattern — Spanish renders
"agosto de 2026", not "agosto 2026".
Formatters are cached per locale and options, since the month grid formats
hundreds of cells per render.
Dropped the global week-start setting added earlier in this branch
The calendar has had per-tab "Week View Start" and "Month View Start" controls
since #127. A second, global control would have fought them. The existing ones
are localized instead, which is what the setting was for.
Verified: client 79/79, build clean, translation check 278/278 keys at 100%,
and a browser pass in both languages — English shows "August 2026" with
Sun/Mon/Tue, Spanish shows "agosto de 2026" with dom/lun/mar/mié, both render
all 31 demo event chips with no page errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the chore-definitions table, the schedule list and its filters, and the
schedule editor: recurrence modes, sleep interval, due date and time, sound
and reminder settings, transferable/snooze toggles, and both delete dialogs.
Two constants moved off hardcoded English:
- Day checkboxes now take their labels from the locale (index 0 = Sunday, so
the crontab values are unchanged).
- Crontab presets carry a key instead of a label; the value stays the crontab
expression and only the visible text translates.
Not yet converted in this file: a dozen short table headers and buttons
(Title, Actions, Edit, Delete, Duplicate, Refresh, and similar) plus the two
composed strings for "Once Completed {interval}" and "Next occurrence: {when}".
They still render English in every language.
Also fixes six props this pass initially broke: the bulk replacement rewrote
plain-text values that turned out to sit inside quoted JSX props, producing
title="{t('key')}" — a literal string with braces rather than an expression.
All six are unwrapped, and the check for that pattern now reports zero.
Verified: client 79/79, build clean, translation check 354/354 keys at 100%,
and a byte-level check that the diff introduces no mangled characters (the
en-dash in "Weekdays (Mon–Fri)" and the Spanish accents both survive).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the last of the file: table headers (including the data-label attributes CSS renders as headings on mobile), the dialog buttons, the recurrence-mode radios and interval units, and the two composed strings for 'Once Completed (interval)' and 'Next occurrence: when', which now interpolate instead of concatenating. The delete-chore dialog's warning takes its schedule count as an interpolated value, and its prompt is composed so the chore's own title stays bold and untranslated. ChoreSchedulesTab now reports zero remaining hardcoded strings. Verified: client 79/79, build clean, translation check 359/359 keys at 100%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The last and largest surface: 239 strings across every tab — widgets and
plugin management, tabs, devices, colours, screensaver, vacation mode, auto
dark mode, users, chore rewards and sounds, prizes, security, connections, and
about — plus all the toast and confirm messages.
Stored values are untouched throughout. Refresh intervals keep their
millisecond values, prize costs their numbers, plugin ids their strings; only
the visible label translates. The data-label attributes get translated too,
since CSS renders them as the row headings on mobile.
Two composed strings now interpolate rather than concatenate: the prize cost
line (which varies on whether the prize is repeatable) and the screensaver's
tab count.
With this, every user-facing surface in the app is translated. Server error
strings and console logging stay English on purpose — the first are
diagnostics rather than user copy, the second keeps issue reports searchable.
The translations guide now records the two traps this work hit: PowerShell's
Set-Content mangles emoji (use Node for scripted edits), and a bulk replace
can produce prop="{t('key')}" — a literal string with braces that builds fine
and renders the key to the user. Both have a grep to catch them.
Verified: client 79/79, build clean, translation check 588/588 keys at 100%,
and a browser pass walking all eight admin tabs in both languages — no raw
keys leak, Spanish headings render, no English headings remain. Two React
prop warnings appear in the console; they are pre-existing, confirmed by
stashing this work and reproducing them at the previous commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closed
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.
No description provided.