Skip to main content

Spring Boot i18n:國際化設定教程

設定 MessageSource、建立特定語言的 properties 檔案、解析語言並渲染多語言 Thymeleaf 範本,再利用 AI 自動翻譯。

1

新增依賴

Spring Boot Starter Web 開箱即用地包含 MessageSource 自動設定。新增 Thymeleaf 以渲染伺服器端 i18n 範本,再新增 validation starter 以本地化錯誤訊息。

Spring Boot 會自動設定一個 MessageSource bean,從 classpath 的 messages.properties 讀取內容。只有需要自訂基本名稱、編碼或快取行為時,才需顯式設定。
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

Spring 的 MessageSource 按基本名稱約定從 .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',對應 classpath 中的 messages.properties。如果檔案名稱不同或位於子目錄,請明確設定 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 傳入參數。在沒有 Spring 的情況下查看範本時,HTML 標籤內的靜態文字會作為回退,這對直接處理範本的設計師很有用。

REST API 本地化

對於 REST API,Spring 會自動從 Accept-Language 標頭解析 Locale。將其作為方法參數注入並傳給 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,而網頁應用程式使用基於 Cookie 的 CookieLocaleResolver。如果同一應用程式同時提供兩者,可考慮自訂 LocaleResolver,先檢查 Cookie,再回退到 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 不夠用,應改用 ICU4J 的 MessageFormat。

自動翻譯

完成 i18n 設定後,使用 AI 翻譯 .properties 檔案。在 IDE 中讓 AI 助手翻譯來源檔案,或在 CI/CD 管線中使用 i18n Agent CLI。

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 產生偽譯文來測試 UI。

通過 spring-locale-chain 實現零設定

spring-locale-chain 是一款開源 Spring Boot starter,只需一個依賴項,即可自動設定 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 字元等會顯示為亂碼。解決方法:在 application.yml 中設定 spring.messages.encoding=UTF-8,或在 .properties 檔案中使用類似 \u00FC 的 Unicode 轉義序列。Spring Boot 的 ReloadableResourceBundleMessageSource 預設使用 UTF-8,但 ResourceBundleMessageSource 並非如此。

ChoiceFormat 無法正確處理非英語複數

Java 的 ChoiceFormat({0,choice,0#|1#|1<})僅支援數字範圍,無法表達 'few' 或 'many' 等 CLDR 複數類別。阿拉伯語(6 種形式)、波蘭語(3 種形式)和俄語(3 種形式)等語言需要使用 ICU4J 才能正確處理複數。請勿假定 ChoiceFormat 能處理所有語言。

翻譯更改未生效

ResourceBundleMessageSource 預設會無限期快取資源包。在開發期間,使用 cacheSeconds=0 的 ReloadableResourceBundleMessageSource,無需重啟即可查看更改。在生產環境中,請設定合理的快取時長(例如 3,600 秒),以兼顧性能和更新速度。

意外回退到 JVM 地區設定

預設情況下,Spring 會回退到 JVM 的預設地區設定(Locale.getDefault()),而不是 messages.properties 檔案。在 application.yml 中設定 spring.messages.fallback-to-system-locale=false,即可始終使用預設資源包。否則,當請求的地區設定缺少某個鍵時,如果伺服器的 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 →

常見問題