Skip to main content

Spring Boot i18n : tutoriel de configuration de l'internationalisation

Configurez MessageSource, créez des fichiers de propriétés spécifiques à chaque locale, résolvez les locales et générez des modèles Thymeleaf multilingues — puis automatisez les traductions grâce à l'IA.

1

Ajouter les dépendances

Spring Boot Starter Web inclut d'emblée la configuration automatique de MessageSource. Ajoutez Thymeleaf pour des modèles i18n rendus côté serveur, ainsi que le starter de validation pour des messages d'erreur localisés.

Spring Boot configure automatiquement un bean MessageSource qui lit messages.properties depuis le classpath. Une configuration explicite n'est nécessaire que si vous souhaitez personnaliser le basename, l'encodage ou le comportement de mise en cache.
pom.xml
<!-- pom.xml — Spring Boot Starter Web includes MessageSource auto-config -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Thymeleaf for server-side rendered templates with i18n -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- Validation (for localized error messages) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2

Configurer MessageSource et LocaleResolver

Le MessageSource de Spring charge les traductions à partir de fichiers .properties selon la convention de basename : messages.properties (par défaut), messages_de.properties (allemand), messages_ja.properties (japonais). Configurez un LocaleResolver pour déterminer la locale à utiliser pour chaque requête.

Fichiers de traduction

messages.properties
# src/main/resources/messages.properties (default / English)
nav.home=Home
nav.about=About
nav.settings=Settings

greeting=Hello, {0}!
cart.itemCount={0,choice,0#No items|1#1 item|1<{0,number} items}

error.notFound=Page not found
error.serverError=Something went wrong. Please try again.

Configuration de MessageSource

I18nConfig.java
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class I18nConfig {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource source =
            new ReloadableResourceBundleMessageSource();
        source.setBasename("classpath:messages");
        source.setDefaultEncoding("UTF-8");
        source.setCacheSeconds(3600); // reload interval in dev
        return source;
    }

    // Wire MessageSource into Bean Validation
    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        bean.setValidationMessageSource(messageSource);
        return bean;
    }
}
Si les traductions renvoient le nom de la clé au lieu du texte traduit, la cause la plus fréquente est un basename incorrect. La valeur par défaut est 'messages', qui correspond à messages.properties sur le classpath. Si vos fichiers portent un nom différent ou se trouvent dans un sous-répertoire, définissez explicitement spring.messages.basename.

Résolution de la locale

Configurez la manière dont Spring détermine la locale active pour chaque requête. CookieLocaleResolver conserve le choix de l'utilisateur d'une session à l'autre. Le LocaleChangeInterceptor permet aux utilisateurs de changer de locale via un paramètre de requête tel que ?lang=de.

LocaleConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

import java.util.Locale;

@Configuration
public class LocaleConfig implements WebMvcConfigurer {

    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver resolver = new CookieLocaleResolver("lang");
        resolver.setDefaultLocale(Locale.ENGLISH);
        resolver.setCookieMaxAge(3600 * 24 * 365); // 1 year
        return resolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang"); // ?lang=de switches locale
        return interceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}
3

Utiliser les traductions dans le code

Accédez aux messages traduits dans les contrôleurs via l'injection de MessageSource, dans les modèles Thymeleaf avec la syntaxe #{...}, et dans les API REST en utilisant le paramètre Locale résolu automatiquement.

Contrôleur avec MessageSource

HomeController.java
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.Locale;

@Controller
public class HomeController {

    private final MessageSource messageSource;

    public HomeController(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @GetMapping("/")
    public String home(Model model, Locale locale) {
        // Spring injects the resolved Locale automatically
        String greeting = messageSource.getMessage(
            "greeting",
            new Object[]{"World"},
            locale
        );
        model.addAttribute("greeting", greeting);
        return "home";
    }
}

Modèles Thymeleaf

L'expression #{...} de Thymeleaf résout automatiquement les clés de message à partir de vos fichiers .properties. Transmettez des paramètres avec la syntaxe #{key(arg0, arg1)}. Le modèle utilise la locale résolue par votre LocaleResolver.

home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:text="#{nav.home}">Home</title>
</head>
<body>
    <!-- Simple message lookup -->
    <h1 th:text="#{greeting('World')}">Hello, World!</h1>

    <!-- Navigation with i18n -->
    <nav>
        <a href="/" th:text="#{nav.home}">Home</a>
        <a href="/about" th:text="#{nav.about}">About</a>
        <a href="/settings" th:text="#{nav.settings}">Settings</a>
    </nav>

    <!-- Parameterized messages -->
    <p th:text="#{cart.itemCount(3)}">3 items</p>

    <!-- Language switcher -->
    <div>
        <a th:href="@{/(lang=en)}">English</a>
        <a th:href="@{/(lang=de)}">Deutsch</a>
        <a th:href="@{/(lang=ja)}">日本語</a>
    </div>

    <!-- Conditional text based on locale -->
    <p th:if="${#locale.language == 'ja'}"
       th:text="#{greeting('ユーザー')}">
        こんにちは、ユーザーさん!
    </p>
</body>
</html>
Les expressions Thymeleaf telles que #{greeting('World')} transmettent des arguments à MessageFormat. Le texte statique à l'intérieur des balises HTML sert de solution de repli lors de la consultation du modèle sans Spring — ce qui est utile pour les designers travaillant directement sur les modèles.

Localisation de l'API REST

Pour les API REST, Spring résout automatiquement la Locale à partir de l'en-tête Accept-Language. Injectez-la comme paramètre de méthode et transmettez-la à MessageSource. Les clients changent de langue en envoyant des en-têtes Accept-Language différents.

ApiController.java
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Locale;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class ApiController {

    private final MessageSource messageSource;

    public ApiController(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @GetMapping("/greeting/{name}")
    public ResponseEntity<Map<String, String>> greeting(
            @PathVariable String name,
            Locale locale) {  // Resolved from Accept-Language header
        String msg = messageSource.getMessage(
            "greeting", new Object[]{name}, locale
        );
        return ResponseEntity.ok(Map.of("message", msg));
    }

    // curl -H "Accept-Language: de" localhost:8080/api/greeting/Max
    // → {"message": "Hallo, Max!"}
}
Les API REST utilisent généralement AcceptHeaderLocaleResolver (basé sur l'en-tête), tandis que les applications web utilisent CookieLocaleResolver (basé sur les cookies). Si vous servez les deux depuis la même application, envisagez un LocaleResolver personnalisé qui vérifie d'abord les cookies, puis se rabat sur l'en-tête Accept-Language.

Messages de Bean Validation

Spring résout automatiquement les messages de contraintes de validation à partir de votre MessageSource. Utilisez des espaces réservés entre accolades tels que {validation.name.required} dans vos annotations de contrainte, et définissez les traductions dans vos fichiers .properties.

Bean Validation i18n
import jakarta.validation.constraints.*;

public class CreateUserRequest {

    @NotBlank(message = "{validation.name.required}")
    @Size(min = 2, max = 50, message = "{validation.name.size}")
    private String name;

    @Email(message = "{validation.email.invalid}")
    private String email;
}

// In messages.properties:
// validation.name.required=Name is required
// validation.name.size=Name must be between {min} and {max} characters
// validation.email.invalid=Please enter a valid email address
//
// In messages_de.properties:
// validation.name.required=Name ist erforderlich
// validation.name.size=Name muss zwischen {min} und {max} Zeichen lang sein
// validation.email.invalid=Bitte geben Sie eine gültige E-Mail-Adresse ein
4

Gérer les pluriels et les variables

Spring utilise java.text.MessageFormat pour l'interpolation et les pluriels. Le motif ChoiceFormat gère les règles de pluriel de base, mais pour une prise en charge complète des pluriels ICU (les 6 formes de l'arabe, les 3 du russe), ajoutez la bibliothèque ICU4J.

MessageFormat Plurals
# MessageFormat plural syntax in messages.properties
# Uses java.text.ChoiceFormat — NOT ICU plural rules
cart.itemCount={0,choice,0#No items|1#1 item|1<{0,number} items}

# For more complex plurals, use ICU4J:
# 1. Add dependency: com.ibm.icu:icu4j
# 2. Use ICUMessageSource instead of ResourceBundleMessageSource
#
# Then you can write ICU-style plurals:
# cart.items={count, plural, one {# item} other {# items}}

# Variables with MessageFormat:
welcome.message=Welcome, {0}! You have {1,number} new {1,choice,1#notification|1<notifications}.
order.total=Order total: {0,number,currency}
event.date=Event date: {0,date,long}
ChoiceFormat n'est pas équivalent aux règles de pluriel ICU. Il utilise des plages numériques (0#, 1#, 1<) plutôt que des catégories CLDR (zero, one, two, few, many, other). Pour les langues aux règles de pluriel complexes comme l'arabe, le polonais ou le russe, ChoiceFormat est insuffisant : utilisez plutôt le MessageFormat d'ICU4J.

Automatiser les traductions

Une fois votre configuration i18n terminée, traduisez vos fichiers .properties grâce à l'IA. Dans votre IDE, demandez à votre assistant IA de traduire votre fichier source, ou utilisez le CLI i18n Agent dans votre pipeline CI/CD.

Terminal
# Translate your .properties files with AI
# In your IDE, ask your AI assistant:
> Translate src/main/resources/messages.properties to German, Japanese, and Spanish

✓ messages_de.properties created (1.2s)
✓ messages_ja.properties created (1.5s)
✓ messages_es.properties created (1.1s)

# Or use the CLI in CI/CD:
npx i18n-agent translate src/main/resources/messages.properties --lang de,ja,es
Traduisez de manière incrémentale : lorsque vous ajoutez de nouvelles clés à messages.properties, traduisez uniquement les nouvelles clés plutôt que de régénérer tous les fichiers de locale. Cela préserve les traductions relues par des humains dans les fichiers existants.

Automatiser la qualité des traductions

Détectez les clés manquantes et les espaces réservés cassés avant leur mise en production grâce à i18n-validate. Testez votre interface avec des pseudo-traductions grâce à i18n-pseudo avant l'arrivée des traductions réelles.

Zéro configuration avec spring-locale-chain

spring-locale-chain est un starter Spring Boot open source qui configure automatiquement LocaleResolver, LocaleChangeInterceptor et la validation des locales prises en charge en une seule dépendance. Définissez vos locales prises en charge dans application.yml et la bibliothèque se charge du reste.

pom.xml
<!-- Add spring-locale-chain for zero-config locale resolution -->
<dependency>
    <groupId>io.github.i18n-agent</groupId>
    <artifactId>spring-locale-chain</artifactId>
    <version>1.0.0</version>
</dependency>

Structure de fichiers recommandée

Project Structure
my-spring-app/
├── src/main/
│   ├── java/com/example/
│   │   ├── config/
│   │   │   ├── I18nConfig.java          # MessageSource bean
│   │   │   └── LocaleConfig.java        # LocaleResolver + interceptor
│   │   ├── controller/
│   │   │   └── HomeController.java      # Uses MessageSource
│   │   └── MyApplication.java
│   └── resources/
│       ├── messages.properties          # Default (English)
│       ├── messages_de.properties       # German
│       ├── messages_ja.properties       # Japanese
│       ├── messages_es.properties       # Spanish
│       ├── application.yml              # Spring config
│       └── templates/
│           └── home.html                # Thymeleaf with #{...}
├── pom.xml
└── build.gradle

Pièges courants

Les caractères non ASCII s'affichent de manière illisible

Par défaut, les fichiers .properties Java utilisent l'encodage ISO-8859-1, et non UTF-8. Des caractères comme les trémas (ü) ou les caractères CJK s'affichent de manière illisible. Solution : définissez spring.messages.encoding=UTF-8 dans application.yml, ou utilisez des échappements Unicode comme ü dans vos fichiers .properties. Le ReloadableResourceBundleMessageSource de Spring Boot utilise UTF-8 par défaut, mais pas ResourceBundleMessageSource.

ChoiceFormat échoue pour les pluriels non anglais

Le ChoiceFormat de Java ({0,choice,0#|1#|1<}) ne prend en charge que des plages numériques : il ne peut pas exprimer des catégories de pluriel CLDR comme 'few' ou 'many'. Des langues comme l'arabe (6 formes), le polonais (3 formes) et le russe (3 formes) nécessitent ICU4J pour une pluralisation correcte. Ne partez pas du principe que ChoiceFormat gère toutes les langues.

Les modifications de traduction ne sont pas prises en compte

Par défaut, ResourceBundleMessageSource met les bundles en cache indéfiniment. En développement, utilisez ReloadableResourceBundleMessageSource avec cacheSeconds=0 pour voir les changements sans redémarrer. En production, définissez une durée de cache raisonnable (par exemple 3600 secondes) pour équilibrer performance et rapidité de mise à jour.

Repli inattendu vers la locale de la JVM

Par défaut, Spring se rabat sur la locale par défaut de la JVM (Locale.getDefault()), et non sur votre fichier messages.properties. Définissez spring.messages.fallback-to-system-locale=false dans application.yml pour toujours utiliser le bundle par défaut. Sinon, un serveur dont la locale JVM est définie sur 'fr' affichera du français au lieu de l'anglais lorsqu'une clé est manquante dans la locale demandée.

Essayez i18n Agent maintenant

Déposez votre fichier de traduction ici

JSON, YAML, PO, XML, CSV, Markdown, Properties

ou cliquez pour parcourir

Langues cibles

Aucune inscription requiseEstimation instantanée

Repli de locale avec spring-locale-chain

Lorsqu'une clé de traduction est manquante dans une locale régionale comme pt-BR, Spring Boot passe directement à la locale par défaut au lieu de vérifier d'abord la locale parente pt.

Terminal
<!-- Maven -->
<dependency>
  <groupId>ai.i18nagent</groupId>
  <artifactId>spring-locale-chain</artifactId>
</dependency>
Configuration
# application.yml
locale-chain:
  fallbacks:
    pt-BR:
      - pt
      - en
    zh-Hant-HK:
      - zh-Hant
      - zh
      - en

Consultez notre guide de repli de locale pour découvrir la liste complète des frameworks pris en charge et les 75 chaînes intégrées. Learn more →

Questions fréquentes