Internationalization overview¶
PerfShop is designed to be multilingual from the ground up. Two languages are currently shipped (French and English) and the MkDocs documentation is configured for five languages (French, English, Spanish, German, Italian). Adding a new language does not require any code change — this is a direct consequence of the dictionary design.
This page describes the overall strategy. The following pages detail each subsystem: Spring backend, React frontend, pedagogical enigmas and ancillary tools.
Supported languages¶
| Language | Code | State | Dictionaries |
|---|---|---|---|
| French | fr |
✅ Source language, complete | Backend + Frontend + chaos-admin + scripts-ui + jmeter-ui + monitoring + welcome + enigmas BAC1-BAC5 + logic |
| English | en |
✅ Complete translation | Backend + Frontend + chaos-admin + scripts-ui + jmeter-ui + monitoring + welcome + enigmas BAC1-BAC5 + logic |
| Spanish | es |
⏳ Planned | — |
| German | de |
⏳ Planned | — |
| Italian | it |
⏳ Planned | — |
The MkDocs documentation is already configured for the five languages via the mkdocs-static-i18n plugin. FR/EN translations have priority; the three other languages will be added progressively.
French is the source language — all texts are written in French first, then translated. In case of inconsistency or missing key, the French dictionaries serve as fallback.
Single variable: PERFSHOP_LANG¶
PerfShop does not use the HTTP Accept-Language header. All interfaces honor a single shared environment variable: PERFSHOP_LANG.
Why not Accept-Language?¶
Several concrete reasons:
- Pedagogical consistency — in a classroom, all students must see the same interface in the same language so that the instructor's guidance is understandable by everyone. If each student had their browser language, a French-speaking instructor would end up pointing at a button that displays differently on some workstations.
- Test reproducibility — a JMeter or Selenium script written against a French label must keep working regardless of the language of the browser executing the request. By freezing the language at deployment time, any drift is eliminated.
- Code simplicity — a single variable, resolved at startup, propagated to all components. No content negotiation, no detection middleware, no runtime header fallback.
- Log replay — if the backend logs error messages, they are always in the same language. An error log in French on one workstation and in English on another would be a source of confusion.
Propagation¶
The variable is defined at the .env level of the stack and propagated to each service via Docker Compose:
flowchart TD
Env[.env<br/>PERFSHOP_LANG=fr]
Env --> BA[perfshop-app<br/>I18nService]
Env --> FE[perfshop-frontend<br/>VITE_LANG injected]
Env --> CA[perfshop-chaos-admin<br/>window.__CONFIG__.LANG]
Env --> MO[perfshop-monitoring<br/>window.__CONFIG__.LANG]
Env --> WE[perfshop-welcome<br/>entrypoint.sh → i18n/<lang>.json]
Env --> SC[perfshop-scripts-ui<br/>public/i18n/<lang>.json]
Env --> JM[perfshop-jmeter-ui<br/>public/i18n/<lang>.json]
The propagation mechanism differs by component:
- Spring Boot backend — reads
PERFSHOP_LANGat startup via@Value - React frontend — uses
VITE_LANG(an alias injected byenv-inject.shat runtime inside the Docker image) - chaos-admin / monitoring —
config.jsexposeswindow.__CONFIG__.LANG, read byi18n.js - welcome — the
entrypoint.shscript readsPERFSHOP_LANGand injects the dictionary at the head ofwelcome.js - scripts-ui / jmeter-ui — same pattern as chaos-admin
All these paths converge to a single effective value. A deployment with PERFSHOP_LANG=en will display the whole stack in English, without exception.
Four families of dictionaries¶
PerfShop maintains four families of dictionaries:
1. Spring backend — messages_*.properties¶
Location: backend/src/main/resources/messages_fr.properties and messages_en.properties.
Around 430 keys. Java Properties key=value format. Used by I18nService for REST error messages, user-facing logs, labels returned in JSON payloads. See Backend internationalization.
2. React frontend — src/i18n/*.json¶
Location: frontend/src/i18n/fr.json and en.json.
Around 400 keys. Flat JSON format. Consumed by I18nContext.jsx and the useT() hook. See Frontend internationalization.
3. Ancillary tools — public/i18n/*.json¶
Locations:
chaos-admin/public/i18n/{fr,en}.jsonmonitoring/public/i18n/{fr,en}.jsonscripts-ui/public/i18n/{fr,en}.jsonjmeter-ui/public/i18n/{fr,en}.jsonwelcome/i18n/{fr,en}.json
Flat JSON format, loaded asynchronously by the common i18n.js loader or by entrypoint.sh for welcome. See Ancillary tools internationalization.
4. Pedagogical content — i18n/enigmes/ and i18n/logique/¶
Location: backend/src/main/resources/i18n/enigmes/bac1/ to bac5/, and backend/src/main/resources/i18n/logique/.
One JSON file per level and per language. Contains the enigma statements, hints, success / failure messages. The answers are universal (numeric or fixed strings) and do not change between languages. See Enigma internationalization.
FR/EN symmetry¶
The absolute rule: if a key exists in French, it must exist in English (and vice versa). This symmetry is checked manually on every update and enforced through "3 amigos" audits on every significant commit.
At the time of this writing, the FR/EN symmetry is total across the four families of dictionaries.
Missing keys trigger fallbacks to French (for the backend and frontend) or display the raw key (for the ancillary tools and welcome). No production message should ever display a raw key to the end user.
Adding a new language¶
The process is the same for each family of dictionaries — and it happens without any code change:
- Backend — create
messages_<lang>.propertiesby copyingmessages_fr.propertiesand translating the values - Frontend — create
frontend/src/i18n/<lang>.json, import it inI18nContext.jsxand add it to the dictionary - Ancillary tools — create
public/i18n/<lang>.jsonfor each tool (chaos-admin, monitoring, scripts-ui, jmeter-ui, welcome) - Pedagogical enigmas — create
i18n/enigmes/bacN/enigmes_<lang>.jsonfor each level, plusi18n/logique/logique_<lang>.json - MkDocs documentation — the
mkdocs-static-i18nplugin is already configured for 5 languages; just create the.<lang>.mdfiles next to the French files
And that is it — deploying with PERFSHOP_LANG=<lang> is enough. No image rebuild, no restart, no change to Java or JavaScript code.
Summary table¶
| Component | i18n files | Approximate key count | Loader |
|---|---|---|---|
| Spring backend | messages_{fr,en}.properties |
~430 | I18nService |
| React frontend | src/i18n/{fr,en}.json |
~400 | I18nContext.jsx + useT() hook |
| chaos-admin | public/i18n/{fr,en}.json |
~400 | js/i18n.js (vanilla async) |
| monitoring | public/i18n/{fr,en}.json |
~150 | same |
| scripts-ui | public/i18n/{fr,en}.json |
~200 | same |
| jmeter-ui | public/i18n/{fr,en}.json |
~180 | same |
| welcome | i18n/{fr,en}.json |
~30 | entrypoint.sh → window.__I18N__ |
| Enigmas BAC1 | i18n/enigmes/bac1/enigmes_{fr,en}.json |
~200 keys per language | PedagogiqueEnigme.loadTranslations() |
| Enigmas BAC2 | i18n/enigmes/bac2/enigmes_{fr,en}.json |
~200 | same |
| Enigmas BAC3 | i18n/enigmes/bac3/enigmes_{fr,en}.json |
~200 | same |
| Enigmas BAC4 | i18n/enigmes/bac4/enigmes_{fr,en}.json |
~200 | same |
| Enigmas BAC5 | i18n/enigmes/bac5/enigmes_{fr,en}.json |
~200 | same |
| Logic | i18n/logique/logique_{fr,en}.json |
25 questions per language | ThemeLogique |
Total volume exceeds 2,500 keys per language. This represents a significant investment but makes PerfShop actually usable by non-French-speaking training programs.
Best practices¶
- Always translate in pairs — when you add a key in
fr.json, add it simultaneously inen.json(and vice versa) - Key names in English — keys are always in English:
admin.login.success,error.payment.failed. Values are in the target language. This convention makes searching and maintenance easier when collaborating with non-French-speaking developers. - Avoid hard-coded strings in templates — always go through
t(),_t()ordata-i18ndepending on the context - Symmetry audit before every release — a simple script that compares the key sets of FR and EN is enough to detect any drift