Spring Boot i18n: ръководство за настройване на интернационализацията
Конфигурирайте MessageSource, създайте файлове .properties за отделните локали, определете локалите и изобразявайте многоезични Thymeleaf шаблони — след това автоматизирайте преводите с ИИ.
Добавете зависимостите
Spring Boot Starter Web включва готова автоматична конфигурация на MessageSource. Добавете Thymeleaf за i18n шаблони, изобразявани от сървъра, и стартовия пакет за валидация за локализирани съобщения за грешки.
<!-- 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>Конфигурирайте MessageSource и LocaleResolver
MessageSource на Spring зарежда преводите от файлове .properties според конвенция за базовото име: messages.properties (по подразбиране), messages_de.properties (немски), messages_ja.properties (японски). Конфигурирайте LocaleResolver, за да определяте кой локал да се използва за всяка заявка.
Файлове с преводи
# 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.Конфигурация на MessageSource
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;
}
}Определяне на локала
Конфигурирайте как Spring определя активния локал за всяка заявка. CookieLocaleResolver запазва избора на потребителя между сесиите. LocaleChangeInterceptor позволява на потребителите да сменят локала чрез параметър на заявката като ?lang=de.
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());
}
}Използвайте преводите в кода
Получавайте достъп до преведените съобщения в контролерите чрез инжектиране на MessageSource, в Thymeleaf шаблоните чрез синтаксиса #{...}, а в REST API — чрез автоматично определения параметър Locale.
Контролер с MessageSource
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 шаблони
Изразът #{...} на Thymeleaf автоматично намира ключовете на съобщенията във Вашите файлове .properties. Подавайте параметри чрез синтаксиса #{key(arg0, arg1)}. Шаблонът използва локала, определен от Вашия LocaleResolver.
<!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>Локализация на REST API
За REST API Spring автоматично определя Locale от заглавката Accept-Language. Инжектирайте го като параметър на метод и го подайте на MessageSource. Клиентите сменят езика, като изпращат различни заглавки Accept-Language.
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!"}
}Съобщения от Bean Validation
Spring автоматично намира съобщенията за ограниченията при валидация във Вашия MessageSource. Използвайте заместващи параметри с фигурни скоби като {validation.name.required} в анотациите за ограничения и определете преводите във Вашите файлове .properties.
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Обработвайте множествено число и променливи
Spring използва java.text.MessageFormat за интерполация и множествено число. Шаблонът ChoiceFormat обработва основни правила за множествено число, но за пълна поддръжка на ICU формите (6 в арабския и 3 в руския) добавете библиотеката ICU4J.
# 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}Автоматизирайте преводите
След като конфигурирате i18n, преведете своите .properties файлове с помощта на ИИ. Поискайте от асистента с ИИ във Вашата IDE да преведе изходния файл или използвайте i18n Agent CLI във Вашия CI/CD пайплайн.
# 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Автоматизирайте контрола на качеството на превода
Без конфигуриране със spring-locale-chain
spring-locale-chain е стартер с отворен код за Spring Boot, който автоматично конфигурира LocaleResolver, LocaleChangeInterceptor и проверката на поддържаните локали чрез една зависимост. Посочете поддържаните локали в application.yml, а библиотеката ще се погрижи за останалото.
<!-- 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>Препоръчителна структура на файловете
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Често срещани затруднения
Знаците извън ASCII се показват неправилно
ChoiceFormat не работи правилно с множествените форми на други езици
Промените в преводите не се отразяват
Неочаквано преминаване към локала на JVM
Изпробвайте i18n Agent сега
Пуснете тук Вашия файл за превод
JSON, YAML, PO, XML, CSV, Markdown, Properties
или натиснете, за да изберете файл
Целеви езици
Резервни локали със spring-locale-chain
Когато липсва ключ за превод в регионален локал като pt-BR, Spring Boot преминава направо към локала по подразбиране, вместо първо да провери родителския локал pt.
<!-- Maven -->
<dependency>
<groupId>ai.i18nagent</groupId>
<artifactId>spring-locale-chain</artifactId>
</dependency># application.yml
locale-chain:
fallbacks:
pt-BR:
- pt
- en
zh-Hant-HK:
- zh-Hant
- zh
- enВижте нашето ръководство за резервни локали за пълния списък с поддържани платформи и 75 вградени вериги. Learn more →