Internationalisation backend (Spring Boot)¶
Le backend Spring Boot utilise des fichiers Java Properties standards pour ses traductions. Cette approche est native à Spring et bénéficie d'outils IDE matures (coloration, validation, go-to-definition). L'intégration avec le reste du système se fait via un service dédié I18nService qui encapsule le chargement et la résolution des clés.
Sources
backend/src/main/resources/messages_fr.properties, messages_en.properties, backend/src/main/java/com/perfshop/service/I18nService.java
Fichiers¶
Deux fichiers à la racine de src/main/resources/ :
backend/src/main/resources/
├── messages_fr.properties (~35 KB, ~430 clés)
├── messages_en.properties (~34 KB, ~430 clés)
├── application.yml
├── db/migration-fr/
└── i18n/
├── enigmes/
└── logique/
Chaque fichier suit le format Java Properties classique :
# Messages d'erreur API
api.error.product_not_found=Produit introuvable
api.error.cart_empty=Votre panier est vide
api.error.payment_invalid=Les informations de paiement sont invalides
# Chaos métier
chaos.business.a1.description=TVA 19,6% au lieu de 20%
chaos.business.a1.log_detail=Application d'une TVA à 19,6% sur la commande {0}
# Licences
license.error.no_license=Aucune licence active sur cette instance PerfShop
license.error.feature_denied=Licence requise pour accéder à {0}
license.status.none_label=Aucune licence
Les valeurs peuvent contenir des placeholders numérotés au format {0}, {1}, etc. Ce sont les positions d'arguments de MessageFormat standard Java.
Conventions de nommage des clés¶
Les clés suivent une hiérarchie par points, en anglais, et suivent un schéma cohérent :
| Préfixe | Usage |
|---|---|
api.error.* |
Messages d'erreur des endpoints REST |
api.success.* |
Messages de succès |
chaos.business.aN.* |
Descriptions et logs des anomalies métier A1 à A16 |
chaos.security.sN.* |
Descriptions et logs des failles OWASP S1 à S12 |
chaos.functional.fN.* |
Descriptions des exceptions F1 à F4 |
chaos.scripting.* |
Tokens Chaos Scripting |
chaos.infra.* |
Chaos infrastructure (CPU, mémoire, GC…) |
chaos.level* |
Labels des niveaux (Junior, Confirmé, Expert, Master) |
scenario.n1-01.* |
Scénarios météo (name, description) |
license.error.* |
Erreurs de validation de licence |
license.status.* |
Labels du statut de licence |
admin.service.* |
Messages de AdminUserService (validation, exceptions) |
order.status.* |
Labels des statuts de commande |
logique.* |
Catalogue des 25 questions logique pédagogique |
Cette normalisation facilite les recherches : grep -r "chaos.business" messages_fr.properties trouve immédiatement toutes les clés liées au Chaos Métier.
Service I18nService¶
I18nService est un bean Spring qui charge les dictionnaires au démarrage et expose des méthodes de résolution.
Chargement¶
Au démarrage, I18nService lit la variable PERFSHOP_LANG (via @Value ou application.yml), détermine le nom du fichier messages_<lang>.properties correspondant et le charge en mémoire. Le français est chargé en premier comme dictionnaire de fallback :
@Service
public class I18nService {
private final Properties current; // langue active
private final Properties fallback; // toujours fr
public I18nService(@Value("${perfshop.lang:fr}") String lang) {
this.fallback = loadProperties("messages_fr.properties");
this.current = lang.equals("fr")
? this.fallback
: loadProperties("messages_" + lang + ".properties");
}
// ...
}
Si la langue demandée n'a pas de fichier correspondant, I18nService logue un warning et utilise le dictionnaire français seul.
Méthodes exposées¶
| Méthode | Rôle |
|---|---|
t(String key) |
Traduit une clé sans argument, retourne la clé elle-même si absente |
t(String key, Object... args) |
Traduit avec substitution MessageFormat des {0}, {1}... |
Exemples d'utilisation depuis un contrôleur ou un service :
return ResponseEntity.badRequest()
.body(Map.of("error", i18n.t("api.error.cart_empty")));
return ResponseEntity.status(422)
.body(Map.of("error", i18n.t("license.error.expired", info.expiresAt)));
throw new IllegalArgumentException(
i18n.t("admin.service.email_exists", email));
Le résultat est une chaîne déjà traduite dans la langue active, prête à être renvoyée au client.
Lifecycle¶
sequenceDiagram
autonumber
participant Env as .env
participant Spring as Spring Boot
participant I18n as I18nService
participant Fs as Filesystem
Env->>Spring: PERFSHOP_LANG=fr
Spring->>I18n: @PostConstruct init
I18n->>Fs: load messages_fr.properties (fallback)
Fs-->>I18n: Properties fr (430 clés)
I18n->>Fs: load messages_fr.properties (current)
Note over I18n: Même fichier<br/>fallback = current
I18n-->>Spring: I18nService prêt
Note over Spring: Appels pendant l'exécution
Spring->>I18n: t("api.error.cart_empty")
I18n-->>Spring: "Votre panier est vide"
En mode anglais :
sequenceDiagram
autonumber
participant Env as .env
participant Spring as Spring Boot
participant I18n as I18nService
participant Fs as Filesystem
Env->>Spring: PERFSHOP_LANG=en
Spring->>I18n: @PostConstruct init
I18n->>Fs: load messages_fr.properties (fallback)
Fs-->>I18n: Properties fr (430 clés)
I18n->>Fs: load messages_en.properties (current)
Fs-->>I18n: Properties en (430 clés)
I18n-->>Spring: I18nService prêt
Spring->>I18n: t("api.error.cart_empty")
I18n-->>Spring: "Your cart is empty"
Spring->>I18n: t("new.key.not.yet.translated")
Note over I18n: Clé absente de EN
I18n-->>Spring: (fallback FR) "Nouvelle clé..."
Intégration avec les exceptions métier¶
Plusieurs exceptions personnalisées (CartItemNotFoundException, CartAccessDeniedException, etc.) reçoivent un I18nService et construisent leur message au moment de la levée :
public class CartItemNotFoundException extends RuntimeException {
public CartItemNotFoundException(I18nService i18n, Long itemId) {
super(i18n.t("api.error.cart_item_not_found", itemId));
}
}
Cette approche garantit qu'un message d'exception est toujours dans la langue active au moment où il est levé. L'exception peut ensuite remonter sans transformation jusqu'au @ExceptionHandler global qui le renvoie au client.
Symétrie FR/EN¶
Les deux fichiers doivent contenir exactement les mêmes clés. Au moment de cette rédaction, la symétrie est de 430 clés en FR / 430 clés en EN.
Pour vérifier la symétrie, un one-liner shell suffit :
diff <(grep -Eo '^[^#][^=]+' messages_fr.properties | sort) \
<(grep -Eo '^[^#][^=]+' messages_en.properties | sort)
Ce diff doit être vide. Toute sortie signale une dérive à corriger.
Utilisation des placeholders¶
Les placeholders suivent la syntaxe MessageFormat standard :
api.error.product_out_of_stock=Le produit {0} n'est plus en stock
license.error.expired=La licence a expiré le {0}
admin.service.account_not_found=Compte admin introuvable pour l'id {0}
Un point à surveiller : MessageFormat traite les {...} spécialement si le nombre est suivi d'un type ({0,number}, {1,date}). Pour un simple remplacement textuel, laisser {0} tel quel suffit.
Les apostrophes françaises dans les valeurs sont une source classique de bugs MessageFormat — elles sont interprétées comme délimiteurs de quote et doivent être doublées ('') dans la valeur properties si la chaîne contient aussi des placeholders. Sans placeholders, pas de problème.
Noms de clé en anglais, valeurs dans la langue cible¶
Convention : les clés sont toujours en anglais, quel que soit le fichier. Seules les valeurs sont traduites :
# messages_fr.properties
api.error.cart_empty=Votre panier est vide
# messages_en.properties
api.error.cart_empty=Your cart is empty
# messages_es.properties (future)
api.error.cart_empty=Tu carrito está vacío
Cette convention facilite la maintenance et permet à un développeur non-francophone de comprendre la structure du fichier sans lire les valeurs.
Ajouter une clé¶
- Ajouter la clé en français dans
messages_fr.properties - Ajouter la même clé en anglais dans
messages_en.properties - Rebuild le backend (
mvn clean package) - Utiliser la clé dans le code Java via
i18n.t("ma.nouvelle.cle")
Aucun autre fichier à toucher. Spring Boot recharge automatiquement les properties au redémarrage.
Ajouter une nouvelle langue¶
- Copier
messages_fr.propertiesversmessages_<lang>.properties - Traduire toutes les valeurs (laisser les clés telles quelles)
- Déployer avec
PERFSHOP_LANG=<lang>
Aucune modification de code requise. I18nService charge automatiquement le nouveau fichier via sa convention de nommage.
Voir aussi¶
- Vue d'ensemble i18n
- Internationalisation frontend
- Internationalisation des énigmes — pour le contenu pédagogique