Skip to main content

Spring Boot i18n: ръководство за настройване на интернационализацията

Конфигурирайте MessageSource, създайте файлове .properties за отделните локали, определете локалите и изобразявайте многоезични Thymeleaf шаблони — след това автоматизирайте преводите с ИИ.

1

Добавете зависимостите

Spring Boot Starter Web включва готова автоматична конфигурация на MessageSource. Добавете Thymeleaf за i18n шаблони, изобразявани от сървъра, и стартовия пакет за валидация за локализирани съобщения за грешки.

Spring Boot автоматично конфигурира компонент MessageSource, който чете messages.properties от classpath. Изрична конфигурация е необходима само ако искате да промените базовото име, кодирането или поведението на кеша.
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

Конфигурирайте MessageSource и LocaleResolver

MessageSource на Spring зарежда преводите от файлове .properties според конвенция за базовото име: messages.properties (по подразбиране), messages_de.properties (немски), messages_ja.properties (японски). Конфигурирайте LocaleResolver, за да определяте кой локал да се използва за всяка заявка.

Файлове с преводи

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.

Конфигурация на 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;
    }
}
Ако вместо преведения текст се връща името на ключа, най-честата причина е неправилно базово име. По подразбиране то е 'messages', което съответства на messages.properties в classpath. Ако файловете Ви имат други имена или се намират в поддиректория, задайте изрично spring.messages.basename.

Определяне на локала

Конфигурирайте как Spring определя активния локал за всяка заявка. CookieLocaleResolver запазва избора на потребителя между сесиите. LocaleChangeInterceptor позволява на потребителите да сменят локала чрез параметър на заявката като ?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

Използвайте преводите в кода

Получавайте достъп до преведените съобщения в контролерите чрез инжектиране на MessageSource, в Thymeleaf шаблоните чрез синтаксиса #{...}, а в REST API — чрез автоматично определения параметър Locale.

Контролер с 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";
    }
}

Thymeleaf шаблони

Изразът #{...} на Thymeleaf автоматично намира ключовете на съобщенията във Вашите файлове .properties. Подавайте параметри чрез синтаксиса #{key(arg0, arg1)}. Шаблонът използва локала, определен от Вашия 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>
Изрази на Thymeleaf като #{greeting('World')} подават аргументи на MessageFormat. Статичният текст в HTML таговете служи като резервен вариант, когато шаблонът се преглежда без Spring — това е полезно за дизайнери, които работят директно по шаблоните.

Локализация на REST API

За REST API Spring автоматично определя Locale от заглавката Accept-Language. Инжектирайте го като параметър на метод и го подайте на MessageSource. Клиентите сменят езика, като изпращат различни заглавки 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!"}
}
REST API обикновено използват AcceptHeaderLocaleResolver (чрез заглавки), докато уеб приложенията използват CookieLocaleResolver (чрез бисквитки). Ако предоставяте и двете от едно приложение, помислете за персонализиран LocaleResolver, който първо проверява бисквитките и след това използва заглавката Accept-Language като резервен вариант.

Съобщения от Bean Validation

Spring автоматично намира съобщенията за ограниченията при валидация във Вашия MessageSource. Използвайте заместващи параметри с фигурни скоби като {validation.name.required} в анотациите за ограничения и определете преводите във Вашите файлове .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

Обработвайте множествено число и променливи

Spring използва java.text.MessageFormat за интерполация и множествено число. Шаблонът ChoiceFormat обработва основни правила за множествено число, но за пълна поддръжка на ICU формите (6 в арабския и 3 в руския) добавете библиотеката 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 не е равнозначен на правилата за множествено число на ICU. Той използва числови диапазони (0#, 1#, 1<), а не CLDR категории (zero, one, two, few, many, other). ChoiceFormat не е достатъчен за езици със сложни правила за множествено число като арабски, полски или руски — вместо него използвайте MessageFormat на ICU4J.

Автоматизирайте преводите

След като конфигурирате i18n, преведете своите .properties файлове с помощта на ИИ. Поискайте от асистента с ИИ във Вашата IDE да преведе изходния файл или използвайте i18n Agent CLI във Вашия 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
Превеждайте поетапно — когато добавяте нови ключове в messages.properties, превеждайте само тях, вместо да създавате отново всички файлове за отделните локали. Така се запазват всички преводи в съществуващите файлове, които вече са прегледани от човек.

Автоматизирайте контрола на качеството на превода

Откривайте липсващи ключове и повредени запазени места с i18n-validate, преди да достигнат до потребителите. Тествайте интерфейса си с псевдопреводи чрез i18n-pseudo, преди да получите истинските преводи.

Без конфигуриране със spring-locale-chain

spring-locale-chain е стартер с отворен код за Spring Boot, който автоматично конфигурира LocaleResolver, LocaleChangeInterceptor и проверката на поддържаните локали чрез една зависимост. Посочете поддържаните локали в application.yml, а библиотеката ще се погрижи за останалото.

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>

Препоръчителна структура на файловете

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

Често срещани затруднения

Знаците извън ASCII се показват неправилно

По подразбиране Java .properties файловете използват кодиране ISO-8859-1, а не UTF-8. Знаци като умлаут (ü) или знаци от CJK писменостите се показват неправилно. Решение: задайте spring.messages.encoding=UTF-8 в application.yml или използвайте Unicode екранирани последователности като \u00FC във Вашите .properties файлове. ReloadableResourceBundleMessageSource на Spring Boot използва UTF-8 по подразбиране, но ResourceBundleMessageSource не го прави.

ChoiceFormat не работи правилно с множествените форми на други езици

ChoiceFormat на Java ({0,choice,0#|1#|1<}) поддържа само числови диапазони — с него не могат да се изразят категориите за множествено число от CLDR, като 'few' или 'many'. Езици като арабски (6 форми), полски (3 форми) и руски (3 форми) изискват ICU4J за правилно образуване на множествено число. Не приемайте, че ChoiceFormat поддържа всички езици.

Промените в преводите не се отразяват

По подразбиране ResourceBundleMessageSource кешира пакетите за неограничено време. По време на разработката използвайте ReloadableResourceBundleMessageSource с cacheSeconds=0, за да виждате промените без рестартиране. В производствена среда задайте разумен срок на кеширане (например 3600 секунди), за да постигнете баланс между производителността и скоростта на обновяване.

Неочаквано преминаване към локала на JVM

По подразбиране Spring преминава към локала по подразбиране на JVM (Locale.getDefault()), а не към Вашия файл messages.properties. Задайте spring.messages.fallback-to-system-locale=false в application.yml, за да използвате винаги пакета по подразбиране. В противен случай, ако локалът на JVM на даден сървър е зададен на 'fr', при липсващ ключ в заявения локал ще се показва френски вместо английски.

Изпробвайте i18n Agent сега

Пуснете тук Вашия файл за превод

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

или натиснете, за да изберете файл

Целеви езици

Не се изисква регистрацияНезабавна оценка

Резервни локали със spring-locale-chain

Когато липсва ключ за превод в регионален локал като pt-BR, Spring Boot преминава направо към локала по подразбиране, вместо първо да провери родителския локал 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

Вижте нашето ръководство за резервни локали за пълния списък с поддържани платформи и 75 вградени вериги. Learn more →

Често задавани въпроси