Skip to main content

Spring Boot i18n: instrukcja konfiguracji internacjonalizacji

Skonfiguruj MessageSource, utwórz pliki właściwości dla poszczególnych języków, rozwiązuj ustawienia regionalne i renderuj wielojęzyczne szablony Thymeleaf, a następnie zautomatyzuj tłumaczenia z AI.

1

Dodaj zależności

Spring Boot Starter Web zawiera gotową automatyczną konfigurację MessageSource. Dodaj Thymeleaf do renderowanych na serwerze szablonów i18n oraz starter walidacji do zlokalizowanych komunikatów o błędach.

Spring Boot automatycznie konfiguruje bean MessageSource odczytujący messages.properties ze ścieżki klas. Jawna konfiguracja jest potrzebna tylko wtedy, gdy chcesz dostosować nazwę bazową, kodowanie lub działanie pamięci podręcznej.
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

Skonfiguruj MessageSource i LocaleResolver

MessageSource Springa wczytuje tłumaczenia z plików .properties zgodnie z konwencją nazwy bazowej: messages.properties (domyślny), messages_de.properties (niemiecki), messages_ja.properties (japoński). Skonfiguruj LocaleResolver, aby dla każdego żądania określać używane ustawienia regionalne.

Pliki tłumaczeń

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.

Konfiguracja 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;
    }
}
Jeśli zamiast przetłumaczonego tekstu zwracana jest nazwa klucza, najczęstszą przyczyną jest nieprawidłowa nazwa bazowa. Domyślna wartość to 'messages', która odpowiada plikowi messages.properties w ścieżce klas. Jeśli pliki mają inne nazwy lub znajdują się w podkatalogu, jawnie ustaw spring.messages.basename.

Rozwiązywanie ustawień regionalnych

Skonfiguruj sposób określania przez Spring aktywnych ustawień regionalnych dla każdego żądania. CookieLocaleResolver zachowuje wybór użytkownika między sesjami. LocaleChangeInterceptor pozwala zmieniać język za pomocą parametru zapytania takiego jak ?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

Używaj tłumaczeń w kodzie

Uzyskuj dostęp do przetłumaczonych wiadomości w kontrolerach przez wstrzyknięcie MessageSource, w szablonach Thymeleaf za pomocą składni #{...} oraz w interfejsach REST przez automatycznie rozwiązany parametr Locale.

Kontroler z 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";
    }
}

Szablony Thymeleaf

Wyrażenie #{...} Thymeleaf automatycznie rozwiązuje klucze wiadomości z plików .properties. Przekazuj parametry za pomocą składni #{key(arg0, arg1)}. Szablon używa ustawień regionalnych rozwiązanych przez 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>
Wyrażenia Thymeleaf takie jak #{greeting('World')} przekazują argumenty do MessageFormat. Statyczny tekst wewnątrz znaczników HTML służy jako wartość rezerwowa podczas wyświetlania szablonu bez Springa — przydaje się projektantom pracującym bezpośrednio nad szablonami.

Lokalizacja interfejsu REST API

W interfejsach REST API Spring automatycznie rozwiązuje Locale z nagłówka Accept-Language. Wstrzyknij go jako parametr metody i przekaż do MessageSource. Klienci zmieniają język, wysyłając inne nagłówki Accept-Language.

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!"}
}
Interfejsy REST API zwykle używają AcceptHeaderLocaleResolver (na podstawie nagłówka), a aplikacje internetowe CookieLocaleResolver (na podstawie pliku cookie). Jeśli obsługujesz oba w tej samej aplikacji, rozważ niestandardowy LocaleResolver, który najpierw sprawdza pliki cookie, a potem przechodzi do nagłówka Accept-Language.

Komunikaty walidacji beanów

Spring automatycznie rozwiązuje komunikaty ograniczeń walidacji z MessageSource. Używaj symboli zastępczych w nawiasach klamrowych, takich jak {validation.name.required}, w adnotacjach ograniczeń i definiuj tłumaczenia w plikach .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

Obsłuż liczbę mnogą i zmienne

Spring używa java.text.MessageFormat do interpolacji i liczby mnogiej. Wzorzec ChoiceFormat obsługuje podstawowe reguły liczby mnogiej, ale pełna obsługa ICU (6 form w arabskim i 3 w rosyjskim) wymaga dodania biblioteki 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 nie jest tym samym co reguły liczby mnogiej ICU. Używa zakresów liczbowych (0#, 1#, 1<), a nie kategorii CLDR (zero, one, two, few, many, other). ChoiceFormat nie wystarcza w językach o złożonych regułach, takich jak arabski, polski i rosyjski — użyj zamiast niego MessageFormat z ICU4J.

Zautomatyzuj tłumaczenia

Po skonfigurowaniu i18n tłumacz pliki .properties za pomocą AI. Poproś asystenta AI w środowisku programistycznym o przetłumaczenie pliku źródłowego albo użyj CLI i18n Agent w 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
Tłumacz przyrostowo — po dodaniu nowych kluczy do messages.properties przetłumacz tylko nowe klucze zamiast ponownie generować wszystkie pliki językowe. Pozwala to zachować istniejące tłumaczenia sprawdzone przez człowieka.

Zautomatyzuj kontrolę jakości tłumaczeń

Wykrywaj brakujące klucze i uszkodzone symbole zastępcze przed wydaniem za pomocą i18n-validate. Testuj interfejs z pseudotłumaczeniami przy użyciu i18n-pseudo, zanim pojawią się prawdziwe tłumaczenia.

Konfiguracja bez kodu z spring-locale-chain

spring-locale-chain to starter Spring Boot o otwartym kodzie źródłowym, który za pomocą jednej zależności automatycznie konfiguruje LocaleResolver, LocaleChangeInterceptor i walidację obsługiwanych ustawień regionalnych. Zdefiniuj obsługiwane ustawienia w application.yml, a biblioteka zajmie się resztą.

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>

Zalecana struktura plików

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

Typowe pułapki

Znaki spoza ASCII są wyświetlane nieprawidłowo

Pliki .properties Javy domyślnie używają kodowania ISO-8859-1, a nie UTF-8. Znaki takie jak umlauty (ü) lub znaki CJK są wyświetlane nieprawidłowo. Rozwiązanie: ustaw spring.messages.encoding=UTF-8 w application.yml albo użyj znaków ucieczki Unicode takich jak \u00FC w plikach .properties. ReloadableResourceBundleMessageSource Spring Boot domyślnie używa UTF-8, ale ResourceBundleMessageSource nie.

ChoiceFormat nie działa dla nieangielskich form liczby mnogiej

ChoiceFormat Javy ({0,choice,0#|1#|1<}) obsługuje tylko zakresy liczbowe — nie potrafi wyrazić kategorii liczby mnogiej CLDR takich jak 'few' i 'many'. Języki takie jak arabski (6 form), polski (3 formy) i rosyjski (3 formy) wymagają ICU4J do poprawnej obsługi liczby mnogiej. Nie zakładaj, że ChoiceFormat obsługuje wszystkie języki.

Zmiany tłumaczeń nie są widoczne

ResourceBundleMessageSource domyślnie bezterminowo buforuje pakiety. Podczas programowania używaj ReloadableResourceBundleMessageSource z cacheSeconds=0, aby widzieć zmiany bez ponownego uruchamiania. W wersji produkcyjnej ustaw rozsądny czas buforowania (np. 3600 sekund), aby zrównoważyć wydajność i szybkość aktualizacji.

Nieoczekiwany powrót do ustawień regionalnych JVM

Domyślnie Spring wraca do ustawień regionalnych JVM (Locale.getDefault()), a nie do pliku messages.properties. Ustaw spring.messages.fallback-to-system-locale=false w application.yml, aby zawsze używać pakietu domyślnego. W przeciwnym razie serwer z ustawieniami JVM równymi 'fr' wyświetli francuski zamiast angielskiego, gdy w żądanym języku brakuje klucza.

Wypróbuj i18n Agent

Upuść tutaj plik tłumaczenia

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

lub kliknij, aby go wybrać

Języki docelowe

Rejestracja nie jest wymaganaNatychmiastowa wycena

Rezerwowe ustawienia regionalne z spring-locale-chain

Gdy brakuje klucza tłumaczenia w regionalnym wariancie języka, takim jak pt-BR, Spring Boot przechodzi bezpośrednio do języka domyślnego, zamiast najpierw sprawdzić język nadrzędny 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

Zobacz nasz przewodnik po rezerwowych ustawieniach regionalnych, aby poznać pełną listę obsługiwanych frameworków i 75 wbudowanych łańcuchów. Learn more →

Najczęściej zadawane pytania