Internationalization

The app speaks two languages properly: English (the base locale) and Serbian in Latin script (sr-Latn-RS), using Paraglide JS.

How it is wired

Switching languages

LocaleProvider sets <html lang> and keys the subtree on the locale, so switching re-renders every messages.* call with no reload. The shell's header, footer and <meta name="description"> live outside the app tree, so ShellText writes their text on mount and on every locale change; the static index.html/404.html keep brand fallbacks and a bilingual <noscript> (the page must say something even with JavaScript off).

Serbian dialect

sr-Latn-RS uses standard Serbian ekavica (napred, levo, pomera, ovde, neverovatno, vreme), not the ijekavica used in Bosnian/Croatian (naprijed, lijevo, pomjeri, ovdje, nevjerovatno, vrijeme).

Key taxonomy

Keys are flat snake_case, prefixed with the feature that owns them: palette_, controls_, level_picker_, block_for_, outcome_details_, skin_chooser_, playground_cell_ and so on. Keys name the element, not the string (palette_drop_to_delete, not drop_to_delete_text). A small set of truly shared words is promoted to common_ (common_close, common_collapse, common_expand); everything else stays in its owner's prefix, because a translator could reasonably write a different sentence for two places that happen to share an English word.

Plurals and variables

Level titles

Level names are content, not UI chrome, so they live in the level JSON as a locale-keyed record ("title": { "en": "...", "sr-Latn-RS": "..." }), resolved with localized(values, locale) (current locale, then base, then the first entry). Levels that introduce a block or hazard name it in the title in both locales.

What is not translated

The documentation in documentation/ is written in English only. It is aimed at developers and reviewers, and every page is a single source file; a translated guide would be a folder per locale with its own link-checker run. The app itself is the bilingual part.

Adding a string

  1. Add the key to messages/en.json in sorted position, with the right prefix.
  2. Add the same key to messages/sr-Latn-RS.json (every locale must have it).
  3. Import messages from the lib/i18n seam and replace the literal.
  4. bun run i18n, then bun validate.

Next: Code export.