Repository navigation
refactor(webapp): split the translations into one file per domain and locale - #359
Conversation
Signed-off-by: InstaZDLL <github.105mh@8shield.net>
… locale Signed-off-by: InstaZDLL <github.105mh@8shield.net>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Résumé |
|---|---|
Catalogues anglais webapp/src/state/i18n/en/* |
Les fichiers définissent les libellés anglais pour la navigation, les réglages, la bibliothèque, la lecture, la maintenance et d’autres domaines. en/index.ts fusionne les catalogues. |
Catalogues français webapp/src/state/i18n/fr/* |
Les fichiers ajoutent les traductions françaises typées selon les clés anglaises. fr/index.ts fusionne les catalogues. |
Sélection et application de la locale webapp/src/state/i18n/index.tsx |
translate choisit les formes plurielles et remplace les paramètres. I18nProvider initialise et persiste la locale, synchronise l’attribut lang du document, et LanguagePicker permet de changer de langue. |
Priority:
Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Refactor
Sequence Diagram(s)
sequenceDiagram
participant Navigateur
participant I18nProvider
participant localStorage
participant Document
participant LanguagePicker
Navigateur->>I18nProvider: fournit la langue du navigateur
I18nProvider->>localStorage: lit la locale enregistrée
I18nProvider->>Document: définit document.documentElement.lang
LanguagePicker->>I18nProvider: transmet la locale choisie
I18nProvider->>localStorage: enregistre la nouvelle locale
I18nProvider->>Document: met à jour document.documentElement.lang
Possibly related PRs
- InstaZDLL/waveflow-server#337 : ajoute des clés de traduction aux réglages que cette PR répartit dans des catalogues par domaine.
- InstaZDLL/waveflow-server#336 : ajoute des libellés pour les paroles, les statistiques et les mixes que cette PR place dans
playingetstats. - InstaZDLL/waveflow-server#328 : ajoute des clés pour les fichiers manquants et la gestion des bibliothèques que cette PR place dans
maintenance,foldersetlibrary.
Suggested labels: scope: web, size: xl
Merge Risk: 
Minimal · up to d5fe3
-
No specific regression is established. The translation-file split appears mergeable after normal validation.
Pre-merge checks: 
4 · 
1
-
Failed checks: 1 warning Passed checks: 4 passed-
Full details: Description check
-
Explanation
La description est complète et couvre le résumé, les changements, les validations effectuées, les limites connues et les décisions techniques. Elle ne reprend pas exactement les titres du modèle, ne fournit pas les cases à cocher du plan de test et ne contient pas la confirmation explicite AGPL/DCO, mais ces écarts ne bloquent pas l’évaluation.
-
-
Finishing Touches: 💡 1
-
📝 Generate docstrings: 💡
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
…no key is defined twice Signed-off-by: InstaZDLL <github.105mh@8shield.net>
…they now sit in Signed-off-by: InstaZDLL <github.105mh@8shield.net>
Phase A of the webapp restructuring, i18n part:
webapp/src/state/i18n.tsx(2,481 lines) becomes a directory with one file per key-prefix family and locale. Every key, value and comment is moved, not changed, and no export or signature changes. Everyimport { … } from "./i18n"/"../state/i18n"resolves unchanged tostate/i18n/index.tsx;i18n.test.tsdid not need a path change.Commits: the
git mvalone; a first split that kept the original key order; then the regrouping by prefix plus the duplicate-key test (see "Key order" below for why the first split was replaced).Old → new
state/i18n.tsxlines 1–8, 1189–1190, 2390–2481 (imports,TranslationKey,TranslationValue,Locale, context, storage key, plural rules,translate,I18nProvider,useI18n,LanguagePicker)state/i18n/index.tsxconst en = { … } as const(lines 10–1187)state/i18n/en/<domain>.ts× 17, assembled byen/index.tsconst fr: Record<TranslationKey, TranslationValue> = { … }(lines 1192–2388)state/i18n/fr/<domain>.ts× 17, assembled byfr/index.tsstate/i18n/keys.test.ts(new: no key defined twice, every domain assembled)The only visibility change:
TranslationValueis now exported fromindex.tsx, because the French domain files type against it (rule 1).New tree
Each prefix lives in exactly one file. Within a file, prefixes come in the order listed, and keys keep their original relative order.
Before: 1 file, 2,481 lines. After: 38 files, 2,751 lines including the new 43-line test (the rest of the difference is the 34 headers, the type imports, the openers/closers and the two assembly files). Largest file: 147 lines.
Key order is not preserved, and that is safe
The keys were added feature by feature, so prefixes interleave: the 975 keys form 86 runs of a single prefix across 57 prefixes (
nav.*alone appeared in 10 places). Grouping by prefix therefore changes theObject.keysorder of the assembledenandfr. That order is observable nowhere. Both objects are private tostate/i18nand are only ever indexed by key intranslate. Nothing exports them, iterates them or serialises them. Within each file, keys keep their original relative order.Typing is as strict as before
Each
en/<domain>.tskeepsas const, soTranslationKey = keyof typeof enis still the union of the 975 string literals. Eachfr/<domain>.tsisRecord<keyof typeof en<Domain>, TranslationValue>, so a missing or extra French key is an error in that file. The assembledfris stillRecord<TranslationKey, TranslationValue>. I broke the typing on purpose and restored it each time;tscexited 0 after each restore:A duplicate key is an error again
In the single object literal, a repeated key was a compile error (TS1117). Across spreads, a later domain silently overwrites an earlier one, and the types do not see it.
keys.test.tsrestores the guard. Forenand forfr, it collects every key of every domain file and asserts two things:index.tsforgets to spread fails too.The test lists the domain files with
import.meta.glob, so a new domain file is covered without editing the test. It needs no separate "French domain has exactly its English domain's keys" check: the per-domainRecordtyping already enforces that in both directions (TS2741 / TS2353 above).Proof by inversion, then restored:
Proofs (rule 4)
Translations. A throwaway script (not committed) evaluates the
enandfrliterals of the original file and imports the newen/index.tsandfr/index.ts:The same keys and the same values. Only the order differs, as explained above.
Lines. A second script counts every non-blank line of the old file against the new files, minus their one-line headers. Every key, value, plural object and comment line is present exactly as often as before. Only three old lines have no verbatim counterpart, because they had to change form:
const en = {is now the per-domainexport const <domain> = {openers;const fr: Record<TranslationKey, TranslationValue> = {is now the per-domain French openers and thefrassembly object;type TranslationValue = …is nowexport type TranslationValue = ….vitest.
bun run test: 40 files / 323 tests before; 41 / 327 after. The difference is exactly the newkeys.test.ts(4 tests). Every pre-existing test is unchanged.Playwright.
bunx playwright test --list: 508 tests in 5 files before and after. Not run locally; left to CI.Checks (in
webapp/)bunx biome check .: 10 warnings, the same asmain(allnoDescendingSpecificityinstyles/refresh.cssandstyles/styles.css), none in the new files.bun run typecheck: clean.bun run test: 327 passed.bun run build: builds.The two section comments the regrouping made wrong
The original objects carried two section comments that described their neighbourhood in the single file:
// RFC-024: lyrics from LRCLIB, statistics and mixes.and// Smart playlists and podcasts (RFC-025), kept together at the end.(the French object had only the first, in French). Once the keys are split by prefix, neither is true where it lands, so the last commit rewords them to say what each file holds:en/track.ts,fr/track.ts:// RFC-024: lyrics from LRCLIB.The French copy is now in English, like every other comment.en/stats.ts,fr/stats.ts:// RFC-024: listening statistics and mixes.This line is new.en/smart.ts:// Smart playlists (RFC-025).en/podcasts.ts:// Podcasts (RFC-025).This line is new.These comment lines are the only content lines that differ from the old file; the line-count proof above was taken before this commit.
Noticed, not changed
node_modules/.vitest-cache, on by default) reuses the transformedkeys.test.ts, including its expandedimport.meta.glob. A domain file added after the test was last transformed is invisible to it until the test file changes or the cache is cleared. The orphan inversion above passed with the cache and failed with--fsModuleCache=false. CI installs fresh and keeps nonode_modulescache, so CI is unaffected. Locally,bunx vitest run --fsModuleCache=falseor--clearCachegives the true answer. This applies to any glob-based test in the repo.