Spring Boot i18n : บทเรียนการตั้งค่าการรองรับหลายภาษา
กำหนดค่า MessageSource สร้างไฟล์ properties เฉพาะภาษา ค้นหาภาษา และเรนเดอร์เทมเพลต Thymeleaf หลายภาษา จากนั้นทำให้การแปลเป็นอัตโนมัติด้วย AI
เพิ่มการพึ่งพา
Spring Boot Starter Web มีการกำหนดค่า MessageSource อัตโนมัติให้ทันที เพิ่ม Thymeleaf สำหรับเทมเพลต i18n ที่เรนเดอร์บนเซิร์ฟเวอร์ และเพิ่ม validation starter สำหรับข้อความแสดงข้อผิดพลาดฉบับโลคัลไลซ์
<!-- 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 ตามข้อกำหนด basename ได้แก่ 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 ด้วย AI โดยบอกผู้ช่วย AI ใน IDE ให้แปลไฟล์ต้นฉบับ หรือใช้ CLI ของ i18n Agent ในไปป์ไลน์ 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 เป็น starter โอเพนซอร์สของ 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 →