feat: (i18n) French localization (en/fr) for the desktop UI#591
Draft
Davidnet wants to merge 26 commits into
Draft
feat: (i18n) French localization (en/fr) for the desktop UI#591Davidnet wants to merge 26 commits into
Davidnet wants to merge 26 commits into
Conversation
…gs (en/fr) Scoped to English (base) + French per the team decision on epic #580 (es/ja out of scope). #571 — i18next infrastructure: - Add react-i18next, i18next, i18next-browser-languagedetector - src/i18n init: en/fr, load: languageOnly (fr-FR→fr), fallback en, localStorage→navigator detection, Suspense disabled - Namespaced locale JSON (common/settings/dashboard/onboarding/activity/ mappings/about); side-effect import wired into index.js #572 — core UI extraction (common namespace): - privacy-proxy-ui.tsx (playground, tabs, status bar, footer, banners) - ErrorBoundary.tsx via withTranslation HOC (class component) - Trans for the footer link and model-health Settings link; count pluralization and provider/port interpolation #573 — settings extraction (settings namespace): - SettingsView, ProvidersSection, PIISection, AdvancedSection, CertificatesSection - Brand names kept; only "Custom Provider" localized - 26 PII entity labels with capitalize fallback for unknown labels French files are machine-drafted, pending native-speaker review. No visible change for English users.
Wire DashboardView and Sidebar to the dashboard namespace: KPI cards, range selector, area chart, donut, provider bars, highlights, recent intercept feed, relative-time labels, and the sidebar nav/server status. Number formatting via fmt() is preserved by passing pre-formatted values as plain interpolation vars rather than i18next count.
Wire ActivityView and MappingsView to the activity/mappings namespaces:
page headers, clear-confirmation dialogs, JSON toggle, table headers,
row-level direction/message/blocked/report controls, sort aria-labels,
per-row delete controls, load-more, and footer summaries.
Uses i18next pluralization (_one/_other with {{count}}) for entry
counts, message counts, confirmation bodies, and footer summaries.
Thread the translator into logFormatters' getDirectionLabel and
formatMessage so shared log-display strings localize too.
Fill the onboarding and about namespaces and add a new modals namespace (registered in i18n/index.ts) for the CA-cert and misclassification dialogs. - WelcomeModal: role picker, explainer, and the three privacy promises. - AboutView: header, hero, resource links, no-support notice, and a <Trans> copyright line with the 575 Lab link and year interpolation. - MisclassificationModal: title, entity/confidence details, feedback form, and submit. - CACertSetupModal: intro, tabs, command copy controls, and the full system/Firefox/Chrome instruction lists rendered via <Trans> with a shared set of inline-markup components (bold/code/accent/amber). Shared Close aria-labels reuse common:actions.close. Certificate paths and shell commands stay literal inside the translated strings.
Add a LanguageSection to Settings (shown everywhere, since language is a universal UI preference) that switches the interface language via i18next.changeLanguage over the supported locales (en/fr). Persistence needs no extra code: the i18next language detector is configured with caches: ["localStorage"], so the choice is written through on change and restored on next launch. Region subtags like fr-FR normalize to the fr base via load: "languageOnly". Syncing the selection to the Electron native menu is handled separately (#578).
The application and tray menus are built in the Electron main process,
which has no access to react-i18next. Add a self-contained main-process
string table (menu-i18n.js) with en/fr labels, a normalize/select/lookup
API, and {{name}} interpolation, then route every hardcoded menu label
through it.
The UI language is persisted as a config field (get-/set-language IPC):
- At startup the menu is built from the last persisted language.
- On set-language the config is saved and the app/tray menus rebuilt.
- The renderer is the source of truth: i18n pushes its resolved base
language to the main process on init and on every languageChanged,
guarded so the web build (no electronAPI) is unaffected.
Add tooling to keep translations in sync and guide future extraction:
- scripts/check-i18n-parity.js (npm run i18n:check) — a plural-aware
en<->fr parity check wired into the frontend-lint CI job. Per namespace
it verifies base-key parity (plural suffixes normalized), plural
completeness against each locale's CLDR categories, matching
{{placeholder}} sets, and matching/balanced <Trans> tags. It caught
French plural keys missing the _many category — fixed here across
common/activity/mappings (i18next does not fall back many->other, so a
missing _many would render the raw key at millions-scale counts).
- eslint-plugin-i18next: no-literal-string in jsx-text-only mode as a
warning over src/**/*.{jsx,tsx}, surfacing un-extracted UI text without
gating CI (technical/brand literals in attributes are out of scope).
- docs/10-internationalization.md documenting the setup, usage, the
French many-plural gotcha, and the tooling; linked from docs/README.
French drafts remain machine-translated pending native review.
Contributor
|
Warning This PR touches 3+ distinct areas of the codebase. Consider splitting into smaller, focused PRs — each covering a single semantic type. Categories found: docs:
code:
chore:
ci:
|
The backend sends the delta window as a raw '<number><unit>' string (e.g. '7d') that was interpolated verbatim. Localize the unit suffix so French renders '7j' (jours) instead of '7d'; English is unchanged and unknown units fall through untouched.
Treat PII as feminine plural throughout: the KPI card now reads "PII protégées" (was "Éléments de PII protégés", masculine via éléments), parallel to "PII masquées au fil du temps".
French wording:
- 'renseignements personnels' for the PII card and chart titles
- 'Types de données masquées', 'données détectées' (donut)
- 'Requêtes par fournisseur', 'Score de détection' (avoid 'Conf.')
- 'req./min', 'masquée' (agrees with requête)
- latency caption -> 'La confidentialité, sans ralentir vos requêtes.'
- entity labels: Courriel, Nom (Surname)
- delta window renders '7 j' (spaced unit)
Locale-aware number formatting in DashboardView: fmt() now groups per
active language ('1 051 ms' fr / '1,051 ms' en) and detection confidence
uses a localized decimal ('79,0 % en moy.' / '79.0% avg').
Unify the personal-information term across the French UI: replace both the "PII" acronym and "données personnelles" with "renseignements personnels" (masculine plural), flipping adjective/pronoun agreement accordingly (détectées->détectés, elles->ils, fausse->faux, etc.). Covers common (tagline, playground plurals, placeholder), settings, onboarding, activity, mappings, modals, about, and the dashboard count badge. The About description keeps the "(PII)" acronym note; the donut's generic "données" labels are left as previously chosen.
- Donut title -> 'Types de renseignements masqués' for terminology
consistency.
- The French center label ('renseignements détectés') overflows the
small donut, so it is set to an empty string; the Donut now renders
the center label only when non-empty (English keeps 'entities'), a
data-driven hide that works for any locale.
Catch the capitalized playground labels missed by the earlier (case-sensitive) sweep: - piiExposed -> 'Renseignements personnels exposés' - piiProtected -> 'Renseignements personnels protégés' - personalInfoRestored -> 'Renseignements personnels restaurés' - personalInfoRemoved -> 'Renseignements personnels masqués' (the info is masked/replaced, not deleted, so 'masqués' not 'supprimées') - regex help: 'type de renseignement personnel détecté'
- Localize Playground average-confidence formatting (fr '90,9 %'). - Tabs: 'Envoyée à ...' (agrees with la requête); 'Réponse de ...' / 'Réponse du LLM' instead of 'Renvoyé par ...'. - 'renseignement personnel fictif reçu' instead of 'faux ... reçu'. - Report button/title: 'Signaler une erreur de classification'.
- subtitle emphasizes the fake replacement value - columns: Type de renseignement / Valeur originale / Remplacement / Première détection
- subtitle: 'Configurez les fournisseurs, la détection...' - providers subtitle: 'identifiants de modèles' (plural) - keychain note: 'stockées de façon sécurisée à l'aide du trousseau du système' - save button: 'Enregistrer les paramètres des fournisseurs'
- 'certificat d'autorité (de certification)' -> 'certificat racine' - model status 'Fonctionnel' -> 'Opérationnel'
- 'certificat CA' -> 'certificat racine' across reveal buttons, the CA setup modal title, and the import step. - Certificates subtitle: 'Faites confiance...' -> 'Installez le certificat racine de Kiji...'.
- 'pour intercepter le trafic HTTPS' -> 'pour permettre l'interception du trafic HTTPS' - 'à l'échelle du système ou par navigateur' -> 'au niveau du système ou dans le navigateur' - revealHelp: 'Ouvre le dossier...' (describes the action, not an order)
Member
Author
Collaborator
I think it works for the Electron app, but what happens if the user uses the Linux server? User A sets French (stored locally as French), next user sees English? |
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.




Summary
Adds internationalization to the desktop/renderer UI with react-i18next and ships French (
fr) alongside the English (en) base, scoped to French only per epic #580.Infrastructure & extraction (#571–#576)
src/i18n/index.tswith 8 namespaces (common,settings,dashboard,activity,mappings,about,onboarding,modals).en/frJSON: core UI & Playground, Settings (all sections), Dashboard & sidebar, Activity & Mappings, and the onboarding/about/modal dialogs.ErrorBoundaryvia thewithTranslationHOC, embedded markup (links, bold, code) via<Trans>, and i18next pluralization for counts.Language selection & Electron menu (#577–#578)
LanguageSection), shown in both web and desktop builds; persistence is handled by the i18next detector'slocalStoragecache.menu-i18n.jsstring table. The renderer pushes its resolved language over a newset-languageIPC channel; the main process persists it and rebuilds the menus, seeding from config at startup.Tooling, CI & docs (#579)
scripts/check-i18n-parity.js(npm run i18n:check, wired into CI): a plural-aware en↔fr parity check — base-key parity, plural completeness against each locale's CLDR categories, matching{{placeholder}}sets, and balanced/matching<Trans>tags. It caught French plural keys missing the_manycategory (i18next does not fall backmany → other), fixed here.eslint-plugin-i18nextno-literal-stringinjsx-text-onlymode as an advisory warning over the renderer.docs/10-internationalization.md(linked from the docs index).Notes