JSP 国际化

fmt 标签库的 setLocale、setBundle、message,以及资源包组织。

Intermediate
Key Points把文案抽到 messages_xx.properties,页面用 fmt:message 取;数字和日期交给 fmt:formatXxx 按 Locale 处理。

资源包

Text
WEB-INF/classes/
├── messages.properties          # 默认(英文)
├── messages_zh_CN.properties    # 简体中文
└── messages_ja.properties       # 日文
PROPERTIES
# messages.properties
app.title=Demo App
nav.home=Home
nav.orders=Orders
welcome=Welcome, {0}!
cart.count=You have {0} items.
PROPERTIES
# messages_zh_CN.properties(UTF-8,需在 JSP 里指定 basename 的读取方式或用转义)
app.title=演示应用
nav.home=首页
nav.orders=订单
welcome=欢迎,{0}!
cart.count=购物车里有 {0} 件商品。

页面里使用

JSP
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>

<%-- 按浏览器 Accept-Language 自动选,或手动指定 --%>
<fmt:setLocale value="${param.lang != null ? param.lang : pageContext.request.locale}"/>
<fmt:setBundle basename="messages"/>

<!DOCTYPE html>
<html><head><title><fmt:message key="app.title"/></title></head>
<body>
  <nav>
    <a href="/"><fmt:message key="nav.home"/></a>
    <a href="/orders"><fmt:message key="nav.orders"/></a>
  </nav>
  <p><fmt:message key="welcome">
       <fmt:param value="${sessionScope.userName}"/>
     </fmt:message></p>
  <p><fmt:message key="cart.count">
       <fmt:param value="${fn:length(sessionScope.cart)}"/>
     </fmt:message></p>
</body></html>
Output
(Accept-Language: zh-CN)
演示应用
首页  订单
欢迎,张三!
购物车里有 3 件商品。

<fmt:param> fills {0}, {1} in order. The numeric placeholders need no escaping, matching MessageFormat.

中文 properties 的编码

JSP
<%-- 办法一:让 fmt:setBundle 按 UTF-8 读 --%>
<fmt:setBundle basename="messages" var="bundle"/>
<fmt:setLocale value="zh_CN"/>

<%-- 办法二:直接写 Unicode 转义(最兼容) --%>
<%-- welcome=\u6b22\u8fce\uff0c{0}\uff01 --%>

<%-- 办法三:用 ResourceBundle 显式按 UTF-8 读 --%>
<%
    try (java.io.Reader r = new java.io.InputStreamReader(
            application.getResourceAsStream("/WEB-INF/classes/messages_zh_CN.properties"),
            java.nio.charset.StandardCharsets.UTF_8)) {
        java.util.ResourceBundle rb = new java.util.PropertyResourceBundle(r);
        pageContext.setAttribute("msg", rb.getString("app.title"));
    }
%>

ResourceBundle reads .properties as ISO-8859-1 by default, garbling Chinese. Unicode escapes are the most compatible (works on old JDKs); an explicit Reader is the cleanest.

数字与日期

JSP
<fmt:setLocale value="zh_CN"/>
<fmt:formatNumber value="1234567.891" pattern="#,##0.00"/>
<fmt:formatNumber value="0.256" type="percent"/>
<fmt:formatNumber value="1234.5" type="currency"/>
<fmt:formatDate value="${now}" pattern="yyyy年M月d日"/>

<fmt:setLocale value="en_US"/>
<fmt:formatNumber value="1234.5" type="currency"/>
Output
1,234,567.89
26%
¥1,234.50
2026年11月3日
$1,234.50

type="currency" uses the locale's symbol, so switching locale changes the currency shown. If the currency is fixed by business rules (CNY only), hardcode the symbol instead of using currency.

语言切换

JSP
<%
    String lang = request.getParameter("lang");
    if (lang != null && lang.matches("[a-zA-Z_]{2,5}")) {
        session.setAttribute("jakarta.servlet.jsp.jstl.fmt.locale.session", lang);
        response.sendRedirect(request.getHeader("Referer"));
        return;
    }
%>
<a href="?lang=zh_CN">中文</a> | <a href="?lang=en_US">English</a>

JSTL stores the chosen locale in the session attribute jakarta.servlet.jsp.jstl.fmt.locale.session. Setting it switches every page in the session without passing a parameter around.

Translating strings is not internationalisation. Date order, currency, plural rules and even the cultural meaning of icons and colours all change. A messages.properties solves about a third of it.

Friends & Partners