@bams-app/i18n¶
📄 创建: 刘泽伟 2026-08-04
面向 BAMS monorepo 的运行时国际化包,重点解决这几类问题:
- 应用运行时切换语言,不刷新页面
- 统一同步
Ant Design Vue、DevExtreme、dayjs的语言 - 支持业务组件按模块拆分语言包,避免全量聚合
- 支持局部翻译和纯 JS 翻译场景
一句话能力图¶
- 应用级管理:
createI18nManager()负责语言状态、模块注册、缓存、持久化和 fallback - 模块级拆分:
defineI18nModule()/defineBusinessI18n()支持同步消息和异步 loader - 组件级接入:
attachI18n()+useComponentI18n()让业务组件接入足够轻 - 局部翻译:
useLocalI18n()/LocalI18nProvider/useLocalI18nProvided()适合弹窗、子模块、临时文案域 - 非 Vue 场景:
translate()、translateScoped()、createScopedTranslator()、createLocalTranslator() - 体验增强:语言持久化、别名标准化、函数式翻译、双插值语法、语言变更监听
目录架构¶
src/
├── index.js # 包级对外统一出口
├── core/ # 核心能力层
│ ├── constants.js # 常量定义(默认语言、支持列表、别名、注入 Symbol 等)
│ ├── manager.js # i18n 管理器核心(状态、缓存、语言切换、模块注册等)
│ ├── composables.js # Vue 组件侧组合式 API(useLocale、useScopedI18n、useI18nContext)
│ ├── builtin.js # 内建模块:Antd / DevExtreme / dayjs 语言适配器
│ ├── utils.js # 通用工具函数(对象合并、路径取值、消息格式化等)
│ └── dayjs-locale.d.ts # dayjs 类型声明
├── components/ # UI 组件层
│ ├── I18nProvider.vue # Ant Design Vue ConfigProvider 包裹组件
│ ├── LocaleSwitchPanel.vue # 语言切换面板组件
│ └── LocalI18nProvider.vue # 局部翻译容器组件
├── business/ # 业务组件接入层
│ └── business.js # 业务组件简化接入(defineBusinessI18n、attachI18n、useComponentI18n、useLocalI18n)
└── internal/ # 内部实现层
├── local-i18n.js # 局部翻译共享核心逻辑
├── module.js # i18n 模块定义与 bundle 解析、解包、fallback 处理
├── locale-resources.js # 语言资源聚合与三方库适配应用
├── storage.js # localStorage 持久化读写
└── scope.js # 命名空间路径拼接
架构分层说明¶
| 层级 | 目录/文件 | 职责 |
|---|---|---|
| 入口层 | index.js |
统一导出,保持对外 API 稳定 |
| 业务接入层 | business/ |
业务组件简化接入、命名空间管理 |
| UI 组件层 | components/ |
语言切换、局部翻译容器等 UI 组件 |
| 核心能力层 | core/ |
通用组合式 API、管理器、工具函数 |
| 内部实现层 | internal/ |
模块解析、资源聚合、局部翻译核心等 |
管理器 API¶
实例方法:
| 方法 | 说明 |
|---|---|
init() |
初始化,读取持久化语言并完成首次资源加载 |
setLocale(locale, options?) |
切换语言 |
loadLocale(locale) |
预加载指定语言资源 |
registerModule(moduleOptions) |
注册单个模块 |
registerModules(modules) |
批量注册模块 |
unregisterModule(moduleName) |
卸载单个模块(内建模块不可卸载) |
unregisterModules(moduleNames) |
批量卸载模块 |
getRegisteredModules() |
获取当前已注册的所有模块名 |
getLocaleMessages(locale?) |
获取聚合后的消息对象 |
onLocaleChange(listener) |
监听语言切换 |
t(path, params?, locale?) |
翻译指定路径 |
te(path, locale?) |
判断路径是否存在翻译 |
实例属性:
| 属性 | 说明 |
|---|---|
state |
只读响应式状态,含 locale、loading、ready、availableLocales 等 |
antdLocale |
当前 Antd locale 引用 |
Vue 全局属性¶
安装 app.use(i18n) 后会自动挂载:
| 属性 | 说明 |
|---|---|
$bamsI18n |
i18n 管理器实例 |
$t |
全局翻译函数 |
$locale |
当前语言 |
常用组件¶
LocaleSwitchPanel¶
开箱即用的语言切换组件。
<LocaleSwitchPanel />
<LocaleSwitchPanel :locales="['zh-CN', 'en-US']" :locale-labels="{ 'zh-CN': '中文', 'en-US': 'English' }" />
Props:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
locales |
显示哪些语言 | Array |
使用管理器中的 availableLocales,为空时退回默认支持列表 |
localeLabels |
自定义语言显示名称 | Object |
内置中文 / English |
LocalI18nProvider¶
为一个局部区域提供独立翻译上下文。
<template>
<LocalI18nProvider :messages="localMessages" namespace="myModule">
<template #default="{ t }">
<div>{{ t("title") }}</div>
</template>
</LocalI18nProvider>
</template>
Props:
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
messages |
局部消息字典 | Object |
{} |
namespace |
局部命名空间,不传时继承父业务组件 namespace | String |
null |
fallbackToGlobal |
局部缺失时是否回退到全局 | Boolean |
true |
使用方式¶
1. 应用级初始化¶
适用场景:整站、平台、工作台根应用。
import { createI18nManager } from "@bams-app/i18n";
const i18n = createI18nManager({
// 默认语言
locale: "zh-CN",
// 兜底语言(当前语言找不到翻译时使用)
fallbackLocale: "zh-CN",
// 是否持久化到 localStorage
persist: true,
// 自定义存储 key
storageKey: "my-app-locale",
// 初始化时预注册的模块
modules: [],
// 可用语言列表(会动态扩展)
availableLocales: ["zh-CN", "en-US"],
});
app.use(i18n);
await i18n.init();
推荐再用 I18nProvider 包一下根视图,保证 Antd 文案同步刷新:
<template>
<I18nProvider>
<router-view />
</I18nProvider>
</template>
<script setup>
import { I18nProvider } from "@bams-app/i18n";
</script>
2. 注册 i18n 模块¶
适用场景:基础组件、平台级模块、不会依赖业务容器自动注册的包。
模块定义(同步 + 异步混合模式)¶
import { defineI18nModule } from "@bams-app/i18n";
export const demoI18nModule = defineI18nModule({
name: "demo-module",
namespace: "demoModule",
// 同步消息(启动时立即加载)
messages: {
"zh-CN": {
common: {
confirm: "确定",
cancel: "取消",
},
},
"en-US": {
common: {
confirm: "Confirm",
cancel: "Cancel",
},
},
},
// 异步加载器(按需加载,减少首屏体积)
loaders: {
"zh-CN": () => import("./locales/zh-CN.js"),
"en-US": () => import("./locales/en-US.js"),
},
});
动态注册模块¶
import { demoI18nModule } from "./i18n.js";
// 单个注册
await i18n.registerModule(demoI18nModule);
// 批量注册
await i18n.registerModules([module1, module2]);
在 Vue 组件中使用¶
import { useScopedI18n } from "@bams-app/i18n";
// 带命名空间的翻译
const { t, te, locale, setLocale } = useScopedI18n("demoModule");
// 使用:t("common.confirm") 等价于 t("demoModule.common.confirm")
console.log(t("common.confirm"));
全局翻译(不带命名空间)¶
import { useLocale } from "@bams-app/i18n";
const { t, te } = useLocale();
// 使用完整路径
console.log(t("demoModule.common.confirm"));
3. 业务组件极简接入¶
适用场景:
ui-xxx业务组件page-xxx页面壳
推荐组合:
defineBusinessI18nattachI18nuseComponentI18n
组件入口文件(index.js)¶
import ReportTeam from "./ReportTeam.vue";
import { defineBusinessI18n, attachI18n } from "@bams-app/i18n";
// 定义业务模块(自动将 kebab-case 包名转成 camelCase namespace)
const i18nModule = defineBusinessI18n({
name: "ui-report-team",
loaders: {
"zh-CN": () => import("./locales/zh-CN.js"),
"en-US": () => import("./locales/en-US.js"),
},
});
// 自动挂载(生命周期管理 + namespace 注入)
export default attachI18n(ReportTeam, i18nModule);
组件内部使用¶
<script setup>
import { useComponentI18n } from "@bams-app/i18n";
// 自动继承 namespace,无需手动指定
const { t } = useComponentI18n();
// 直接使用相对路径
console.log(t("toolbar.export"));
</script>
子孙组件使用(自动继承 namespace)¶
<script setup>
import { useComponentI18n } from "@bams-app/i18n";
// 直接获取父组件的 namespace
const { t } = useComponentI18n();
</script>
完整指南见:guide.md
4. 局部翻译¶
适用场景:
- 一个页面中的局部模块需要自带独立字典
- 弹窗、配置面板、临时子流程不想污染全局命名空间
- 希望局部优先,找不到时再回退到全局
方式一:useLocalI18n(当前组件内使用)¶
<script setup>
import { useLocalI18n } from "@bams-app/i18n";
// 可直接调用的翻译函数
const t = useLocalI18n({
messages: {
"zh-CN": {
title: "局部标题",
desc: "描述信息:{name}",
},
"en-US": {
title: "Local Title",
desc: "Description: {name}",
},
},
// 可选:自定义 namespace
namespace: "myLocal",
// 可选:找不到时是否回退到全局(默认 true)
fallbackToGlobal: true,
});
// 使用方式1:直接调用
console.log(t("title"));
console.log(t("desc", { name: "测试" }));
// 使用方式2:通过 .t 调用
console.log(t.t("title"));
// 检查翻译是否存在
console.log(t.te("title"));
</script>
<template>
<div>
<h1>{{ t("title") }}</h1>
</div>
</template>
方式二:LocalI18nProvider + useLocalI18nProvided(跨组件共享)¶
<!-- 父组件提供 -->
<template>
<LocalI18nProvider :messages="localMessages" namespace="dialog">
<template #default="{ t }">
<div>
<h2>{{ t("title") }}</h2>
<ChildComponent />
</div>
</template>
</LocalI18nProvider>
</template>
<script setup>
import { LocalI18nProvider } from "@bams-app/i18n";
const localMessages = {
"zh-CN": { title: "对话框标题" },
"en-US": { title: "Dialog Title" },
};
</script>
<!-- 子孙组件消费 -->
<script setup>
import { useLocalI18nProvided } from "@bams-app/i18n";
// 获取父组件 provide 的局部翻译
// 如果找不到,会自动回退到全局翻译并警告
const t = useLocalI18nProvided();
console.log(t("title"));
</script>
局部翻译的回退机制详解¶
局部翻译的查找顺序(优先级从高到低):
- 局部字典查找 → 当前语言的对应 key
- 全局回退 → 如果找不到且
fallbackToGlobal=true,查找全局翻译 - 兜底语言查找 → 当前语言找不到,尝试
fallbackLocale的局部字典
<script setup>
import { useLocalI18n } from "@bams-app/i18n";
const localMessages = {
"zh-CN": {
localKey: "中文局部翻译",
},
"en-US": {
localKey: "English local translation",
},
};
const t = useLocalI18n({
messages: localMessages,
fallbackToGlobal: true, // 默认 true
});
</script>
<template>
<div>
<!-- 1. 优先使用局部翻译 -->
<p>{{ t('localKey') }}</p>
<!-- 2. 局部找不到,回退到全局 -->
<p>{{ t('common.title') }}</p>
<!-- 3. 都找不到,返回 key 本身 -->
<p>{{ t('nonExistentKey') }}</p>
</div>
</template>
useLocalI18n 的完整返回值¶
const translator = useLocalI18n({
messages: localMessages,
namespace: 'myModule',
fallbackToGlobal: true,
});
// 可以直接调用 translator()
translator('key');
// 也可以通过属性访问:
translator.t; // 翻译函数
translator.te; // 检查翻译是否存在
translator.locale; // 当前语言(响应式)
translator.setLocale; // 设置语言
translator.antdLocale;// Ant Design 的 locale 对象
translator.loading; // 加载状态
translator.state; // 完整的 i18n 状态
translator.namespace; // 当前 namespace
响应式的 messages 和 options¶
useLocalI18n 支持响应式的配置:
<script setup>
import { ref, computed } from 'vue';
import { useLocalI18n } from "@bams-app/i18n";
const isDarkMode = ref(false);
// 响应式的 messages
const dynamicMessages = computed(() => ({
"zh-CN": {
theme: isDarkMode.value ? "深色模式" : "浅色模式",
},
"en-US": {
theme: isDarkMode.value ? "Dark Mode" : "Light Mode",
},
}));
const t = useLocalI18n({
messages: dynamicMessages, // 传入 ref 或 computed
});
</script>
<template>
<div>
<a-switch v-model:checked="isDarkMode" />
<p>{{ t('theme') }}</p>
</div>
</template>
5. 纯 JS 翻译¶
适用场景:工具函数、helper、非 Vue 模块。
获取管理器实例¶
import { getI18nManager } from "@bams-app/i18n";
const i18n = getI18nManager();
if (i18n) {
console.log(i18n.t("path.to.key"));
}
全局翻译¶
import { translate } from "@bams-app/i18n";
const msg = translate("demoModule.common.confirm");
const msgWithParams = translate("greeting", { name: "张三" });
const msgWithLocale = translate("hello", {}, "en-US");
带命名空间的翻译¶
import { translateScoped } from "@bams-app/i18n";
const msg = translateScoped("reportTeam", "toolbar.export");
创建可复用的作用域翻译器¶
import { createScopedTranslator } from "@bams-app/i18n";
const scopedT = createScopedTranslator("reportTeam");
// 方式1:直接调用
console.log(scopedT("toolbar.export"));
// 方式2:通过 .t 调用
console.log(scopedT.t("toolbar.export"));
// 检查是否存在
console.log(scopedT.te("toolbar.export"));
创建局部翻译器(纯 JS)¶
import { createLocalTranslator } from "@bams-app/i18n";
const localT = createLocalTranslator({
messages: {
"zh-CN": { confirm: "确定", cancel: "取消" },
"en-US": { confirm: "Confirm", cancel: "Cancel" },
},
namespace: "myUtils",
fallbackToGlobal: true,
});
console.log(localT("confirm"));
console.log(localT.te("confirm"));
6. 语言切换与监听¶
切换语言¶
import { useLocale } from "@bams-app/i18n";
const { setLocale } = useLocale();
// 切换到英文
await setLocale("en-US");
// 切换到中文(支持别名)
await setLocale("zh");
监听语言变化¶
import { getI18nManager } from "@bams-app/i18n";
const i18n = getI18nManager();
const unsubscribe = i18n.onLocaleChange((locale, aggregate) => {
console.log("语言已切换为:", locale);
console.log("三方库 locale:", aggregate.antdLocale);
});
// 取消监听
unsubscribe();
7. 预加载语言资源¶
import { getI18nManager } from "@bams-app/i18n";
const i18n = getI18nManager();
// 预加载英文资源(提升切换体验)
await i18n.loadLocale("en-US");
8. 模板中直接使用¶
<template>
<div>
<!-- 全局翻译 -->
<button>{{ $t("common.confirm") }}</button>
<!-- 当前语言 -->
<span>{{ $locale }}</span>
</div>
</template>
五类能力¶
1. 运行时语言管理¶
setLocale()切换语言时会重新聚合模块资源并同步三方库init()会读取持久化语言并完成首次加载registerModule()/registerModules()支持运行时动态注册模块unregisterModule()/unregisterModules()支持运行时动态卸载模块(引用计数自动清理)onLocaleChange()支持监听语言切换事件
2. 模块化消息组织¶
- 单个模块可同时声明
messages和loaders - 当前语言缺失时,自动回退到
fallbackLocale - 模块消息会按
namespace自动挂载,避免键冲突 defineBusinessI18n()默认把kebab-case包名转成camelCase namespace
3. 三方库语言同步¶
内建模块会自动处理:
Ant Design Vue localeDevExtreme locale + messagesdayjs locale
默认内建支持:
zh-CNen-US
4. 局部翻译与回退¶
- 局部字典优先
- 局部找不到时可回退到全局
- 支持继承父业务组件 namespace
- 同时支持 Vue 和纯 JS 两类局部翻译能力
5. 文案表达能力¶
- 支持
{{key}}和{key}两种插值语法 - 支持函数式翻译值:
params => string - 支持语言别名标准化:
zh、zh-cn、zh_cn、en、en-us、en_us
关键边界说明¶
这部分建议使用方先看一遍,能少踩很多坑。
- 不是所有 API 都返回可直接调用的函数:
useLocale()、useScopedI18n()、useComponentI18n()返回的是对象,需要解构出t - 可直接调用的 translator 主要有这些:
createScopedTranslator()、useLocalI18n()、useLocalI18nProvided()、createLocalTranslator() createLocalTranslator()不是useLocalI18n()的完全等价物:它适合纯 JS 调用,但不提供 Vue 响应式的locale/loading/stateuseLocalI18nProvided()找不到 provider 时会回退到全局翻译并给出警告
高级用法¶
1. 手动管理模块引用计数¶
import { retainI18nModule, releaseI18nModule } from "@bams-app/i18n";
// 增加引用计数并注册模块(如果未注册)
await retainI18nModule(myModule);
// 减少引用计数,计数为 0 时卸载模块
await releaseI18nModule(myModule);
2. 函数式翻译值¶
// 语言包文件
export default {
greeting: params => {
const hour = new Date().getHours();
if (hour < 12) return `早上好,${params.name}!`;
if (hour < 18) return `下午好,${params.name}!`;
return `晚上好,${params.name}!`;
},
};
// 使用
t("greeting", { name: "张三" });
3. 获取注册的模块列表¶
import { getI18nManager } from "@bams-app/i18n";
const i18n = getI18nManager();
const modules = i18n.getRegisteredModules();
console.log("已注册模块:", modules);
4. 获取当前语言的所有消息¶
import { getI18nManager } from "@bams-app/i18n";
const i18n = getI18nManager();
const messages = i18n.getLocaleMessages(); // 当前语言
const enMessages = i18n.getLocaleMessages("en-US"); // 指定语言
最佳实践¶
1. 模块组织¶
- 按业务领域划分模块,避免单模块过大
- 通用功能(如按钮、表单)抽取为共享模块
- 业务组件用
defineBusinessI18n+attachI18n简化接入
2. 性能优化¶
- 常用翻译放在
messages(同步加载) - 不常用或体积大的翻译用
loaders(异步加载) - 可在用户可能切换语言前预加载
i18n.loadLocale()
3. 命名规范¶
- 包名使用 kebab-case:
ui-report-team - namespace 自动转 camelCase:
uiReportTeam - 翻译 key 使用小写字母 + 点分隔:
toolbar.export