Skip to main content

Spring Boot i18n: Vejledning til opsætning af internationalisering

Konfigurer MessageSource, opret landestandardspecifikke properties-filer, fastlæg landestandarder og gengiv flersprogede Thymeleaf-skabeloner — automatiser derefter oversættelser med AI.

1

Tilføj afhængigheder

Spring Boot Starter Web leveres med automatisk konfiguration af MessageSource. Tilføj Thymeleaf til servergengivne i18n-skabeloner og valideringsstarteren til lokaliserede fejlmeddelelser.

Spring Boot konfigurerer automatisk en MessageSource-bean, der læser fra messages.properties på classpath. Du behøver kun en eksplicit konfiguration, hvis du vil tilpasse basisnavnet, tegnkodningen eller cachefunktionen.
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

Konfigurer MessageSource og LocaleResolver

Springs MessageSource indlæser oversættelser fra .properties-filer efter konventionen for basisnavne: messages.properties (standard), messages_de.properties (tysk), messages_ja.properties (japansk). Konfigurer en LocaleResolver for at afgøre, hvilken landestandard der skal bruges til hver anmodning.

Oversættelsesfiler

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.

Konfiguration af 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;
    }
}
Hvis oversættelser returnerer nøglenavnet i stedet for den oversatte tekst, er den mest almindelige årsag et forkert basisnavn. Standardværdien er 'messages', som svarer til messages.properties på classpath. Hvis dine filer har andre navne eller ligger i en undermappe, skal du angive spring.messages.basename eksplicit.

Fastlæggelse af landestandard

Konfigurer, hvordan Spring fastlægger den aktive landestandard for hver anmodning. CookieLocaleResolver gemmer brugerens valg på tværs af sessioner. LocaleChangeInterceptor giver brugerne mulighed for at skifte landestandard via en forespørgselsparameter som ?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

Brug oversættelser i kode

Få adgang til oversatte meddelelser i controllere via injektion af MessageSource, i Thymeleaf-skabeloner med syntaksen #{...} og i REST-API'er med den automatisk fastlagte Locale-parameter.

Controller med 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-skabeloner

Thymeleaf's #{...}-udtryk finder automatisk meddelelsesnøgler i dine .properties-filer. Overfør parametre med syntaksen #{key(arg0, arg1)}. Skabelonen bruger den landestandard, som din LocaleResolver har fastlagt.

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-udtryk som #{greeting('World')} overfører argumenter til MessageFormat. Den statiske tekst i HTML-tags fungerer som reservetekst, når skabelonen vises uden Spring — det er nyttigt for designere, der arbejder direkte med skabelonerne.

Lokalisering af REST-API'er

For REST-API'er fastlægger Spring automatisk Locale ud fra Accept-Language-headeren. Injicer den som en metodeparameter og overfør den til MessageSource. Klienter skifter sprog ved at sende forskellige Accept-Language-headere.

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'er bruger typisk AcceptHeaderLocaleResolver (headerbaseret), mens webapps bruger CookieLocaleResolver (cookiebaseret). Hvis du leverer begge fra den samme app, kan du overveje en tilpasset LocaleResolver, der først kontrollerer cookies og derefter falder tilbage til Accept-Language-headeren.

Meddelelser fra Bean Validation

Spring finder automatisk meddelelser om valideringsbegrænsninger i din MessageSource. Brug pladsholdere med krøllede parenteser som {validation.name.required} i dine begrænsningsannoteringer og definer oversættelserne i dine .properties-filer.

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

Håndter flertalsformer og variabler

Spring bruger java.text.MessageFormat til interpolation og flertalsformer. ChoiceFormat-mønstret håndterer grundlæggende flertalsregler, men fuld understøttelse af ICU-flertalsregler (arabisk har 6 former, russisk har 3) kræver ICU4J-biblioteket.

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 er ikke det samme som ICU-flertalsregler. Det bruger numeriske intervaller (0#, 1#, 1<) frem for CLDR-kategorier (zero, one, two, few, many, other). ChoiceFormat er utilstrækkeligt til sprog med komplekse flertalsregler som arabisk, polsk eller russisk — brug ICU4J's MessageFormat i stedet.

Automatiser oversættelser

Når din i18n-opsætning er færdig, kan du oversætte dine .properties-filer med AI. Bed din AI-assistent i din IDE om at oversætte kildefilen eller brug i18n Agent CLI i din 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
Oversæt trinvist — når du tilføjer nye nøgler til messages.properties, skal du kun oversætte de nye nøgler frem for at generere alle landestandardfiler igen. Det bevarer eventuelle oversættelser i eksisterende filer, som er blevet gennemgået af mennesker.

Automatiser oversættelseskvaliteten

Find manglende nøgler og ugyldige pladsholdere med i18n-validate, før de udgives. Test din brugergrænseflade med pseudooversættelser ved hjælp af i18n-pseudo, før de rigtige oversættelser er klar.

Konfigurationsfri opsætning med spring-locale-chain

spring-locale-chain er en Spring Boot-starter med åben kildekode, der automatisk konfigurerer LocaleResolver, LocaleChangeInterceptor og validering af understøttede landestandarder gennem én enkelt afhængighed. Definer dine understøttede landestandarder i application.yml, så håndterer biblioteket resten.

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>

Anbefalet filstruktur

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

Almindelige faldgruber

Ikke-ASCII-tegn vises som ulæselige tegn

Java .properties-filer bruger som standard ISO-8859-1-tegnkodning, ikke UTF-8. Tegn som omlyde (ü) eller CJK-tegn vises som ulæselige tegn. Løsning: Angiv spring.messages.encoding=UTF-8 i application.yml eller brug Unicode-koder som \u00FC i dine .properties-filer. Spring Boots ReloadableResourceBundleMessageSource bruger som standard UTF-8, men det gør ResourceBundleMessageSource ikke.

ChoiceFormat fungerer ikke med ikke-engelske flertalsformer

Java's ChoiceFormat ({0,choice,0#|1#|1<}) understøtter kun numeriske intervaller — det kan ikke udtrykke CLDR-flertalskategorier som 'few' eller 'many'. Sprog som arabisk (6 former), polsk (3 former) og russisk (3 former) kræver ICU4J for korrekt bøjning i flertal. Gå ikke ud fra, at ChoiceFormat håndterer alle sprog.

Ændringer i oversættelser vises ikke

ResourceBundleMessageSource gemmer som standard pakker i cachen på ubestemt tid. Under udvikling kan du bruge ReloadableResourceBundleMessageSource med cacheSeconds=0 for at se ændringer uden at genstarte. I produktion bør du angive en passende cachevarighed (f.eks. 3600 sekunder) for at afveje ydeevne og opdateringshastighed.

Uventet fallback til JVM-landestandarden

Som standard falder Spring tilbage til JVM'ens standardlandestandard (Locale.getDefault()), ikke din messages.properties-fil. Angiv spring.messages.fallback-to-system-locale=false i application.yml for altid at bruge standardpakken. Ellers viser en server med JVM-landestandarden 'fr' fransk i stedet for engelsk, når en nøgle mangler i den ønskede landestandard.

Prøv i18n Agent nu

Slip din oversættelsesfil her

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

eller klik for at vælge en fil

Målsprog

Kræver ingen tilmeldingEstimat med det samme

Fallback af landestandard med spring-locale-chain

Når en oversættelsesnøgle mangler i en regional landestandard som pt-BR, springer Spring Boot direkte til standardlandestandarden i stedet for først at kontrollere den overordnede landestandard 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

Se vores guide til fallback af landestandarder for at få den komplette liste over understøttede frameworks og 75 indbyggede kæder. Learn more →

Ofte stillede spørgsmål