一、核心概念
1.1 什么是 ?i18n
?i18n
是 FreeMarker 提供的 国际化(Internationalization, i18n)内建函数,用于根据当前语言环境(Locale)从资源文件中查找并返回对应的本地化文本。- 它基于 Java 的
ResourceBundle
机制实现,支持多语言文本的动态加载与展示。
1.2 作用
- 实现模板内容的多语言支持(如:中文、英文、日文等)。
- 避免在模板中硬编码文本,提升可维护性与可扩展性。
1.3 原理
- FreeMarker 在运行时通过
ResourceBundle.getBundle(baseName, locale)
加载对应的.properties
文件。 ?i18n
接收一个 键(key),返回该键在当前语言环境下的翻译文本。
二、操作步骤(非常详细)
步骤 1:准备资源文件(.properties)
创建资源文件,命名格式为:
baseName_language_country.properties
- 示例:
messages_zh_CN.properties
→ 中文(简体)messages_en_US.properties
→ 英文(美国)messages_ja_JP.properties
→ 日文(日本)messages.properties
→ 默认语言(通常为英文)
- 示例:
文件内容为键值对(Key-Value),例如:
messages_zh_CN.properties
welcome.message=欢迎使用我们的服务! login.button=登录 error.required=该字段为必填项
messages_en_US.properties
welcome.message=Welcome to our service! login.button=Login error.required=This field is required
将这些文件放在 Java 类路径(classpath)下,如
src/main/resources/i18n/
。
步骤 2:配置 FreeMarker 环境(Java 代码)
在 Java 应用中配置 FreeMarker 时,需要设置 locale
和资源文件的 baseName
。
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.StringWriter;
import java.util.Locale;
import java.util.HashMap;
import java.util.Map;
public class I18nExample {
public static void main(String[] args) throws Exception {
// 1. 创建 FreeMarker 配置
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
// 2. 设置模板加载路径(可选)
cfg.setClassForTemplateLoading(I18nExample.class, "/templates/");
// 3. 设置默认语言环境(关键!)
cfg.setLocale(Locale.SIMPLIFIED_CHINESE); // 或 Locale.US 等
// 4. 设置资源文件基础名(不带语言后缀)
cfg.setLocalizedLookup(true); // 启用本地化查找(默认 true)
cfg.setNumberFormat("computer");
// 5. 获取模板
Template template = cfg.getTemplate("example.ftl");
// 6. 准备数据模型
Map<String, Object> dataModel = new HashMap<>();
// 7. 处理模板
StringWriter out = new StringWriter();
template.process(dataModel, out);
System.out.println(out.toString());
}
}
⚠️ 注意:
cfg.setLocale(...)
决定了当前请求使用哪种语言。
步骤 3:在 FreeMarker 模板中使用 ?i18n
创建模板文件 example.ftl
:
<!DOCTYPE html>
<html>
<head>
<title>${"page.title"?i18n}</title>
</head>
<body>
<h1>${"welcome.message"?i18n}</h1>
<button>${"login.button"?i18n}</button>
<!-- 带参数的国际化(使用 ?i18n with arguments) -->
<p>${"greeting.user"?i18n("张三")}</p>
<!-- 多个参数 -->
<p>${"order.info"?i18n("ORD123", "2025-07-28")}</p>
</body>
</html>
参数化支持说明:
- 如果你的资源文件中有:
greeting.user=你好,{0}! order.info=订单号 {0} 创建于 {1}
- FreeMarker 会自动使用
MessageFormat
格式化参数。
步骤 4:动态切换语言(可选)
你可以在每个请求中动态设置 Configuration
的 locale
,或为每个用户设置不同的 Template
实例。
// 伪代码:根据用户偏好设置语言
String userLang = getUserPreferredLanguage(); // 如 "zh-CN", "en-US"
Locale locale = Locale.forLanguageTag(userLang.replace("-", "_"));
cfg.setLocale(locale);
三、常见错误与解决方案
错误 | 原因 | 解决方案 |
---|---|---|
TemplateException: No message bundle found |
找不到 .properties 文件 |
检查文件路径、命名是否正确,是否在 classpath |
显示 key 而非翻译文本 |
键不存在或语言不匹配 | 确保 baseName 正确,检查 locale 设置 |
参数未替换(如显示 {0} ) |
资源文件中使用了 MessageFormat 但未传参 |
传入参数:"key"?i18n(arg1, arg2) |
乱码(中文显示为 ????) | 文件编码非 UTF-8 | 保存 .properties 文件为 UTF-8 编码,或使用 native2ascii 工具 |
?i18n 无效果 |
localizedLookup 被禁用 |
确保 cfg.setLocalizedLookup(true) |
四、注意事项
- ✅ 资源文件必须在 classpath 中,FreeMarker 才能通过
ResourceBundle
加载。 - ✅ 文件命名必须规范:
baseName_language_country.properties
,否则无法识别。 - ✅ 默认语言文件(如
messages.properties
)必须存在,作为兜底。 - ✅ 推荐使用 UTF-8 编码保存
.properties
文件,避免中文乱码。 - ❌ 不要在模板中硬编码语言,应由后端控制
locale
。 - ✅ 使用常量键(如
welcome.message
),避免拼写错误。
五、使用技巧
5.1 使用宏简化重复代码
<#macro t key, args=[]>
<#if args?size == 0>
${key?i18n}
<#else>
${key?i18n(args?to_seq)}
</#if>
</#macro>
<!-- 使用 -->
<@t "welcome.message" />
<@t "greeting.user" ["李四"] />
5.2 动态键名
<#assign key = "error." + errorCode />
${key?i18n}
5.3 检查键是否存在(避免异常)
<#if "some.key"?i18n??>
${"some.key"?i18n}
<#else>
Some Key Not Found
</#if>
六、最佳实践
- ✅ 统一资源文件管理:集中放在
i18n/
或messages/
目录下。 - ✅ 使用分层键名:如
user.login.title
、user.profile.save
,便于组织。 - ✅ 提供英文默认值:
messages.properties
作为主文件,其他语言翻译。 - ✅ 自动化翻译流程:结合工具(如 POEditor、Crowdin)导出/导入
.properties
。 - ✅ 前后端分离时:可将翻译文件预编译为 JSON,由前端处理(非 FreeMarker 场景)。
七、性能优化
- ✅ 缓存
Configuration
和Template
实例:不要每次请求都新建。 - ✅ 启用模板缓存(FreeMarker 默认开启):
cfg.setTemplateCache(new freemarker.cache.MruCacheStorage(20, 250));
- ✅ 减少
?i18n
调用次数:对频繁使用的文本,可在 Java 层预加载并放入dataModel
。 - ✅ 避免在循环中频繁调用
?i18n
:<#-- ❌ 不推荐 --> <#list items as item> <p>${"item.label"?i18n}: ${item.name}</p> </#list> <#-- ✅ 推荐:提前获取 --> <#assign label = "item.label"?i18n> <#list items as item> <p>${label}: ${item.name}</p> </#list>
八、总结
项目 | 说明 |
---|---|
核心功能 | 多语言文本动态加载 |
关键配置 | cfg.setLocale() 、资源文件路径 |
模板语法 | ${"key"?i18n} 、${"key"?i18n(arg1, arg2)} |
最佳实践 | 分层键名、UTF-8 编码、宏封装 |
性能建议 | 缓存模板、减少重复调用 |
通过以上详细步骤与技巧,你可以快速掌握 FreeMarker 的 ?i18n
功能,并在实际项目中高效实现国际化支持。建议结合 Spring Boot 等框架使用,可进一步简化配置。
📌 提示:在 Spring Boot 中,可使用
FreeMarkerConfigurer
和LocaleResolver
实现自动语言切换。