Internationalisation
Le frontend web est bilingue français et anglais. Il n'utilise aucune bibliothèque d'internationalisation (ni next-intl, ni i18next) et aucun fichier de traduction : chaque texte est écrit dans le composant sous la forme d'une paire t('texte français', 'English text'). Le français est la langue par défaut et la langue de référence du projet. Le même mécanisme existe dans l'application mobile, ce qui permet de copier un composant d'un support à l'autre.
La fonction t
La fonction est fournie par le contexte de frontend-web/src/theme/ThemeProvider.tsx, qui gère à la fois le thème et la langue :
t: (frText: string, enText: string) => (language === 'FR' ? frText : enText)Un composant la récupère avec le hook useAppTheme() :
const { t, language } = useAppTheme();
<h1 className="text-title font-bold">{t('Billetterie', 'Ticket Office')}</h1>Le code compte un peu plus de 500 appels à t( dans src/. Les textes dynamiques sont construits avec des gabarits dans chaque branche, par exemple :
t(
`Pièce envoyée le ${formatDateShort(identity.submittedAt)}. La Mairie la vérifie.`,
`Document sent on ${formatDateShort(identity.submittedAt)}. City Hall is reviewing it.`
)Il n'y a ni gestion des pluriels, ni clés de traduction. Les accords se font à la main, ou sont contournés par une forme invariable comme « ticket(s) confirmé(s) » dans le reçu de la billetterie.
Choix et mémorisation de la langue
| Élément | Emplacement |
|---|---|
| Valeurs possibles | LanguageCode : FR ou EN (user.types.ts) |
| Mémorisation | localStorage, clé bl.pref.language |
| Attribut HTML | <html lang="fr"> ou lang="en" |
| Bascule | PreferenceToggles (en-tête, barre latérale, page de connexion) et bouton « Langue » de /profil |
| Synchronisation serveur | /profil seulement : PUT /user/preferences avec language |
Au chargement, le script themeInitScript du layout racine pose lang à partir de bl.pref.language, avant le rendu de React. ThemeProvider lit ensuite l'attribut lang du document grâce à useSyncExternalStore et à un MutationObserver. Changer de langue modifie simplement l'attribut et la clé ; tous les composants abonnés se réaffichent.
La langue du navigateur (navigator.language) n'est pas consultée : un premier visiteur voit toujours le site en français. Le rendu serveur se fait aussi en français ; un visiteur qui a choisi l'anglais voit donc brièvement le texte français avant l'hydratation, puisque toutes les pages sont des composants client.
La préférence envoyée au backend par /profil est rangée dans Redis (clé user:prefs:<userId>) et renvoyée dans GET /user/profile sous preferences, mais aucun écran ne la relit. Le backend ne s'en sert pas non plus : ses emails et ses messages restent en français. La langue ne suit donc pas l'utilisateur d'un navigateur à l'autre.
Documents légaux
Les six documents légaux (CGU, règlement de participation, mentions légales, politique de confidentialité, politique relative aux traceurs, charte de transparence) ne passent pas par t. Ils sont écrits en entier dans content/legal.ts, une version FR et une version EN par document, et getLegalDocument(id, language) renvoie la version de la langue courante, ou le français à défaut. La date de version vient de LEGAL_VERSION (« Version du 12 novembre 2026 »). Le même fichier sert à l'application mobile. La charte de transparence affichée par TransparencyCharter est désormais lue dans ce fichier et non plus dans GET /lottery/transparency-charter, que lotteryApi déclare toujours.
Ce qui n'est pas traduit
| Texte | Langue | Raison |
|---|---|---|
Messages d'erreur et de succès du backend (data.message) |
français | Rédigés en dur dans le backend, affichés tels quels par getErrorMessage et les feuilles d'erreur |
Messages de repli de getErrorMessage (« Serveur injoignable... ») |
français | Texte fixe dans baseApi.ts |
| Titre par défaut de la feuille d'erreur réseau (« Oups, réseau instable ») | français | Valeur de repli dans uiSlice.ts |
Loader de RequireRole (« Vérification de la session… ») et de /invite |
français | Texte fixe |
Libellé du Spinner (« Chargement ») |
français | Texte fixe |
Métadonnées de page (<title>, description, Open Graph fr_FR) |
français | layout.tsx |
Pages d'erreur error.tsx, not-found.tsx |
français avec une phrase anglaise | Rendues hors du contexte de traduction |
global-error.tsx |
français | Remplace tout le document |
| Rapport imprimable de la Mairie | français | Gabarit de download.ts |
| Contenu renvoyé par l'API (carrousel, missions, notifications) | celui du backend | Les composants ont un texte de repli bilingue quand l'API ne répond pas |
Libellés des rôles dans les messages (USER, ADMIN) |
codes techniques | Interpolés tels quels |
Formats de nombres et de dates
| Fonction | Fichier | Localisation |
|---|---|---|
formatNumber(value, locale) |
formatters.ts |
Intl.NumberFormat en fr-FR ou en-GB selon la langue passée |
Solde de WalletCard |
WalletCard.tsx |
Intl.NumberFormat selon la langue, 4 décimales au plus |
formatEth, formatPoints, formatPercentage |
formatters.ts |
Non localisés : point décimal, suffixes ETH et pts |
formatDateShort |
date.ts |
Format fixe JJ/MM/AAAA HH:MM, fuseau du navigateur |
formatRemainingTime |
date.ts |
Heures, minutes et secondes sur deux chiffres |
Certains montants sont donc affichés avec une virgule et d'autres avec un point selon le composant. Les textes fixes en français écrivent aussi 0,01 ETH fictif en dur.
Ajouter ou modifier un texte
- Écrire le texte dans le composant avec
t('...', '...'), en français d'abord. Ne jamais laisser une chaîne visible sans sa version anglaise. - Pour un texte venant du backend, prévoir un texte de repli bilingue côté client, comme le fait
OnboardingCarousel. - Pour un document légal, modifier les deux versions (
FRetEN) dansfrontend-web/src/content/legal.ts, recopier le fichier à l'identique dansmobile-app/src/content/legal.tset mettre à jourLEGAL_VERSION. - Garder la même formulation que l'application mobile quand l'écran existe sur les deux supports.
- Pour un attribut d'accessibilité (
aria-label,alt), passer aussi part: la plupart des composants le font déjà.
Le mécanisme suffit pour deux langues. Pour en ajouter une troisième, il faudrait remplacer la signature t(fr, en) par un système à clés dans tous les fichiers, côté web comme côté mobile. Les limites connues du projet sont regroupées dans Dette technique et limites.