一、核心概念

1.1 什么是 ?i18n

  • ?i18n 是 FreeMarker 提供的 国际化(Internationalization, i18n)内建函数,用于根据当前语言环境(Locale)从资源文件中查找并返回对应的本地化文本。
  • 它基于 Java 的 ResourceBundle 机制实现,支持多语言文本的动态加载与展示。

1.2 作用

  • 实现模板内容的多语言支持(如:中文、英文、日文等)。
  • 避免在模板中硬编码文本,提升可维护性与可扩展性。

1.3 原理

  • FreeMarker 在运行时通过 ResourceBundle.getBundle(baseName, locale) 加载对应的 .properties 文件。
  • ?i18n 接收一个 键(key),返回该键在当前语言环境下的翻译文本。

二、操作步骤(非常详细)

步骤 1:准备资源文件(.properties)

  1. 创建资源文件,命名格式为:baseName_language_country.properties

    • 示例:
      • messages_zh_CN.properties → 中文(简体)
      • messages_en_US.properties → 英文(美国)
      • messages_ja_JP.properties → 日文(日本)
      • messages.properties → 默认语言(通常为英文)
  2. 文件内容为键值对(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
    
  3. 将这些文件放在 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:动态切换语言(可选)

你可以在每个请求中动态设置 Configurationlocale,或为每个用户设置不同的 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)

四、注意事项

  1. 资源文件必须在 classpath 中,FreeMarker 才能通过 ResourceBundle 加载。
  2. 文件命名必须规范baseName_language_country.properties,否则无法识别。
  3. 默认语言文件(如 messages.properties)必须存在,作为兜底。
  4. 推荐使用 UTF-8 编码保存 .properties 文件,避免中文乱码。
  5. ❌ 不要在模板中硬编码语言,应由后端控制 locale
  6. ✅ 使用常量键(如 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>

六、最佳实践

  1. 统一资源文件管理:集中放在 i18n/messages/ 目录下。
  2. 使用分层键名:如 user.login.titleuser.profile.save,便于组织。
  3. 提供英文默认值messages.properties 作为主文件,其他语言翻译。
  4. 自动化翻译流程:结合工具(如 POEditor、Crowdin)导出/导入 .properties
  5. 前后端分离时:可将翻译文件预编译为 JSON,由前端处理(非 FreeMarker 场景)。

七、性能优化

  1. 缓存 ConfigurationTemplate 实例:不要每次请求都新建。
  2. 启用模板缓存(FreeMarker 默认开启):
    cfg.setTemplateCache(new freemarker.cache.MruCacheStorage(20, 250));
    
  3. 减少 ?i18n 调用次数:对频繁使用的文本,可在 Java 层预加载并放入 dataModel
  4. 避免在循环中频繁调用 ?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 中,可使用 FreeMarkerConfigurerLocaleResolver 实现自动语言切换。