Skip to main content

Spring Boot i18n: tutorial de configurare a internaționalizării

Configurați MessageSource, creați fișiere de proprietăți pentru fiecare setare regională, determinați setările regionale și randați șabloane Thymeleaf multilingve — apoi automatizați traducerile cu IA.

1

Adăugați dependențele

Spring Boot Starter Web include din start configurarea automată pentru MessageSource. Adăugați Thymeleaf pentru șabloane i18n randate pe server și starterul de validare pentru mesaje de eroare localizate.

Spring Boot configurează automat un bean MessageSource care citește messages.properties din classpath. Configurarea explicită este necesară numai dacă doriți să personalizați numele de bază, codificarea sau comportamentul memoriei 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

Configurați MessageSource și LocaleResolver

MessageSource din Spring încarcă traducerile din fișiere .properties folosind convenția numelui de bază: messages.properties (implicit), messages_de.properties (germană), messages_ja.properties (japoneză). Configurați un LocaleResolver pentru a stabili setarea regională utilizată la fiecare solicitare.

Fișiere de traducere

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.

Configurarea 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;
    }
}
Dacă traducerile returnează numele cheii în locul textului tradus, cea mai frecventă cauză este un nume de bază greșit. Valoarea implicită este 'messages', care corespunde fișierului messages.properties din classpath. Dacă fișierele au alte nume sau se află într-un subdirector, setați explicit spring.messages.basename.

Determinarea setărilor regionale

Configurați modul în care Spring determină setarea regională activă pentru fiecare solicitare. CookieLocaleResolver păstrează alegerea utilizatorului între sesiuni. LocaleChangeInterceptor le permite utilizatorilor să schimbe setarea regională printr-un parametru de interogare precum ?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

Utilizați traducerile în cod

Accesați mesajele traduse în controlere prin injectarea MessageSource, în șabloanele Thymeleaf cu sintaxa #{...} și în API-urile REST utilizând parametrul Locale determinat automat.

Controler cu 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";
    }
}

Șabloane Thymeleaf

Expresia #{...} din Thymeleaf determină automat cheile mesajelor din fișierele .properties. Transmiteți parametrii cu sintaxa #{key(arg0, arg1)}. Șablonul utilizează setarea regională determinată de 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>
Expresiile Thymeleaf precum #{greeting('World')} transmit argumente către MessageFormat. Textul static din etichetele HTML servește drept rezervă când șablonul este vizualizat fără Spring — o funcție utilă pentru designerii care lucrează direct cu șabloanele.

Localizarea API-urilor REST

Pentru API-urile REST, Spring determină automat Locale din antetul Accept-Language. Injectați-l ca parametru al metodei și transmiteți-l către MessageSource. Clienții schimbă limba trimițând antete Accept-Language diferite.

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!"}
}
API-urile REST utilizează de obicei AcceptHeaderLocaleResolver (bazat pe antet), în timp ce aplicațiile web utilizează CookieLocaleResolver (bazat pe cookie-uri). Dacă le furnizați pe ambele din aceeași aplicație, luați în considerare un LocaleResolver personalizat care verifică mai întâi cookie-urile, apoi revine la antetul Accept-Language.

Mesaje de validare Bean

Spring determină automat mesajele pentru constrângerile de validare din MessageSource. Utilizați substituenți între acolade, precum {validation.name.required}, în adnotările constrângerilor și definiți traducerile în fișierele .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

Gestionați formele de plural și variabilele

Spring utilizează java.text.MessageFormat pentru interpolare și pluraluri. Modelul ChoiceFormat gestionează reguli de plural elementare, dar pentru compatibilitate completă cu pluralurile ICU (cele 6 forme din arabă sau cele 3 din rusă), adăugați biblioteca 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 nu este echivalent cu regulile de plural ICU. Acesta utilizează intervale numerice (0#, 1#, 1<), nu categorii CLDR (zero, one, two, few, many, other). Pentru limbile cu reguli de plural complexe, precum araba, polona sau rusa, ChoiceFormat nu este suficient — utilizați MessageFormat din ICU4J.

Automatizați traducerile

După finalizarea configurării i18n, traduceți fișierele .properties folosind IA. În IDE, solicitați-i asistentului IA să traducă fișierul-sursă sau utilizați i18n Agent CLI în fluxul 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
Traduceți incremental — când adăugați chei noi în messages.properties, traduceți numai cheile noi, fără să regenerați toate fișierele pentru setările regionale. Astfel păstrați traducerile existente verificate de persoane.

Automatizați verificarea calității traducerilor

Detectați cheile lipsă și substituenții nevalizi înainte de lansare cu i18n-validate. Testați interfața cu pseudotraduceri folosind i18n-pseudo înainte de sosirea traducerilor reale.

Configurare automată cu spring-locale-chain

spring-locale-chain este un starter Spring Boot cu sursă deschisă care configurează automat LocaleResolver, LocaleChangeInterceptor și validarea setărilor regionale acceptate printr-o singură dependență. Definiți setările regionale acceptate în application.yml, iar biblioteca se ocupă de restul.

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>

Structura recomandată a fișierelor

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

Capcane frecvente

Caracterele non-ASCII sunt afișate incorect

Fișierele Java .properties utilizează implicit codificarea ISO-8859-1, nu UTF-8. Caractere precum diacriticele germane (ü) sau caracterele CJK sunt afișate incorect. Remediere: setați spring.messages.encoding=UTF-8 în application.yml sau utilizați secvențe escape Unicode precum \u00FC în fișierele .properties. ReloadableResourceBundleMessageSource din Spring Boot utilizează implicit UTF-8, dar ResourceBundleMessageSource nu.

ChoiceFormat nu funcționează pentru pluralurile din alte limbi

ChoiceFormat din Java ({0,choice,0#|1#|1<}) acceptă numai intervale numerice — nu poate exprima categorii de plural CLDR precum 'few' sau 'many'. Limbi precum araba (6 forme), polona (3 forme) și rusa (3 forme) necesită ICU4J pentru pluralizare corectă. Nu presupuneți că ChoiceFormat poate gestiona toate limbile.

Modificările traducerilor nu apar

ResourceBundleMessageSource păstrează implicit pachetele în memoria cache pe termen nelimitat. În timpul dezvoltării, utilizați ReloadableResourceBundleMessageSource cu cacheSeconds=0 pentru a vedea modificările fără repornire. În producție, setați o durată rezonabilă a memoriei cache (de exemplu, 3.600 de secunde) pentru a echilibra performanța și viteza actualizărilor.

Revenire neașteptată la setarea regională JVM

În mod implicit, Spring revine la setarea regională implicită a JVM (Locale.getDefault()), nu la fișierul messages.properties. Setați spring.messages.fallback-to-system-locale=false în application.yml pentru a utiliza întotdeauna pachetul implicit. Altfel, un server cu setarea regională JVM configurată ca 'fr' va afișa franceză în loc de engleză când lipsește o cheie din setarea regională solicitată.

Încercați acum i18n Agent

Plasați aici fișierul de traducere

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

sau faceți clic pentru a-l selecta

Limbi țintă

Nu este necesară înregistrareaEstimare instantanee

Rezerva pentru setările regionale cu spring-locale-chain

Când lipsește o cheie de traducere dintr-o setare regională precum pt-BR, Spring Boot trece direct la setarea regională implicită, fără să verifice mai întâi setarea regională părinte 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

Consultați Ghidul nostru privind setările regionale de rezervă pentru lista completă a cadrelor acceptate și a celor 75 de lanțuri predefinite. Learn more →

Întrebări frecvente