Skip to main content

i18n no Spring Boot: tutorial de configuração da internacionalização

Configure MessageSource, crie ficheiros properties específicos de cada região, resolva regiões e apresente modelos Thymeleaf multilingues; depois automatize traduções com IA.

1

Adicionar dependências

Spring Boot Starter Web inclui configuração automática de MessageSource. Adicione Thymeleaf para modelos i18n apresentados no servidor e o starter de validação para mensagens de erro localizadas.

O Spring Boot configura automaticamente um bean MessageSource que lê messages.properties no classpath. Só precisa de configuração explícita para personalizar o nome base, a codificação ou o comportamento da 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

Configurar MessageSource e LocaleResolver

MessageSource do Spring carrega traduções de ficheiros .properties segundo o nome base: messages.properties —predefinido—, messages_de.properties —alemão— e messages_ja.properties —japonês—. Configure LocaleResolver para determinar a região de cada pedido.

Ficheiros de tradução

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.

Configuração de 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 as traduções devolverem o nome da chave, a causa mais frequente é um nome base errado. O valor predefinido é 'messages', associado a messages.properties no classpath. Se os ficheiros tiverem outros nomes ou estiverem num subdiretório, defina explicitamente spring.messages.basename.

Resolução regional

Configure como o Spring determina a região ativa de cada pedido. CookieLocaleResolver guarda a escolha do utilizador entre sessões. LocaleChangeInterceptor permite mudá-la através de um parâmetro de consulta como ?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

Utilizar traduções no código

Aceda às mensagens traduzidas nos controladores através da injeção de MessageSource, nos modelos Thymeleaf com a sintaxe #{...} e nas API REST através do parâmetro Locale resolvido automaticamente.

Controlador com 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";
    }
}

Modelos Thymeleaf

A expressão #{...} do Thymeleaf resolve automaticamente chaves de mensagens dos seus ficheiros .properties. Passe parâmetros com a sintaxe #{key(arg0, arg1)}. O modelo utiliza a região resolvida por 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>
Expressões Thymeleaf como #{greeting('World')} passam argumentos a MessageFormat. O texto estático nas etiquetas HTML serve de recurso ao ver o modelo sem Spring, sendo útil para designers que trabalham diretamente nos modelos.

Localização de API REST

Nas API REST, o Spring resolve automaticamente Locale a partir do cabeçalho Accept-Language. Injete-o como parâmetro do método e passe-o a MessageSource. Os clientes mudam de idioma ao enviar cabeçalhos diferentes.

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!"}
}
As API REST utilizam normalmente AcceptHeaderLocaleResolver —baseado em cabeçalhos— e as aplicações Web utilizam CookieLocaleResolver —baseado em cookies—. Se servir ambos, considere um LocaleResolver personalizado que verifique primeiro cookies e depois recorra a Accept-Language.

Mensagens de validação de beans

O Spring resolve automaticamente as mensagens das restrições de validação através de MessageSource. Utilize marcadores entre chavetas, como {validation.name.required}, nas anotações e defina as traduções nos ficheiros .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

Tratar plurais e variáveis

O Spring utiliza java.text.MessageFormat para interpolação e plurais. O padrão ChoiceFormat trata regras básicas, mas para compatibilidade completa com plurais ICU —6 formas em árabe e 3 em russo— adicione a 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 não é igual às regras de plural ICU. Utiliza intervalos numéricos —0#, 1#, 1<— em vez de categorias CLDR —zero, one, two, few, many e other—. Não chega para idiomas complexos como árabe, polaco ou russo; utilize MessageFormat de ICU4J.

Automatizar as traduções

Depois de concluir a configuração de i18n, traduza os ficheiros .properties com IA. No IDE, peça ao assistente para traduzir o ficheiro de origem ou utilize a CLI do i18n Agent no pipeline de 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
Traduza de forma incremental: quando adicionar novas chaves a messages.properties, traduza apenas essas chaves em vez de gerar novamente todos os ficheiros regionais. Assim preserva as traduções revistas por pessoas nos ficheiros existentes.

Automatizar a qualidade das traduções

Detete chaves em falta e marcadores danificados antes da publicação com i18n-validate. Teste a interface com pseudotraduções através de i18n-pseudo antes de chegarem as traduções reais.

Sem configuração com spring-locale-chain

spring-locale-chain é um starter de código aberto do Spring Boot que configura automaticamente LocaleResolver, LocaleChangeInterceptor e a validação das regiões compatíveis numa única dependência. Defina as regiões em application.yml e a biblioteca trata do 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>

Estrutura de ficheiros recomendada

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

Erros frequentes

Os caracteres não ASCII aparecem ilegíveis

Os ficheiros .properties do Java utilizam ISO-8859-1 por predefinição, não UTF-8. Caracteres como tremas —ü— ou CJK ficam ilegíveis. Solução: defina spring.messages.encoding=UTF-8 em application.yml ou utilize escapes Unicode como \u00FC nos ficheiros. ReloadableResourceBundleMessageSource do Spring Boot utiliza UTF-8 por predefinição, mas ResourceBundleMessageSource não.

ChoiceFormat falha em plurais que não sejam ingleses

ChoiceFormat do Java ({0,choice,0#|1#|1<}) aceita apenas intervalos numéricos; não consegue expressar categorias CLDR como 'few' ou 'many'. Idiomas como árabe —6 formas—, polaco —3— e russo —3— precisam de ICU4J. Não pressuponha que ChoiceFormat trata todos os idiomas.

As alterações das traduções não aparecem

ResourceBundleMessageSource guarda os pacotes em cache indefinidamente por predefinição. Durante o desenvolvimento, utilize ReloadableResourceBundleMessageSource com cacheSeconds=0 para ver alterações sem reiniciar. Em produção, defina uma duração razoável —por exemplo, 3.600 segundos— que equilibre o desempenho e a rapidez das atualizações.

Recurso inesperado para a região da JVM

Por predefinição, o Spring recorre à região predefinida da JVM —Locale.getDefault()—, não ao seu messages.properties. Defina spring.messages.fallback-to-system-locale=false em application.yml para utilizar sempre o pacote predefinido. Caso contrário, um servidor cuja JVM esteja em 'fr' mostra francês em vez de inglês quando falta uma chave na região pedida.

Experimente já o i18n Agent

Largue aqui o seu ficheiro de tradução

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

ou clique para selecionar

Idiomas de destino

Sem registoEstimativa imediata

Recurso regional com spring-locale-chain

Quando falta uma chave numa região como pt-BR, o Spring Boot passa diretamente para a região predefinida em vez de verificar primeiro a região principal 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

Consulte o nosso guia de recurso regional para ver a lista completa de frameworks compatíveis e as 75 cadeias integradas. Learn more →

Perguntas frequentes