Pedagogical enigma internationalization¶
PerfShop's pedagogical journey is built around 100 enigmas spread across five levels (BAC1 to BAC5, 20 enigmas per level) and a pool of 25 logic questions drawn at random during some journeys. All of this content is fully internationalized through dedicated JSON files.
Sources
backend/src/main/resources/i18n/enigmes/bac1/ to bac5/, backend/src/main/resources/i18n/logique/
Directory structure¶
backend/src/main/resources/i18n/
├── enigmes/
│ ├── bac1/
│ │ ├── enigmes_fr.json ← complete (~5.9 KB)
│ │ ├── enigmes_en.json ← complete (~5.4 KB)
│ │ ├── enigmes_de.json ← placeholder
│ │ ├── enigmes_es.json ← placeholder
│ │ ├── enigmes_it.json ← placeholder
│ │ ├── enigmes_pt.json ← placeholder
│ │ └── enigmes_zh.json ← placeholder
│ ├── bac2/
│ ├── bac3/
│ ├── bac4/
│ └── bac5/
└── logique/
├── logique_fr.json ← 25 questions
└── logique_en.json ← 25 questions
Each BAC1-BAC5 level has seven files — one per supported language. At the time of writing, only French and English are complete. The five others (German, Spanish, Italian, Portuguese, Chinese) are present as structural placeholders, ready to receive their translations.
The logic pool is organized differently: only two files (FR and EN), with no level structure, because logic-question drawing is independent of the journey level.
Enigma file format¶
Each enigmes_<lang>.json file is a flat JSON object whose keys follow the convention BAC<level>-<step>.<property>:
{
"BAC1-1.text": "📡 SIGNAL RECEIVED. Welcome, agent.\n\nThe catalog contains 994 products.\nYour mission: find the product whose price matches the sum of the digits of this number.\n\nSet both the Min Price AND Max Price filters to this amount in euros.\nEnter this price.",
"BAC1-1.hint": "Add the three digits: 9 + 9 + 4",
"BAC1-1.culturalNote": "October 22, 1991 — Linus Torvalds releases Linux 0.0.1 on the internet. He is 21 and warns that it won't amount to much. Today, 96% of web servers run on Linux.",
"BAC1-2.text": "📡 Well done. You found the 22€ USB-C cable.\n...",
"BAC1-2.hint": "After 5 days: 60 − 35 = 25 (still above). After 6 days: 60 − 42 = ?"
}
The three properties per enigma¶
| Key | Role | Required |
|---|---|---|
BAC<N>-<step>.text |
Statement displayed to the student in the pedagogical overlay | ✅ yes |
BAC<N>-<step>.hint |
Hint revealed on demand (if hints are enabled) | ✅ yes |
BAC<N>-<step>.culturalNote |
Historical, scientific or cultural anecdote displayed after successful validation | ⭕ optional, present on most enigmas |
The answers are not in these files. They are hard-coded in the PedagogiqueEnigmeBacN Java classes and are universal: a number, a fixed string, an exact price. They never change from one language to another. This separation guarantees that a student following BAC3 in English and another in French solve exactly the same mathematical puzzle, with only the display language differing.
Placeholders and substitutions¶
Unlike the Spring Boot backend which uses MessageFormat with {0}, {1}, the enigma format is static: no on-the-fly substitution. Displayed values (product count, exact price, agent code) are either hard-written in the statement (like 994 products or 22€), or built by the React component that renders the overlay.
This simplicity is intentional: pedagogical content changes rarely and dynamic placeholders would needlessly complicate translation.
Loading by PedagogiqueEnigme¶
The PedagogiqueEnigme service (Java side) loads translations at startup via a loadTranslations(level) method that:
- Determines the file to read:
i18n/enigmes/bac<level>/enigmes_<PERFSHOP_LANG>.json - If the file does not exist or is empty (case of the DE/ES/IT/PT/ZH placeholders), falls back to
enigmes_fr.json - Parses the JSON with a minimal parser (zero additional Jackson dependency)
- Stores the
key → valuemap in memory - Exposes a
t(level, key)method that resolvesBAC<level>-<step>.<property>
The fallback to French is the reason the DE/ES/IT/PT/ZH files can remain as empty placeholders: the user experience stays functional in every language, even those not yet translated. Only the display is in French instead of the requested language.
Per-level architecture¶
Each BAC1 to BAC5 level has its own dedicated Java class:
backend/src/main/java/com/perfshop/pedagogique/
├── PedagogiqueEnigme.java ← Common superclass
├── PedagogiqueEnigmeBac1.java ← BAC1 logic + answers
├── PedagogiqueEnigmeBac2.java
├── PedagogiqueEnigmeBac3.java
├── PedagogiqueEnigmeBac4.java
└── PedagogiqueEnigmeBac5.java
The PedagogiqueEnigme superclass exposes JSON loading and key resolution. Each subclass provides:
- The list of journey steps (20 per level)
- The validation function for each step (mathematical computation, string comparison, expected hash)
- The progression rule and any conditional jumps
For the engine details and the 20 steps per level, see BAC1 to BAC5 levels and Enigma system.
File volumetry¶
The text volume varies with enigma complexity:
| Level | FR size | EN size | Key count |
|---|---|---|---|
| BAC1 | ~5.9 KB | ~5.4 KB | ~50 |
| BAC2 | ~6.5 KB | ~6.0 KB | ~55 |
| BAC3 | ~8.0 KB | ~7.5 KB | ~60 |
| BAC4 | ~10.0 KB | ~9.2 KB | ~65 |
| BAC5 | ~12.1 KB | ~11.2 KB | ~75 |
The BAC5 level is the longest because it combines detailed statements (cryptography, multi-step computations) with richer cultural notes. 20 steps × 5 levels × 2 complete languages represent around 100 KB of carefully written pedagogical content.
Logic pool format¶
The logique/logique_fr.json file is a JSON array of 25 objects:
[
{"text": "Sequence: 2, 6, 18, 54, ?", "hint": "Each term is multiplied by 3"},
{"text": "If A=1, B=2, ..., Z=26: what is M + A + T + H?", "hint": "M=13, A=1, T=20, H=8"},
{"text": "How many diagonals does a hexagon (6-sided polygon) have?", "hint": "Formula: n×(n−3) / 2 with n=6"}
]
Two properties per question:
text— question statementhint— optional hint
The answers are stored in a Java array parallel to the JSON pool, indexed in the same order. The 25 questions' indices are readable on the backend side; the drawing uses an LCG (linear congruential generator) seeded by the session's X-Student-Token to draw 5 reproducible questions per journey.
The 25 questions are deliberately at a general mathematics level — additions, sequences, elementary geometry, factorials, prime numbers. They do not depend on computer science knowledge and are solvable by any post-high-school student. Random (but deterministic per session) drawing prevents rote memorization.
Loading logic questions¶
The ThemeLogique service loads the FR/EN pool at startup and exposes:
The algorithm uses an LCG seeded by the token hash to draw count distinct indices in [0, 25), then returns the corresponding questions in the active language. The drawn indices are stored in the database in the logique_question_indices column of pedagogique_sessions (see Database schema) to guarantee reproducibility between the server and the client.
Previously, the LCG algorithm was duplicated on the frontend side — this approach was dropped because it generated desynchronizations when the token changed. From now on, only the backend draws the questions, and the frontend retrieves the final list via GET /pedagogique/logique/questions.
FR/EN symmetry¶
At the time of writing, the FR and EN files are in perfect symmetry for each BAC1-BAC5 level and for the logic pool. A "3 amigos" audit validated this parity before writing this documentation.
A simple script can verify symmetry:
jq 'keys | sort' enigmes_fr.json > /tmp/fr.keys
jq 'keys | sort' enigmes_en.json > /tmp/en.keys
diff /tmp/fr.keys /tmp/en.keys
For the DE/ES/IT/PT/ZH placeholder files, minimal content like {} is enough — the FR fallback applies automatically.
Adding a new language¶
The process is the following for each level:
- Copy
enigmes_fr.jsontoenigmes_<lang>.jsonin the level folder - Translate only the values (leave the
BAC<N>-<step>.textetc. keys as-is) - Repeat the same operation for every BAC1-BAC5 level
- Copy and translate
logique/logique_fr.jsontologique/logique_<lang>.json - Deploy with
PERFSHOP_LANG=<lang>
No Java code modification is necessary. The FR fallback will handle any keys that happen to be missing during the translation period.
Points of caution¶
- The numbers in the statements are untouchable. If an enigma says "994 products" because the catalog contains exactly 994 products, this value must be preserved as-is in all languages. Same for prices (
22€,64€,255€,34.99€) that correspond to the pedagogical products created by the V10 migration. - Product names are sometimes in French. For example "Câble USB-C basique 1m" appears in the BAC1-2 statement. In English, the translation can use the English name ("Basic USB-C Cable 1m") but that name must match the real product name in the database — or accept that the English statement quotes the French name in quotes.
- Agent credentials are universal. The passwords
Cable22Go,Code64Exp6,Docker8080Get,Lorem255FF42,Rsa3Xor51Pi14are fixed strings that must never be translated. - Cultural notes can be replaced. An anecdote about Linus Torvalds for the French-speaking audience can be replaced by a more relevant anecdote for a German or Spanish-speaking audience. This is not a simple translation — it is a cultural adaptation.