Skip to content

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.

PERFSHOP_LANG=fr   # or en, es, de, it

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_LANG at startup via @Value
  • React frontend — uses VITE_LANG (an alias injected by env-inject.sh at runtime inside the Docker image)
  • chaos-admin / monitoringconfig.js exposes window.__CONFIG__.LANG, read by i18n.js
  • welcome — the entrypoint.sh script reads PERFSHOP_LANG and injects the dictionary at the head of welcome.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}.json
  • monitoring/public/i18n/{fr,en}.json
  • scripts-ui/public/i18n/{fr,en}.json
  • jmeter-ui/public/i18n/{fr,en}.json
  • welcome/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:

  1. Backend — create messages_<lang>.properties by copying messages_fr.properties and translating the values
  2. Frontend — create frontend/src/i18n/<lang>.json, import it in I18nContext.jsx and add it to the dictionary
  3. Ancillary tools — create public/i18n/<lang>.json for each tool (chaos-admin, monitoring, scripts-ui, jmeter-ui, welcome)
  4. Pedagogical enigmas — create i18n/enigmes/bacN/enigmes_<lang>.json for each level, plus i18n/logique/logique_<lang>.json
  5. MkDocs documentation — the mkdocs-static-i18n plugin is already configured for 5 languages; just create the .<lang>.md files 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.shwindow.__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 in en.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() or data-i18n depending 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