Servlet 国际化
Locale 检测、ResourceBundle 资源包、格式化数字与日期。
Key Points按 Accept-Language 选 Locale,从对应语言的 properties 文件取文案;代码里不出现任何硬编码文字。
三件事
| Area | Mechanism |
|---|---|
| Text | ResourceBundle + messages_xx.properties |
| Numbers | NumberFormat 按 Locale 加千分位 |
| Dates | DateTimeFormatter.ofLocalizedDate |
资源包
Text
src/main/resources/
├── messages.properties # 默认(英文)
├── messages_zh.properties # 中文
└── messages_ja.properties # 日文PROPERTIES
# messages.properties
greeting=Hello, {0}!
items=You have {0} items in the cart.PROPERTIES
# messages_zh.properties(注意:properties 默认按 ISO-8859-1 读,中文要转义或用 UTF-8 读取)
greeting=\u4f60\u597d\uff0c{0}\uff01
items=\u8d2d\u7269\u8f66\u91cc\u6709 {0} \u4ef6\u5546\u54c1\u3002在 Servlet 里用
Java
@WebServlet("/greet")
public class GreetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
Locale locale = req.getLocale(); // 解析 Accept-Language 得到最匹配的
ResourceBundle rb = ResourceBundle.getBundle("messages", locale);
String name = req.getParameter("name");
if (name == null) name = "Guest";
String text = MessageFormat.format(rb.getString("greeting"), name);
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().println(text);
}
}Output
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
→ 你好,Guest!
Accept-Language: en-US,en;q=0.9
→ Hello, Guest!getLocale() returns the best match computed from the Accept-Language q-weights; getLocales() gives the full preference list for multi-level fallback.
中文 properties 的坑
getBundle decodes .properties as ISO-8859-1, so raw Chinese garbles. Either escape as \uXXXX (as above) or wrap a UTF-8 Reader in a PropertyResourceBundle.
Java
try (Reader r = Files.newBufferedReader(
Path.of("messages_zh.properties"), StandardCharsets.UTF_8)) {
ResourceBundle rb = new PropertyResourceBundle(r);
System.out.println(rb.getString("greeting"));
}格式化数字与日期
Java
Locale zh = Locale.SIMPLIFIED_CHINESE;
Locale us = Locale.US;
System.out.println(NumberFormat.getNumberInstance(zh).format(1234567.891));
System.out.println(NumberFormat.getNumberInstance(us).format(1234567.891));
System.out.println(NumberFormat.getCurrencyInstance(zh).format(1234.5));Output
1,234,567.891
1,234,567.891
¥1,234.50Internationalisation is more than translating strings: number formats, date order, currency symbols, plural rules and even colour and icon connotations all change. Extracting strings to properties is only about a third of the job.
三件事
| 方面 | 机制 |
|---|---|
| 文案 | ResourceBundle + messages_xx.properties |
| 数字 | NumberFormat 按 Locale 加千分位 |
| 日期 | DateTimeFormatter.ofLocalizedDate |
资源包
Text
src/main/resources/
├── messages.properties # 默认(英文)
├── messages_zh.properties # 中文
└── messages_ja.properties # 日文PROPERTIES
# messages.properties
greeting=Hello, {0}!
items=You have {0} items in the cart.PROPERTIES
# messages_zh.properties(注意:properties 默认按 ISO-8859-1 读,中文要转义或用 UTF-8 读取)
greeting=\u4f60\u597d\uff0c{0}\uff01
items=\u8d2d\u7269\u8f66\u91cc\u6709 {0} \u4ef6\u5546\u54c1\u3002在 Servlet 里用
Java
@WebServlet("/greet")
public class GreetServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
Locale locale = req.getLocale(); // 解析 Accept-Language 得到最匹配的
ResourceBundle rb = ResourceBundle.getBundle("messages", locale);
String name = req.getParameter("name");
if (name == null) name = "Guest";
String text = MessageFormat.format(rb.getString("greeting"), name);
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().println(text);
}
}Output
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
→ 你好,Guest!
Accept-Language: en-US,en;q=0.9
→ Hello, Guest!getLocale() 返回的是容器按 Accept-Language 的 q 权重算出的最匹配值。getLocales() 能拿到完整偏好列表,做多级回退时用得上。
中文 properties 的坑
ResourceBundle.getBundle 默认按 ISO-8859-1 解码 .properties。直接写中文会变乱码。两个办法:用 native2ascii 式的 \uXXXX 转义(如上),或者自己用 PropertyResourceBundle 包一个 UTF-8 的 Reader。
Java
try (Reader r = Files.newBufferedReader(
Path.of("messages_zh.properties"), StandardCharsets.UTF_8)) {
ResourceBundle rb = new PropertyResourceBundle(r);
System.out.println(rb.getString("greeting"));
}格式化数字与日期
Java
Locale zh = Locale.SIMPLIFIED_CHINESE;
Locale us = Locale.US;
System.out.println(NumberFormat.getNumberInstance(zh).format(1234567.891));
System.out.println(NumberFormat.getNumberInstance(us).format(1234567.891));
System.out.println(NumberFormat.getCurrencyInstance(zh).format(1234.5));Output
1,234,567.891
1,234,567.891
¥1,234.50国际化不只是翻译文案。数字格式、日期顺序、货币符号、复数形式、甚至颜色和图标的文化含义都要跟着变。只把字符串抽到 properties 里,通常只完成了三成。