Trong ứng dụng Spring Boot, việc **kết hợp sử dụng quốc tế hóa toàn cục (như tin nhắn giao diện/giao thức) và xác thực dữ liệu (Bean Validation)** là hoàn toàn khả thi và nên được thực hiện thông qua **quản lý tệp tài nguyên thống nhất** hoặc **cấu hình phân tách nhưng đồng bộ**. Vấn đề cốt lõi nằm ở việc hiểu cơ chế tải của từng thành phần và tổ chức tệp tài nguyên một cách hợp lý.
Dưới đây trình bày **hai phương án chính**, kèm ví dụ đầy đủ:
Phương án Một: Tệp tài nguyên tập trung (khuyến nghị)
Đặt **tin nhắn nghiệp vụ thông thường** và **tin nhắn lỗi xác thực** vào **cùng hệ thống `messages.properties`**, phân biệt bằng tiền tố khác nhau.
Bước 1: Thiết lập tệp quốc tế hóa tập trung
src/main/resources/
└── localization/
├── messages.properties # Mặc định
├── messages_zh_CN.properties
└── messages_en_US.properties
Nội dung tệp ví dụ:
# === Tin nhắn nghiệp vụ ===
greeting.message=Xin chào, {0}!
profile.modified=Cập nhật hồ sơ thành công.
# === Tin nhắn lỗi xác thực (sử dụng mã lỗi chuẩn Bean Validation) ===
javax.validation.constraints.NotNull.message=Không được để trống
javax.validation.constraints.Length.message=Chiều dài phải từ {min} đến {max}
org.hibernate.validator.constraints.Email.message=Định dạng email không hợp lệ
# === Tin nhắn xác thực tùy chỉnh ===
username.length=Tên người dùng phải có độ dài 3-15 ký tự
phone.format=Số điện thoại không hợp lệ
Ưu điểm: Chỉ cần duy trì một bộ tệp duy nhất, logic chuyển đổi ngôn ngữ thống nhất.
Bước 2: Cấu hình `MessageSource` (trỏ đến tệp tập trung)
// config/LocalizationConfig.java
@Configuration
public class LocalizationConfig {
@Bean
public MessageSource messageProvider() {
ResourceBundleMessageSource provider = new ResourceBundleMessageSource();
// Lưu ý: basename trỏ đến tệp tập trung (không bao gồm .properties)
provider.setBasename("localization/messages");
provider.setDefaultEncoding("UTF-8");
provider.setFallbackToSystemLocale(false);
return provider;
}
// Cấu hình LocaleResolver (hỗ trợ Accept-Language hoặc tham số chuyển đổi)
@Bean
public LocaleResolver localeHandler() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return resolver;
}
// Tùy chọn: hỗ trợ ?language=zh_CN chuyển đổi
@Bean
public LocaleChangeInterceptor languageInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("language");
return interceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(languageInterceptor());
}
}
Điểm mấu chốt:
LocalValidatorFactoryBeancủa Spring sẽ tự động sử dụngMessageSourcetrong container Spring để phân giải thuộc tínhmessagecủa các chú thích xác thực!Vì vậy, chỉ cần
MessageSourcecó thể tải được tệp tài nguyên chứa mã lỗi xác thực, quá trình quốc tế hóa xác thực sẽ hoạt động.
Bước 3: Định nghĩa DTO (sử dụng `{key}` tham chiếu tin nhắn)
public class UserProfileDto {
@NotNull(message = "{javax.validation.constraints.NotNull.message}")
@Length(min = 3, max = 15, message = "{username.length}")
private String username;
@Email(message = "{org.hibernate.validator.constraints.Email.message}")
private String contactEmail;
// getter/setter...
}
Cũng có thể sử dụng trực tiếp mã lỗi chuẩn (không cần khóa tùy chỉnh):
@NotNull // Sử dụng mặc định javax.validation.constraints.NotNull.message
private String username;
Bước 4: Controller sử dụng cả hai loại quốc tế hóa
@RestController
public class ProfileController {
@Autowired
private MessageSource messageProvider;
// 1. Quốc tế hóa xác thực tham số (tự động hoạt động)
@PostMapping("/profiles")
public String createProfile(@Valid @RequestBody UserProfileDto profile) {
return "completed";
}
// 2. Lấy thủ công tin nhắn nghiệp vụ quốc tế hóa
@GetMapping("/greeting")
public String greetUser(@RequestParam String userName, Locale locale) {
return messageProvider.getMessage("greeting.message", new Object[]{userName}, locale);
}
// Xử lý ngoại lệ toàn cục (tự động trả về lỗi xác thực quốc tế hóa)
@RestControllerAdvice
static class GlobalErrorProcessor {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity processValidation(
MethodArgumentNotValidException ex) {
List<String> errorList = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage) // ← Tự động quốc tế hóa!
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errorList);
}
}
}
Phương án Hai: Tệp tài nguyên phân tách (cách ly rõ ràng)
Nếu bạn muốn **phân tách nghiêm ngặt** giữa tin nhắn nghiệp vụ và tin nhắn xác thực:
resources/
├── localization/
│ ├── messages.properties # Tin nhắn nghiệp vụ
│ └── messages_zh_CN.properties
└── ValidationMessages.properties # Tin nhắn xác thực (theo quy tắc Bean Validation)
└── ValidationMessages_zh_CN.properties
Mô tả cấu hình:
ValidationMessages.properties: Được Hibernate Validator tự động tải (không cần cấu hình)messages.properties: Được SpringMessageSourcetải (cần cấu hình)
⚠ Lưu ý: Lúc này
MessageSourcechỉ chịu trách nhiệm cho tin nhắn nghiệp vụ, tin nhắn xác thực do Validator xử lý riêng.
Ưu điểm so với Nhược điểm:
| Phương án | Ưu điểm | Nhược điểm |
|---|---|---|
| Tệp tập trung | Dễ bảo trì, chuyển đổi ngôn ngữ đồng nhất | Tệp có thể trở nên lớn |
| Tệp phân tách | Trách nhiệm rõ ràng | Phải duy trì hai bộ tệp, logic chuyển đổi cần đồng bộ |
Khuyến nghị: Trừ khi dự án rất lớn, ngược lại nên ưu tiên phương án một (tệp tập trung).
Cấu hình xác nhận quan trọng (application.yml)
spring:
messages:
basename: classpath:localization/messages # Chỉ áp dụng cho phương án một
encoding: UTF-8
fallback-to-system-locale: false
# Tin nhắn xác thực không cần cấu hình bổ sung (Hibernate Validator tự đọc ValidationMessages)
Kiểm tra hiệu quả
Yêu cầu 1: Môi trường tiếng Trung
POST /profiles
Accept-Language: zh-CN
Content-Type: application/json
{"username": "", "contactEmail": "invalid-email"}
Phản hồi (lỗi xác thực):
["Không được để trống", "Định dạng email không hợp lệ"]
Yêu cầu 2: Môi trường tiếng Anh
GET /greeting?userName=Bob
Accept-Language: en-US
Phản hồi (tin nhắn nghiệp vụ):
Xin chào, Bob!
Vấn đề thường gặp và giải pháp
| Vấn đề | Nguyên nhân | Giải pháp |
|---|---|---|
| Tin nhắn xác thực chưa quốc tế hóa | Sử dụng message = "cố định" thay vì message = "{key}" |
Chuyển sang tham chiếu khóa tài nguyên |
| Hiển thị sai ký tự tiếng Trung | Tệp .properties không phải UTF-8 |
Thiết lập IDE mã hóa tệp là UTF-8 và bật **Chuyển đổi native-to-ascii trong suốt** |
| Ngôn ngữ không chuyển đổi | Chưa đăng ký LocaleChangeInterceptor |
Thêm bộ chặn và cấu hình tham số language |
| Tin nhắn xác thực vẫn là tiếng Anh | ValidationMessages_zh_CN.properties chưa được tải |
Kiểm tra tên tệp có phải là **ValidationMessages** (chữ cái đầu viết hoa!) |
Tổng kết thực hành tốt nhất
- Sử dụng thống nhất
messages.propertiesquản lý mọi văn bản (bao gồm tin nhắn xác thực) - Chú thích xác thực luôn dùng
message = "{your.key}" - Thực hiện chuyển đổi ngôn ngữ qua
Accept-Languagehoặc?language= - Bộ xử lý ngoại lệ toàn cục trả về lỗi xác thực thống nhất
- Đảm bảo tệp
.propertieslà UTF-8
Như vậy, ứng dụng Spring Boot của bạn có thể hỗ trợ đồng thời:
- Quốc tế hóa tin nhắn nghiệp vụ trả về giao thức (ví dụ:
"Thao tác thành công"/"Operation succeeded") - Quốc tế hóa lỗi xác thực tham số (ví dụ:
"Email không được để trống"/"Email must not be blank")