Skip to main content

Spring Boot i18n: tutorial voor internationalisatieconfiguratie

Configureer MessageSource, maak localespecifieke properties-bestanden, bepaal locales en render meertalige Thymeleaf-sjablonen. Automatiseer daarna vertalingen met AI.

1

Afhankelijkheden toevoegen

Spring Boot Starter Web bevat standaard automatische MessageSource-configuratie. Voeg Thymeleaf toe voor op de server gerenderde i18n-sjablonen en de validatiestarter voor gelokaliseerde foutmeldingen.

Spring Boot configureert automatisch een MessageSource-bean die messages.properties van de classpath leest. Expliciete configuratie is alleen nodig als je de basisnaam, codering of het cachegedrag wilt aanpassen.
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

MessageSource en LocaleResolver configureren

MessageSource van Spring laadt vertalingen uit .properties-bestanden volgens de basisnaamconventie: messages.properties (standaard), messages_de.properties (Duits), messages_ja.properties (Japans). Configureer een LocaleResolver om per verzoek te bepalen welke locale wordt gebruikt.

Vertaalbestanden

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.

MessageSource-configuratie

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;
    }
}
Als vertalingen de sleutelnaam in plaats van de vertaalde tekst retourneren, is een verkeerde basisnaam de meest voorkomende oorzaak. De standaard is 'messages', wat overeenkomt met messages.properties op de classpath. Stel spring.messages.basename expliciet in als je bestanden anders heten of in een submap staan.

Localebepaling

Configureer hoe Spring voor elk verzoek de actieve locale bepaalt. CookieLocaleResolver bewaart de keuze van de gebruiker tussen sessies. Met LocaleChangeInterceptor kunnen gebruikers via een queryparameter zoals ?lang=de van locale wisselen.

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

Vertalingen in code gebruiken

Open vertaalde berichten in controllers via MessageSource-injectie, in Thymeleaf-sjablonen met de syntaxis #{...} en in REST-API's via de automatisch bepaalde parameter Locale.

Controller met 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";
    }
}

Thymeleaf-sjablonen

De Thymeleaf-expressie #{...} zoekt automatisch berichtsleutels in je .properties-bestanden op. Geef parameters door met de syntaxis #{key(arg0, arg1)}. Het sjabloon gebruikt de locale die je LocaleResolver heeft bepaald.

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>
Thymeleaf-expressies zoals #{greeting('World')} geven argumenten door aan MessageFormat. De statische tekst binnen HTML-tags fungeert als terugval wanneer het sjabloon zonder Spring wordt bekeken, wat handig is voor ontwerpers die rechtstreeks aan sjablonen werken.

Lokalisatie van REST-API's

Voor REST-API's bepaalt Spring de Locale automatisch op basis van de Accept-Language-header. Injecteer deze als methodeparameter en geef deze door aan MessageSource. Clients wisselen van taal door verschillende Accept-Language-headers te verzenden.

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!"}
}
REST-API's gebruiken doorgaans AcceptHeaderLocaleResolver (op basis van headers), terwijl webapps CookieLocaleResolver gebruiken (op basis van cookies). Als je beide vanuit dezelfde app aanbiedt, kun je een aangepaste LocaleResolver gebruiken die eerst cookies controleert en daarna terugvalt op de Accept-Language-header.

Bean-validatieberichten

Spring haalt berichten voor validatiebeperkingen automatisch uit je MessageSource. Gebruik placeholders met accolades, zoals {validation.name.required}, in je beperkingsannotaties en definieer de vertalingen in je .properties-bestanden.

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

Meervoudsvormen en variabelen verwerken

Spring gebruikt java.text.MessageFormat voor interpolatie en meervoudsvormen. Het ChoiceFormat-patroon verwerkt eenvoudige meervoudsregels; voeg voor volledige ICU-ondersteuning (de 6 Arabische en 3 Russische vormen) de bibliotheek ICU4J toe.

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 is niet hetzelfde als ICU-meervoudsregels. Het gebruikt numerieke bereiken (0#, 1#, 1<) in plaats van CLDR-categorieën (zero, one, two, few, many, other). Voor talen met complexe meervoudsregels, zoals Arabisch, Pools en Russisch, volstaat ChoiceFormat niet; gebruik MessageFormat van ICU4J.

Vertalingen automatiseren

Nu je i18n-configuratie klaar is, kun je .properties-bestanden met AI vertalen. Vraag je AI-assistent in je IDE om het bronbestand te vertalen of gebruik de CLI van i18n Agent in je CI/CD-pipeline.

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
Vertaal stapsgewijs: vertaal bij nieuwe sleutels in messages.properties alleen die nieuwe sleutels in plaats van alle localebestanden opnieuw te genereren. Zo blijven door mensen beoordeelde vertalingen behouden.

Kwaliteitscontrole van vertalingen automatiseren

Vind ontbrekende sleutels en kapotte plaatsaanduidingen vóór de release met i18n-validate. Test je gebruikersinterface met pseudovertalingen uit i18n-pseudo voordat de echte vertalingen klaar zijn.

Zonder configuratie met spring-locale-chain

spring-locale-chain is een opensourcestarter voor Spring Boot die LocaleResolver, LocaleChangeInterceptor en validatie van ondersteunde locales automatisch als één afhankelijkheid configureert. Definieer je ondersteunde locales in application.yml; de bibliotheek doet de rest.

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>

Aanbevolen bestandsstructuur

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

Veelvoorkomende valkuilen

Niet-ASCII-tekens worden onleesbaar weergegeven

Java gebruikt voor .properties-bestanden standaard de codering ISO-8859-1, niet UTF-8. Tekens zoals umlauten (ü) en CJK-tekens worden daardoor onleesbaar weergegeven. Oplossing: stel spring.messages.encoding=UTF-8 in application.yml in of gebruik Unicode-escapes zoals \u00FC in je .properties-bestanden. ReloadableResourceBundleMessageSource van Spring Boot gebruikt standaard UTF-8, maar ResourceBundleMessageSource niet.

ChoiceFormat werkt niet voor meervoudsvormen buiten het Engels

ChoiceFormat van Java ({0,choice,0#|1#|1<}) ondersteunt alleen numerieke bereiken en kan CLDR-meervoudscategorieën zoals 'few' en 'many' niet uitdrukken. Talen zoals Arabisch (6 vormen), Pools (3 vormen) en Russisch (3 vormen) hebben ICU4J nodig voor correcte meervoudsvormen. Ga er niet van uit dat ChoiceFormat alle talen verwerkt.

Vertaalwijzigingen worden niet weergegeven

ResourceBundleMessageSource bewaart bundels standaard onbeperkt in de cache. Gebruik tijdens de ontwikkeling ReloadableResourceBundleMessageSource met cacheSeconds=0 om wijzigingen zonder herstart te zien. Stel in productie een redelijke cacheduur in (bijvoorbeeld 3600 seconden) voor een goede balans tussen prestaties en updatesnelheid.

Onverwachte terugval op de JVM-locale

Spring valt standaard terug op de standaardlocale van de JVM (Locale.getDefault()), niet op je bestand messages.properties. Stel spring.messages.fallback-to-system-locale=false in application.yml in om altijd de standaardbundel te gebruiken. Anders toont een server waarvan de JVM-locale op 'fr' staat Frans in plaats van Engels wanneer een sleutel in de aangevraagde locale ontbreekt.

Probeer i18n Agent nu

Zet je vertaalbestand hier neer

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

of klik om een bestand te selecteren

Doeltalen

Geen registratie nodigDirecte prijsindicatie

Localeterugval met spring-locale-chain

Wanneer een vertaalsleutel ontbreekt in een regionale locale zoals pt-BR, schakelt Spring Boot rechtstreeks over op de standaardlocale in plaats van eerst de bovenliggende locale pt te controleren.

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

Bekijk onze handleiding voor locale-fallbacks voor de volledige lijst met ondersteunde frameworks en 75 ingebouwde ketens. Learn more →

Veelgestelde vragen