Skip to main content

Spring Boot i18n: Tutorial Persediaan Pengantarabangsaan

Konfigurasikan MessageSource, cipta fail properties khusus bahasa, selesaikan bahasa, dan paparkan templat Thymeleaf berbilang bahasa—kemudian automatikkan terjemahan dengan AI.

1

Tambahkan Kebergantungan

Spring Boot Starter Web terus menyertakan konfigurasi automatik MessageSource. Tambahkan Thymeleaf untuk templat i18n yang dipaparkan pelayan dan starter pengesahan untuk mesej ralat setempat.

Spring Boot mengkonfigurasi bean MessageSource secara automatik untuk membaca messages.properties daripada classpath. Konfigurasi jelas hanya diperlukan jika anda mahu menyesuaikan basename, pengekodan, atau tingkah laku 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

Konfigurasikan MessageSource & LocaleResolver

MessageSource Spring memuatkan terjemahan daripada fail .properties menggunakan konvensyen basename: messages.properties (lalai), messages_de.properties (Jerman), messages_ja.properties (Jepun). Konfigurasikan LocaleResolver untuk menentukan bahasa yang digunakan bagi setiap permintaan.

Fail Terjemahan

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.

Konfigurasi 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;
    }
}
Jika terjemahan mengembalikan nama kekunci dan bukannya teks terjemahan, punca paling biasa ialah basename yang salah. Lalainya 'messages', yang dipetakan kepada messages.properties dalam classpath. Jika nama fail berbeza atau berada dalam subdirektori, tetapkan spring.messages.basename secara jelas.

Penyelesaian Bahasa

Konfigurasikan cara Spring menentukan bahasa aktif bagi setiap permintaan. CookieLocaleResolver menyimpan pilihan pengguna merentas sesi. LocaleChangeInterceptor membolehkan pengguna menukar bahasa melalui parameter pertanyaan seperti ?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

Gunakan Terjemahan dalam Kod

Akses mesej terjemahan dalam pengawal melalui suntikan MessageSource, dalam templat Thymeleaf dengan sintaks #{...}, dan dalam REST API menggunakan parameter Locale yang diselesaikan secara automatik.

Pengawal dengan 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";
    }
}

Templat Thymeleaf

Ungkapan #{...} Thymeleaf menyelesaikan kekunci mesej daripada fail .properties secara automatik. Teruskan parameter dengan sintaks #{key(arg0, arg1)}. Templat menggunakan bahasa yang diselesaikan oleh 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>
Ungkapan Thymeleaf seperti #{greeting('World')} meneruskan argumen kepada MessageFormat. Teks statik dalam teg HTML berfungsi sebagai sandaran apabila melihat templat tanpa Spring—berguna untuk pereka yang mengerjakan templat secara langsung.

Penyetempatan REST API

Untuk REST API, Spring menyelesaikan Locale daripada pengepala Accept-Language secara automatik. Suntikkannya sebagai parameter kaedah dan teruskan kepada MessageSource. Klien menukar bahasa dengan menghantar pengepala Accept-Language yang berbeza.

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 biasanya menggunakan AcceptHeaderLocaleResolver (berasaskan pengepala), manakala aplikasi web menggunakan CookieLocaleResolver (berasaskan kuki). Jika kedua-duanya disediakan daripada aplikasi yang sama, pertimbangkan LocaleResolver tersuai yang memeriksa kuki terlebih dahulu, kemudian beralih kepada pengepala Accept-Language.

Mesej Pengesahan Bean

Spring menyelesaikan mesej kekangan pengesahan daripada MessageSource secara automatik. Gunakan ruang letak kurungan keriting seperti {validation.name.required} dalam anotasi kekangan dan takrifkan terjemahannya dalam fail .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

Kendalikan Bentuk Jamak & Pemboleh Ubah

Spring menggunakan java.text.MessageFormat untuk interpolasi dan bentuk jamak. Corak ChoiceFormat mengendalikan peraturan bentuk jamak asas, tetapi untuk sokongan bentuk jamak ICU lengkap (6 bentuk bahasa Arab, 3 bentuk Rusia), tambahkan pustaka 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 tidak sama dengan peraturan bentuk jamak ICU. Format ini menggunakan julat berangka (0#, 1#, 1<), bukan kategori CLDR (zero, one, two, few, many, other). Untuk bahasa dengan peraturan bentuk jamak kompleks seperti Arab, Poland, atau Rusia, ChoiceFormat tidak mencukupi—gunakan MessageFormat ICU4J.

Automatikkan Terjemahan

Selepas persediaan i18n selesai, terjemah fail .properties dengan AI. Dalam IDE, minta pembantu AI menterjemah fail sumber atau gunakan CLI i18n Agent dalam saluran 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
Terjemah secara berperingkat—apabila menambahkan kekunci baharu pada messages.properties, terjemah hanya kekunci baharu dan bukannya menjana semula semua fail bahasa. Ini mengekalkan terjemahan yang telah disemak manusia dalam fail sedia ada.

Automatikkan Kualiti Terjemahan

Kesan kekunci hilang dan ruang letak rosak sebelum dikeluarkan dengan i18n-validate. Uji UI dengan terjemahan pseudo menggunakan i18n-pseudo sebelum terjemahan sebenar tersedia.

Tanpa Konfigurasi dengan spring-locale-chain

spring-locale-chain ialah starter Spring Boot sumber terbuka yang mengkonfigurasi LocaleResolver, LocaleChangeInterceptor, dan pengesahan bahasa yang disokong secara automatik dalam satu kebergantungan. Takrifkan bahasa yang disokong dalam application.yml dan pustaka mengendalikan selebihnya.

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>

Struktur Fail yang Disyorkan

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

Kesilapan Umum

Aksara Bukan ASCII Dipaparkan Bercelaru

Fail Java .properties menggunakan pengekodan ISO-8859-1 secara lalai, bukan UTF-8. Aksara seperti umlaut (ü) atau CJK dipaparkan bercelaru. Penyelesaian: tetapkan spring.messages.encoding=UTF-8 dalam application.yml atau gunakan pelepasan Unicode seperti \u00FC dalam fail .properties. ReloadableResourceBundleMessageSource Spring Boot menggunakan UTF-8 secara lalai, tetapi ResourceBundleMessageSource tidak.

ChoiceFormat Rosak untuk Bentuk Jamak Bukan Inggeris

ChoiceFormat Java ({0,choice,0#|1#|1<}) hanya menyokong julat berangka—format ini tidak boleh menyatakan kategori bentuk jamak CLDR seperti 'few' atau 'many'. Bahasa seperti Arab (6 bentuk), Poland (3 bentuk), dan Rusia (3 bentuk) memerlukan ICU4J untuk bentuk jamak yang betul. Jangan anggap ChoiceFormat mengendalikan semua bahasa.

Perubahan Terjemahan Tidak Ditunjukkan

ResourceBundleMessageSource menyimpan cache bundle tanpa batas secara lalai. Semasa pembangunan, gunakan ReloadableResourceBundleMessageSource dengan cacheSeconds=0 untuk melihat perubahan tanpa memulakan semula. Dalam pengeluaran, tetapkan tempoh cache yang munasabah (contohnya 3,600 saat) untuk mengimbangi prestasi dan kelajuan kemas kini.

Sandaran Tidak Dijangka kepada Bahasa JVM

Secara lalai, Spring beralih kepada bahasa lalai JVM (Locale.getDefault()), bukan fail messages.properties. Tetapkan spring.messages.fallback-to-system-locale=false dalam application.yml supaya sentiasa menggunakan bundle lalai. Jika tidak, pelayan dengan bahasa JVM 'fr' akan memaparkan bahasa Perancis, bukan Inggeris, apabila kekunci hilang dalam bahasa yang diminta.

Cuba i18n Agent Sekarang

Lepaskan fail terjemahan anda di sini

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

atau klik untuk semak imbas

Bahasa sasaran

Tidak perlu mendaftarAnggaran serta-merta

Sandaran Bahasa dengan spring-locale-chain

Apabila kekunci terjemahan tiada dalam bahasa serantau seperti pt-BR, Spring Boot terus beralih kepada bahasa lalai dan bukannya memeriksa bahasa induk pt terlebih dahulu.

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

Lihat Panduan Sandaran Bahasa kami untuk senarai lengkap rangka kerja yang disokong dan 75 rantaian terbina dalam. Learn more →

Soalan Lazim