# Languages ## The i18n layer is hand-rolled, and that's measured rather than dogmatic On this toolchain `vue-i18n` costs **+34.4 kB gzip** against **+0.55 kB** for `src/i18n/` (about 240 lines, 112 of them code), for 3 languages and roughly 90 strings with no dates or currencies to format, and it doesn't catch unknown keys at compile time, where a typed `t()` does. The day this needs lazy loading, more than ten languages, or non-developer translators, switching is possible without touching the callers. ## `fr.ts` is the reference `en.ts` and `zh.ts` are typed `typeof fr`, so a forgotten key is a named compile error. **Definitely no `as const` on `fr.ts`**: every value would become its own literal type and *all* the translations would be rejected. ## Detection is not written to storage Only an **explicit** choice goes there (`bloub:langue`). Without that distinction, a first visit from abroad would freeze that language forever. ## Quotes, spaces and punctuation belong to the translation Not to the code: French wants « … » with non-breaking spaces, English "…", Chinese full-width punctuation and no space before its units. A component that adds a `« ` itself breaks two languages out of three. Same rule for numbers: `Intl` handles them (`nombre`, `pourcentage`, `secondes`), never a `.replace('.', ',')`. ## `src/ui/timeline.ts` is pure, no localised formatting in it Durations go through `secondes` / `secondesCourtes` from `@/i18n`, which knows the current language. Only `mmss` stays there: it has neither a unit nor a decimal separator. ## A montage with no name is the seed montage `defaultCycle()` has `name: ''`, and the display resolves `nomDeCycle()`: that's what makes it follow the language. Writing "Default cycle" in there would freeze it, since the name goes to `localStorage` on the first visit and becomes user data that changing language no longer retranslates.