Skip to content

国际化 (i18n)

ACE Framework 内置轻量国际化模块,提供消息文件加载、语言协商中间件、占位符替换等核心能力,无需引入外部依赖。

消息文件格式

消息文件采用 key=value 纯文本格式,一行一条,# 开头为注释:

# messages/zh.txt
greeting=你好,{0}!
order.created=订单 {0} 已创建,共 {1} 件商品
error.notFound=找不到资源:{0}
# messages/en.txt
greeting=Hello, {0}!
order.created=Order {0} created with {1} item(s)
error.notFound=Resource not found: {0}

{0}{1} 等为位置占位符,按 args 数组下标替换。

推荐目录结构

config/
  messages/
    zh.txt       # 简体中文(默认)
    zh-TW.txt    # 繁体中文
    en.txt       # 英文
    ja.txt       # 日文

也可按模块拆分,在应用启动时合并加载:

config/messages/
  order/
    zh.txt
    en.txt
  user/
    zh.txt
    en.txt

初始化配置

在应用启动入口加载消息文件并设置默认语言:

cangjie
import ace_framework.i18n.*
import std.fs.*

func main(): Unit {
    // 加载消息文件(locale 标识 + 文件内容字符串)
    let zh = File.readText("config/messages/zh.txt")
    let en = File.readText("config/messages/en.txt")

    loadMessages("zh", zh)
    loadMessages("en", en)
    setDefaultLocale("zh")  // 找不到匹配语言时回退到此

    let app = App()
    // 挂载语言协商中间件(建议放在最前)
    app.use(localeMiddleware())
    app.use(router.routes())
    listen(app, "0.0.0.0", 8080)
}

API 参考

函数签名说明
loadMessages(locale: String, text: String): Unit解析并注册一个语言包
setDefaultLocale(locale: String): Unit设置默认/回退语言
t(locale: String, key: String, args: Array<String>): String翻译并替换占位符
localeMiddleware(): MiddlewareAccept-Language 头自动解析语言

翻译函数 t()

cangjie
import ace_framework.i18n.*

// 无占位符
let msg = t("zh", "greeting", ["张三"])
// => "你好,张三!"

// 多个占位符
let msg2 = t("en", "order.created", ["ORD-001", "3"])
// => "Order ORD-001 created with 3 item(s)"

// key 不存在时返回 key 本身(不抛异常)
let msg3 = t("zh", "unknown.key", [])
// => "unknown.key"

回退策略

当请求语言包中找不到对应 key 时,ACE 会先尝试默认语言包,再返回 key 字符串本身,保证页面不会出现空白。

语言协商中间件

localeMiddleware() 解析请求头 Accept-Language,将语言标识写入 ctx.state["ace.locale"],后续处理器直接读取即可:

cangjie
@Controller["/api"]
class ProductController {
    @Get["/products/{id}"]
    func getProduct(@PathParam id: String, ctx: Context): JsonResponse {
        let locale = ctx.state["ace.locale"] as? String ?? "zh"
        let name = productService.getName(id, locale)
        let msg = t(locale, "product.detail", [name])
        return json({"message": msg, "id": id})
    }
}

中间件语言匹配规则:

  1. 精确匹配 Accept-Language 首选项(如 zh-CNzh-CN
  2. 语言前缀匹配(如 zh-CNzh
  3. 回退到 setDefaultLocale 设置的默认语言

语言标识大小写

loadMessagessetDefaultLocale 的 locale 字符串区分大小写,建议统一使用小写(zhenzh-tw),并确保与消息文件名保持一致。

完整示例

下面展示一个支持中英文切换的完整 API 服务:

cangjie
package example

import ace_framework.*
import ace_framework.i18n.*
import ace_web.*
import std.fs.*

// 启动入口
func main(): Unit {
    loadMessages("zh", File.readText("config/messages/zh.txt"))
    loadMessages("en", File.readText("config/messages/en.txt"))
    setDefaultLocale("zh")

    let app = App()
    let router = Router()
    app.use(localeMiddleware())

    router.get("/hello") { ctx =>
        let locale = ctx.state["ace.locale"] as? String ?? "zh"
        let name = ctx.query("name") ?? "访客"
        ctx.body = t(locale, "greeting", [name])
    }

    app.use(router.routes())
    listen(app, "0.0.0.0", 8080)
}
bash
# 请求中文(默认)
curl http://localhost:8080/hello?name=小明
# => 你好,小明!

# 请求英文
curl -H "Accept-Language: en" http://localhost:8080/hello?name=Tom
# => Hello, Tom!

# 请求不支持的语言,自动回退到默认语言
curl -H "Accept-Language: fr" http://localhost:8080/hello?name=Pierre
# => 你好,Pierre!

与 @Service 集成

在 Service 层同样可以调用 t() 函数,但需由 Controller 层将 locale 字符串传入,避免 Service 直接读取 HTTP 上下文,保持层次清晰。

基于 Apache-2.0 许可证发布