适用版本:FreeMarker 2.3+
核心机制:基于 Java 的ResourceBundle
+<#ftl>
指令 +?i18n
内建函数
目标:实现模板层面的语言切换,支持中文、英文、日文等多语言
一、核心概念
概念 | 说明 |
---|---|
<#ftl locale="zh_CN"> |
在模板中声明默认语言环境(Locale),影响后续 ?i18n 行为 |
?i18n 内建函数 |
用于从 .properties 文件中获取对应语言的文本 |
ResourceBundle |
Java 标准的国际化资源加载机制,FreeMarker 基于此实现 i18n |
messages_zh_CN.properties |
中文资源文件(UTF-8 编码) |
messages_en_US.properties |
英文资源文件 |
?locale |
获取当前模板或数据模型中的 Locale 设置 |
📌 关键理解:
- FreeMarker 本身不“翻译”,而是通过
?i18n
读取外部.properties
文件中的键值对。 <#ftl locale="...">
设置模板的默认 Locale,可被运行时传入的 Locale 覆盖。
二、操作步骤(超详细,适合快速上手)
步骤 1:准备多语言资源文件(.properties
)
在 src/main/resources/i18n/
目录下创建:
文件 1:messages.properties
(默认语言,如英文)
# Default (en_US)
welcome=Welcome
login=Login
logout=Logout
language=Language
title=My Website
error.required=This field is required.
文件 2:messages_zh_CN.properties
(简体中文)
# zh_CN
welcome=欢迎
login=登录
logout=退出
language=语言
title=我的网站
error.required=该字段为必填项。
文件 3:messages_ja_JP.properties
(日文)
# ja_JP
welcome=ようこそ
login=ログイン
logout=ログアウト
language=言語
title=私のウェブサイト
error.required=このフィールドは必須です。
✅ 注意:
- 文件名必须为
basename + _ + language + _ + country
(如messages_zh_CN
)- 所有文件必须使用 UTF-8 编码(建议用 IDE 设置或转码工具)
- 键(key)保持一致,值(value)翻译不同
步骤 2:配置 FreeMarker Configuration
支持 i18n
import freemarker.template.Configuration;
import java.util.Locale;
@Configuration
public class FreeMarkerConfig {
@Bean
public Configuration freemarkerConfiguration() {
Configuration config = new Configuration(Configuration.VERSION_2_3_31);
config.setClassForTemplateLoading(getClass(), "/templates");
config.setDefaultEncoding("UTF-8");
// 设置默认 Locale(可选)
config.setLocale(Locale.CHINA); // 默认 zh_CN
return config;
}
}
🔑 说明:
setLocale()
设置全局默认语言- 实际语言可在请求时动态传入(见步骤 4)
步骤 3:在模板中使用 <#ftl locale="...">
和 ?i18n
示例模板:home.ftl
<#ftl locale="zh_CN"> <!-- 声明模板默认语言,可被运行时覆盖 -->
<!DOCTYPE html>
<html lang="${currentLocale?replace('_', '-')}">
<head>
<meta charset="UTF-8">
<title>${"title"?i18n}</title>
</head>
<body>
<h1>${"welcome"?i18n}</h1>
<p>
<a href="?lang=en_US">${"language"?i18n}: English</a> |
<a href="?lang=zh_CN">${"language"?i18n}: 中文</a> |
<a href="?lang=ja_JP">${"language"?i18n}: 日本語</a>
</p>
<form>
<label>${"username"?i18n}:</label>
<input type="text" name="username">
<span class="error">${"error.required"?i18n}</span>
<br>
<button type="submit">${"login"?i18n}</button>
</form>
<footer>${"copyright"?i18n}</footer>
</body>
</html>
✅ 关键语法:
"key"?i18n
:从资源文件中查找对应语言的文本<#ftl locale="zh_CN">
:设置模板默认语言(可省略,由 Java 传入决定)
步骤 4:在 Java 后端动态设置语言(Spring Boot 示例)
@Controller
public class I18nController {
@Autowired
private Configuration freemarkerConfig;
@GetMapping("/")
public String index(
@RequestParam(value = "lang", required = false) String lang,
Model model,
HttpServletRequest request) throws IOException, TemplateException {
// 1. 解析 Locale
Locale locale = Locale.CHINA; // 默认中文
if (lang != null && !lang.isEmpty()) {
String[] parts = lang.split("_");
if (parts.length == 2) {
locale = new Locale(parts[0], parts[1]);
}
}
// 2. 将 locale 传入模板
model.addAttribute("currentLocale", locale.toString());
// 3. 渲染模板(FreeMarker 会自动使用传入的 locale 进行 ?i18n 查找)
Template template = freemarkerConfig.getTemplate("home.ftl");
StringWriter out = new StringWriter();
model.addAttribute("locale", locale); // 关键:传入 locale
template.process(model.asMap(), out);
model.addAttribute("content", out.toString());
return "base";
}
}
🔑 说明:
- FreeMarker 在执行
"key"?i18n
时,会自动使用当前上下文的Locale
- 优先级:
model.get("locale")
><#ftl locale="...">
>Configuration.setLocale()
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
Missing bundle for xxx |
资源文件未找到或命名错误 | 检查文件名、路径、拼写(如 messages_zh_CN.properties ) |
显示 key 而非翻译文本(如 welcome ) |
键不存在或文件编码错误 | 确认 .properties 文件中包含该 key,使用 UTF-8 保存 |
中文乱码 | .properties 文件非 UTF-8 |
使用 IDE 转码或 native2ascii 工具处理 |
<#ftl locale="zh_CN"> 不生效 |
被 Java 传入的 locale 覆盖 | 检查是否在 model 中传入了 locale ,或移除它 |
?i18n 报错 |
FreeMarker 版本过低 | 升级到 2.3.20+ |
四、注意事项
.properties
文件必须 UTF-8 编码:Java 默认用 ISO-8859-1,FreeMarker 需显式支持 UTF-8。- 资源文件路径必须在 classpath 中:如
src/main/resources/i18n/
。 <#ftl locale="...">
是模板级默认值,可被运行时传入的locale
覆盖。?i18n
只查找basename
相同的资源:需在Configuration
中设置setI18nBasename("i18n/messages")
。- 避免在 block 中使用
?i18n
导致继承问题:确保父/子模板语言一致。
五、使用技巧
1. 设置全局资源基名(推荐)
config.setI18nBasename("i18n/messages"); // 自动加载 i18n/messages_zh_CN.properties
无需在模板中指定路径,
?i18n
自动查找。
2. 使用 ${text?i18n}
动态 key
<#assign field = "username">
<label>${(field + ".label")?i18n}:</label> <!-- 转换为 "username.label"?i18n -->
3. 提供 fallback 机制(安全)
${("button.save"?i18n)! "Save"} <!-- 如果 key 不存在,显示默认值 -->
4. 在宏中使用 i18n
<#macro inputField name>
<label>${(name + ".label")?i18n}:</label>
<input type="text" name="${name}">
</#macro>
<@inputField name="username" />
六、最佳实践
实践 | 说明 |
---|---|
✅ 统一资源文件命名 | 使用 messages_{lang}_{country}.properties |
✅ 集中管理 keys | 建立 key 文档,避免重复或拼写错误 |
✅ 使用英文作为默认语言文件 | messages.properties 作为 fallback |
✅ 避免在模板中硬编码文本 | 所有文本都应通过 ?i18n 获取 |
✅ 支持浏览器 Accept-Language | 自动检测用户语言偏好 |
✅ 提供语言切换 UI | 如顶部语言选择器 |
七、性能优化建议
启用资源缓存(默认开启):
config.setLocalizedLookup(true); // 启用本地化查找缓存
避免频繁加载资源文件:FreeMarker 会缓存
ResourceBundle
,无需手动管理。减少
?i18n
调用次数:对频繁使用的文本,可提前放入 model:model.addAttribute("btnLogin", "login".i18n(locale)); // Java 层翻译
使用 CDN 或静态化:对多语言页面,可生成静态 HTML 缓存。
异步加载语言包(高级):在 SPA 场景下,可前端动态加载 JSON 语言包。
八、总结:快速掌握要点
要点 | 一句话总结 |
---|---|
资源文件 | messages_zh_CN.properties ,UTF-8 编码 |
<#ftl locale="..."> |
设置模板默认语言(可被覆盖) |
?i18n |
从 .properties 中获取翻译文本 |
setI18nBasename() |
指定资源文件基路径 |
model.addAttribute("locale", ...) |
运行时动态切换语言 |
编码问题 | 所有 .properties 必须 UTF-8 |
✅ 一句话掌握精髓:
资源文件定翻译,
?i18n
取文本,<#ftl>
设默认,Java 传 locale 真切换。
通过以上完整流程,你已具备在 FreeMarker 项目中实现多语言支持的能力。建议动手实践:创建 messages_zh_CN.properties
和 messages_en_US.properties
,配合 <#ftl locale="zh_CN">
和 ?i18n
,快速验证效果。