Contactez-nous

Internationalisation (i18n) et localisation (L10n)

Apprenez à rendre vos applications Spring Boot multilingues (i18n) et adaptées aux formats régionaux (L10n) en utilisant MessageSource et LocaleResolver.

Introduction : Parler la langue de vos utilisateurs

Pour toucher un public mondial, une application web doit pouvoir s'adapter aux différentes langues et conventions régionales de ses utilisateurs. C'est l'objectif de l'internationalisation (souvent abrégée en i18n, car il y a 18 lettres entre le 'i' initial et le 'n' final) et de la localisation (L10n).

L'internationalisation est le processus de conception d'une application de manière à ce qu'elle puisse être adaptée à différentes langues et régions sans nécessiter de modifications techniques majeures. Cela implique principalement d'externaliser tous les textes visibles par l'utilisateur (libellés, messages d'erreur, titres) hors du code source.

La localisation est le processus d'adaptation effective de l'application internationalisée à une langue et une région spécifiques (une "locale"). Cela comprend la traduction des textes, mais aussi l'adaptation des formats de date, d'heure, de nombre, de devise, et parfois même des images ou de la mise en page.

Spring Framework, et par conséquent Spring Boot, offre un support robuste et intégré pour l'i18n et la L10n, rendant le développement d'applications multilingues beaucoup plus simple.

Les concepts clés de l'i18n dans Spring

Le support i18n de Spring repose sur quelques concepts fondamentaux :

  • java.util.Locale : Représente une région géographique, politique ou culturelle spécifique. Une Locale est généralement définie par une langue (code ISO 639, ex: "fr", "en") et optionnellement un pays (code ISO 3166, ex: "FR", "CA", "US"). Exemples : fr_FR (Français de France), en_US (Anglais des Etats-Unis), fr_CA (Français du Canada). Spring a besoin de déterminer la Locale de l'utilisateur pour savoir quelle langue et quels formats utiliser.
  • org.springframework.context.MessageSource : C'est l'interface centrale pour résoudre les messages textuels. Elle permet de récupérer une chaîne de caractères (un message) associée à une clé (un code), en fonction d'une Locale donnée. Elle permet également de gérer les messages paramétrés.
  • Fichiers de propriétés (Resource Bundles) : La manière la plus courante de stocker les messages externalisés est d'utiliser des fichiers .properties. Chaque fichier contient des paires clé=valeur. On crée un fichier par défaut (ex: messages.properties) et des fichiers spécifiques pour chaque locale supportée, en suivant une convention de nommage précise (ex: messages_fr.properties, messages_de_DE.properties). Spring recherche automatiquement le fichier le plus spécifique correspondant à la locale demandée, et se rabat sur des versions moins spécifiques (langue seule, puis défaut) si nécessaire.

Configuration avec Spring Boot

La bonne nouvelle est que Spring Boot simplifie grandement la configuration de base pour l'i18n. Si vous placez vos fichiers de messages dans le répertoire par défaut src/main/resources et que vous respectez la convention de nommage (par exemple, messages.properties, messages_fr.properties), Spring Boot auto-configure un bean MessageSource pour vous (une instance de ResourceBundleMessageSource).

Exemple de fichiers de messages :

src/main/resources/messages.properties (Défaut - Anglais) :

welcome.title=Welcome to our Application!
greeting.user=Hello, {0}!
error.notfound=Resource not found.

src/main/resources/messages_fr.properties (Français) :

welcome.title=Bienvenue sur notre Application !
greeting.user=Bonjour, {0} !
error.notfound=Ressource non trouvée.

src/main/resources/messages_fr_CA.properties (Français du Canada - peut surcharger ou ajouter des clés) :

welcome.title=Bienvenue sur notre Super Application Québécoise !
# greeting.user n'est pas défini, on utilisera celui de messages_fr.properties

Vous pouvez personnaliser le nom de base des fichiers et l'encodage via application.properties ou application.yml si nécessaire :

# application.properties
# Chemin de base (sans extension ni suffixe de locale)
spring.messages.basename=i18n/app_messages,i18n/errors 
# Encodage des fichiers
spring.messages.encoding=UTF-8
# Durée pendant laquelle les messages chargés sont mis en cache (secondes)
# spring.messages.cache-duration= PT1H # 1 heure (format ISO-8601 Duration)
# application.yml
spring:
  messages:
    basename: i18n/app_messages,i18n/errors
    encoding: UTF-8
    # cache-duration: PT1H

Déterminer la locale de l'utilisateur (`LocaleResolver`)

Pour que le MessageSource puisse fournir le bon message, il faut savoir quelle est la Locale de l'utilisateur courant. C'est le rôle du LocaleResolver. Spring Boot en configure un par défaut, mais vous pouvez le changer.

  • AcceptHeaderLocaleResolver : C'est le resolver par défaut. Il détermine la locale en se basant sur l'en-tête Accept-Language envoyé par le navigateur du client. Simple, mais l'utilisateur n'a pas de contrôle direct dessus (sauf via les paramètres de son navigateur).
  • SessionLocaleResolver : Stocke la Locale choisie dans l'HttpSession de l'utilisateur. Permet à l'utilisateur de sélectionner une langue qui persistera pendant sa session.
  • CookieLocaleResolver : Stocke la Locale choisie dans un cookie côté client. Permet à la préférence de langue de persister entre les sessions.
  • FixedLocaleResolver : Utilise une locale fixe, définie côté serveur. Utile pour les tests ou si l'application ne supporte qu'une seule locale.

Si vous souhaitez utiliser un autre resolver que celui par défaut (AcceptHeaderLocaleResolver), vous devez le déclarer comme un bean @Bean dans une classe de configuration :

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import java.util.Locale;

@Configuration
public class LocaleConfig {

    @Bean
    public LocaleResolver localeResolver() {
        // SessionLocaleResolver resolver = new SessionLocaleResolver();
        // resolver.setDefaultLocale(Locale.ENGLISH);
        
        CookieLocaleResolver resolver = new CookieLocaleResolver();
        resolver.setDefaultLocale(Locale.FRENCH); // Locale par défaut si non trouvée
        resolver.setCookieName("myAppLocaleCookie");
        resolver.setCookieMaxAge(60 * 60 * 24 * 30); // 30 jours
        resolver.setCookiePath("/");
        return resolver;
    }
}

Permettre le changement de locale (`LocaleChangeInterceptor`)

Si vous utilisez un SessionLocaleResolver ou CookieLocaleResolver, vous voudrez probablement permettre à l'utilisateur de changer sa langue préférée, par exemple via un lien ou un menu déroulant. Le LocaleChangeInterceptor facilite cela.

Cet intercepteur inspecte les requêtes entrantes à la recherche d'un paramètre spécifique (par défaut `locale`). S'il le trouve, il change la locale de l'utilisateur en utilisant le LocaleResolver configuré.

Vous devez déclarer l'intercepteur comme un bean et l'enregistrer dans la configuration de Spring MVC via WebMvcConfigurer :

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

@Configuration
public class LocaleConfig implements WebMvcConfigurer {

    // ... le bean LocaleResolver (voir section précédente)

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang"); // Nom du paramètre URL pour changer la locale (ex: ?lang=fr)
        return lci;
    }

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

Ensuite, dans vos vues, vous pouvez ajouter des liens comme :

English | Français

Utilisation des messages

Dans les Contrôleurs : Vous pouvez injecter le bean MessageSource et l'utiliser pour récupérer des messages programmatiquement. Vous aurez aussi besoin de la Locale courante, que vous pouvez obtenir via LocaleContextHolder.getLocale() ou en l'injectant comme paramètre de méthode.

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.Locale;

@Controller
public class MessageDemoController {

    @Autowired
    private MessageSource messageSource;

    @GetMapping("/action")
    public String performAction(RedirectAttributes redirectAttributes /*, Locale locale */) {
        boolean success = // ... 
        Locale currentLocale = LocaleContextHolder.getLocale(); // Ou utiliser le paramètre 'locale'
        String userName = "Utilisateur Anonyme"; // Exemple d'argument
        
        if (success) {
            String successMsg = messageSource.getMessage("action.success", new Object[]{userName}, currentLocale);
            redirectAttributes.addFlashAttribute("message", successMsg);
        } else {
            String errorMsg = messageSource.getMessage("error.generic", null, "Erreur inconnue", currentLocale);
            redirectAttributes.addFlashAttribute("error", errorMsg);
        }
        return "redirect:/home";
    }
}

Dans les Vues (avec Thymeleaf) : Les moteurs de templates comme Thymeleaf s'intègrent nativement avec le MessageSource de Spring. Vous pouvez utiliser la syntaxe #{...} pour résoudre les messages. Thymeleaf utilise automatiquement la Locale résolue pour la requête en cours.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:text="#{welcome.title}">Titre par défaut</title>
</head>
<body>
    <h1 th:text="#{welcome.title}">Titre par défaut</h1>
    
    <!-- Message avec paramètre -->
    <p th:text="#{greeting.user(${username})}">Bonjour, utilisateur par défaut</p>

    <!-- Message d'erreur (ex: flash attribute) -->
    <div th:if="${error}" class="error">
        <p th:text="${error}">Erreur par défaut</p>
    </div>
</body>
</html>

Localisation (L10n) : Dates, Nombres, Devises

La localisation ne se limite pas aux textes. Les formats de date, d'heure, de nombres et de devises varient considérablement d'une région à l'autre. Spring gère cela via son système de formatage et de conversion, souvent utilisé conjointement avec la liaison de données et les moteurs de vues.

Thymeleaf, par exemple, fournit des utilitaires qui utilisent la Locale courante pour formater correctement ces types de données :

<!-- Supposons que 'productPrice' est un double/BigDecimal et 'orderDate' une date/heure -->

<p>Prix: <span th:text="${#numbers.formatCurrency(productPrice)}">99.99</span></p>
<p>Quantité: <span th:text="${#numbers.formatInteger(quantity, 3, 'POINT')}">1,000</span></p>

<p>Date de commande (court): <span th:text="${#dates.format(orderDate, 'short')}">01/01/2024</span></p>
<p>Date de commande (long): <span th:text="${#dates.format(orderDate, 'long')}">1 janvier 2024</span></p>
<p>Date de commande (pattern): <span th:text="${#dates.format(orderDate, 'dd MMMM yyyy HH:mm')}">...</span></p>

En utilisant ces utilitaires, Thymeleaf appliquera automatiquement les conventions de formatage (symbole monétaire, séparateur décimal/milliers, format de date) appropriées pour la Locale de l'utilisateur (par exemple, 99,99 € et 1 000 pour fr_FR, $99.99 et 1,000 pour en_US).

En conclusion, Spring Boot et Spring MVC offrent un cadre complet et flexible pour créer des applications internationalisées et localisées, permettant d'offrir une expérience utilisateur adaptée à un public diversifié à travers le monde.