Skip to main content

i18n di Spring Boot: tutorial di configurazione dell'internazionalizzazione

Configuri MessageSource, crei file properties specifici per lingua, risolva le lingue e visualizzi modelli Thymeleaf multilingue, quindi automatizzi le traduzioni con l'IA.

1

Aggiungere le dipendenze

Spring Boot Starter Web include la configurazione automatica di MessageSource. Aggiunga Thymeleaf per i modelli i18n con rendering lato server e lo starter di convalida per i messaggi di errore localizzati.

Spring Boot configura automaticamente un bean MessageSource che legge messages.properties dal classpath. Serve una configurazione esplicita soltanto per personalizzare il nome di base, la codifica o il comportamento della 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

Configurare MessageSource e LocaleResolver

MessageSource di Spring carica le traduzioni da file .properties seguendo la convenzione del nome di base: messages.properties (predefinito), messages_de.properties (tedesco), messages_ja.properties (giapponese). Configuri un LocaleResolver per determinare la lingua da usare per ogni richiesta.

File di traduzione

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.

Configurazione di 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;
    }
}
Se le traduzioni restituiscono il nome della chiave anziché il testo tradotto, la causa più comune è un nome di base errato. Il valore predefinito è 'messages', che corrisponde a messages.properties nel classpath. Se i file hanno nomi diversi o si trovano in una sottodirectory, imposti esplicitamente spring.messages.basename.

Risoluzione della lingua

Configuri il modo in cui Spring determina la lingua attiva per ogni richiesta. CookieLocaleResolver mantiene la scelta dell'utente tra le sessioni. LocaleChangeInterceptor consente agli utenti di cambiare lingua tramite un parametro di query come ?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

Usare le traduzioni nel codice

Acceda ai messaggi tradotti nei controller tramite l'iniezione di MessageSource, nei modelli Thymeleaf con la sintassi #{...} e nelle API REST tramite il parametro Locale risolto automaticamente.

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

Modelli Thymeleaf

L'espressione #{...} di Thymeleaf risolve automaticamente le chiavi dei messaggi dai file .properties. Passi i parametri con la sintassi #{key(arg0, arg1)}. Il modello usa la lingua risolta da 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>
Le espressioni Thymeleaf come #{greeting('World')} passano gli argomenti a MessageFormat. Il testo statico nei tag HTML funge da fallback quando il modello viene visualizzato senza Spring, una caratteristica utile per i designer che lavorano direttamente sui modelli.

Localizzazione delle API REST

Per le API REST, Spring risolve automaticamente Locale dall'intestazione Accept-Language. Lo inserisca come parametro del metodo e lo passi a MessageSource. I client cambiano lingua inviando intestazioni Accept-Language diverse.

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!"}
}
In genere, le API REST usano AcceptHeaderLocaleResolver (basato sulle intestazioni), mentre le app web usano CookieLocaleResolver (basato sui cookie). Se entrambi sono serviti dalla stessa app, valuti un LocaleResolver personalizzato che controlli prima i cookie e poi passi all'intestazione Accept-Language.

Messaggi di convalida dei bean

Spring risolve automaticamente i messaggi dei vincoli di convalida da MessageSource. Usi segnaposto tra parentesi graffe come {validation.name.required} nelle annotazioni dei vincoli e definisca le traduzioni nei file .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

Gestire plurali e variabili

Spring usa java.text.MessageFormat per interpolazione e plurali. Il pattern ChoiceFormat gestisce le regole del plurale di base, ma per un supporto ICU completo, comprese le 6 forme dell'arabo e le 3 del russo, aggiunga la 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 non equivale alle regole del plurale ICU. Usa intervalli numerici (0#, 1#, 1<) anziché categorie CLDR (zero, one, two, few, many, other). Per le lingue con regole complesse, come arabo, polacco o russo, ChoiceFormat non è sufficiente: usi invece MessageFormat di ICU4J.

Automatizzare le traduzioni

Dopo aver completato la configurazione i18n, traduca i file .properties con l'IA. Nell'IDE, chieda all'assistente IA di tradurre il file di origine oppure usi la CLI di i18n Agent nella 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
Traduca in modo incrementale: quando aggiunge nuove chiavi a messages.properties, traduca soltanto le nuove chiavi anziché rigenerare tutti i file di lingua. In questo modo preserva le traduzioni revisionate da persone nei file esistenti.

Automatizzare la qualità

Con i18n-validate, rilevi chiavi mancanti e segnaposto non validi prima del rilascio. Testi l'interfaccia con le pseudotraduzioni di i18n-pseudo prima che arrivino le traduzioni reali.

Configurazione automatica con spring-locale-chain

spring-locale-chain è uno starter Spring Boot open source che configura automaticamente LocaleResolver, LocaleChangeInterceptor e la convalida delle lingue supportate tramite un'unica dipendenza. Definisca le lingue supportate in application.yml e la biblioteca gestirà tutto il resto.

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>

Struttura dei file consigliata

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

Problemi comuni

I caratteri non ASCII vengono visualizzati in modo errato

Per impostazione predefinita, i file .properties Java usano la codifica ISO-8859-1, non UTF-8. Caratteri come le dieresi (ü) o i caratteri CJK vengono visualizzati in modo errato. Soluzione: imposti spring.messages.encoding=UTF-8 in application.yml oppure usi sequenze di escape Unicode come \u00FC nei file .properties. ReloadableResourceBundleMessageSource di Spring Boot usa UTF-8 per impostazione predefinita, mentre ResourceBundleMessageSource no.

ChoiceFormat non funziona per i plurali non inglesi

ChoiceFormat di Java ({0,choice,0#|1#|1<}) supporta soltanto intervalli numerici: non può esprimere categorie del plurale CLDR come 'few' o 'many'. Lingue come arabo (6 forme), polacco (3 forme) e russo (3 forme) richiedono ICU4J per una corretta gestione dei plurali. Non presuma che ChoiceFormat gestisca tutte le lingue.

Le modifiche alle traduzioni non vengono applicate

Per impostazione predefinita, ResourceBundleMessageSource memorizza i bundle nella cache a tempo indeterminato. Durante lo sviluppo, usi ReloadableResourceBundleMessageSource con cacheSeconds=0 per vedere le modifiche senza riavviare. In produzione, imposti una durata ragionevole della cache, ad esempio 3.600 secondi, per bilanciare prestazioni e velocità di aggiornamento.

Fallback imprevisto alla lingua della JVM

Per impostazione predefinita, Spring passa alla lingua predefinita della JVM (Locale.getDefault()), non al file messages.properties. Imposti spring.messages.fallback-to-system-locale=false in application.yml per usare sempre il bundle predefinito. Altrimenti, un server con lingua della JVM impostata su 'fr' mostrerà il francese anziché l'inglese quando manca una chiave nella lingua richiesta.

Provi subito i18n Agent

Trascinare qui il file di traduzione

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

oppure fare clic per selezionarlo

Lingue di destinazione

Nessuna registrazione richiestaPreventivo immediato

Fallback della lingua con spring-locale-chain

Quando manca una chiave di traduzione in una lingua regionale come pt-BR, Spring Boot passa direttamente alla lingua predefinita anziché controllare prima la lingua principale 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

Consultare la Guida al fallback delle lingue per l'elenco completo dei framework supportati e delle 75 catene integrate. Learn more →

Domande frequenti