# I18n I18n 是基于 `vanilla-signal` 的响应式国际化管理器,源码位于 `src/i18n.js`。它既可以通过 `new I18n(options)` 创建独立实例,也可以用 `createI18n(options)` 创建;同时保留 `setLanguages/getLanguages/getLocale/getLang/t` 这些历史工具函数,兼容旧项目。 推荐新代码优先使用独立实例,避免多个页面或组件共享同一个全局语言状态。 ## 安装 npm: ```bash npm install vanilla-signal-i18n ``` script: ```html ``` ## 导入 ```js // esm import { I18n, createI18n, defaultI18n, t } from 'vanilla-signal-i18n'; // umd: 全局变量 `vanillaSignalI18n` const { I18n, createI18n, defaultI18n, t } = vanillaSignalI18n; ``` ## 基础使用 ```js const i18n = createI18n({ locale: 'zh-CN', fallbackLocale: 'en', messages: { en: { common: { confirm: 'Confirm', hello: 'Hello {name}', }, }, zh: { common: { confirm: '确定', hello: '你好 {name}', }, }, }, }); i18n.t('common.confirm'); // 确定 i18n.t('common.hello', { name: 'World' }); // 你好 世界 ``` `key` 支持点路径。找不到当前语言的文案时,会按 `zh-cn -> zh -> en` 这样的顺序回退;仍找不到时返回原始 key。 ## 响应式翻译 `i18n.t()` 会读取内部 signal。在 `vanilla-signal` 的响应式上下文中调用时,`locale` 或 `messages` 变化会自动触发重新计算。 ```js import { createEffect } from 'vanilla-signal'; const i18n = new I18n({ locale: 'en', messages: { en: { title: 'Settings' }, zh: { title: '设置' }, }, }); createEffect(() => { document.title = i18n.t('title'); }); i18n.setLocale('zh'); ``` 如果只需要读取 locale signal,也可以使用 `getLocaleSignal()`: ```js const locale = i18n.getLocaleSignal(); createEffect(() => { console.log(locale()); }); ``` ## 追加语言包 ```js i18n.addMessages('zh', { modal: { title: '提示', cancel: '取消', }, }); i18n.addMessages('en', { modal: { title: 'Tip', cancel: 'Cancel', }, }); ``` `addMessages(locale, messages)` 默认会和已有语言包深度合并。需要替换指定语言的语言包时: ```js i18n.addMessages('zh', { ok: '确定' }, { merge: false }); ``` 需要替换或合并完整语言包时: ```js i18n.setMessages( { en: { save: 'Save' }, zh: { save: '保存' }, }, { merge: true } ); ``` ## 命名空间 `createTranslator(namespace)` 可以创建带前缀的翻译函数,适合组件内部使用。 ```js const modalT = i18n.createTranslator('modal'); modalT('title'); // 等价于 i18n.t('modal.title') modalT('confirm'); ``` ## 缺失翻译 默认情况下,缺失翻译会返回原始 key。可以通过 `warnMissing` 输出 warning,或通过 `missing` 自定义返回值。 ```js const i18n = createI18n({ locale: 'zh', fallbackLocale: 'en', warnMissing: true, missing: ({ key }) => `[missing:${key}]`, }); ``` ## 订阅变化 `subscribe(handler)` 可监听 locale、fallback locale 和 messages 变化,适合和非 signal 代码集成。 ```js const unsubscribe = i18n.subscribe((event) => { console.log(event.type, event.locale); }); i18n.setLocale('zh'); unsubscribe(); ``` 事件类型: | 类型 | 触发时机 | | ----------------- | ------------------ | | `locale` | 当前语言变化 | | `fallback-locale` | 回退语言变化 | | `messages` | 语言包被设置或追加 | ## 默认实例 `defaultI18n` 是库内置的默认实例。历史工具函数都委托给它: ```js import { setLanguages, getLanguages, getLocale, getLang, t, } from 'vanilla-signal-i18n'; setLanguages({ en: { ok: 'OK' }, zh: { ok: '确定' }, }); t('ok'); // 根据当前默认 locale 返回文案 getLanguages(); getLocale(); getLang(); ``` `t()` 同时兼容旧签名: ```js t( 'ok', { en: { ok: 'OK' }, zh: { ok: '确定' }, }, 'zh' ); // 确定 ``` 新代码如果需要插值、动态切换语言或隔离不同组件的语言状态,建议使用独立 `I18n` 实例的 `i18n.t()`。 ## Options | 参数 | 类型 | 默认值 | 说明 | | ---------------- | ------------------------------------- | ------- | --------------------------------------- | | `locale` | `string` | 自动 | 初始语言;为空时读取 html lang 或浏览器 | | `fallbackLocale` | `string` | `'en'` | 回退语言 | | `messages` | `Record>` | `{}` | 语言包 | | `languages` | `Record>` | `{}` | `messages` 的别名,兼容旧命名 | | `missing` | `(payload: object) => string` | `null` | 缺失翻译时的返回处理函数 | | `warnMissing` | `boolean` | `false` | 缺失翻译时是否输出 warning | ## 实例属性 I18n 的状态通过实例方法读取,不暴露可直接修改的公共属性。`messages` 会在写入时做浅层/深层克隆,避免外部对象引用被直接复用。 ## 实例方法 | 方法 | 说明 | | ---------------------------------------- | ------------------------------------ | | `getLocaleSignal()` | 获取当前语言的 signal accessor | | `getLocale()` | 获取当前语言 | | `setLocale(locale)` | 设置当前语言 | | `getFallbackLocale()` | 获取回退语言 | | `setFallbackLocale(locale)` | 设置回退语言 | | `setMessages(messages, options)` | 设置完整语言包 | | `setLanguages(messages)` | `setMessages()` 的兼容别名 | | `addMessages(locale, messages, options)` | 追加或替换指定语言的语言包 | | `getMessages(locale)` | 获取指定语言包;不传 locale 返回全部 | | `getLanguages()` | `getMessages()` 的兼容别名 | | `has(key, options)` | 判断 key 是否有可用翻译 | | `resolve(key, options)` | 解析翻译,返回命中语言和原始值 | | `t(key, params, options)` | 获取翻译文案并执行插值 | | `createTranslator(namespace, defaults)` | 创建带命名空间的翻译函数 | | `subscribe(handler)` | 订阅 i18n 状态变化,返回取消订阅函数 | | `destroy()` | 清理监听器和语言包 | ## 翻译值类型 翻译值可以是字符串、数字、布尔值,或函数。 ```js const i18n = createI18n({ locale: 'en', messages: { en: { count: ({ value }) => `${value} items`, }, }, }); i18n.t('count', { value: 3 }); // 3 items ``` 函数会收到两个参数: | 参数 | 说明 | | --------- | --------------------------- | | `params` | 调用 `t()` 时传入的插值参数 | | `context` | `{ key, locale, i18n }` | ## Locale 规则 locale 会被规范化为小写,并把 `_` 转为 `-`: ```js i18n.setLocale('zh_CN'); i18n.getLocale(); // zh-cn ``` 查找文案时会按完整 locale、基础语言、fallback locale、fallback 基础语言的顺序回退。例如当前语言为 `zh-CN`、回退语言为 `en-US` 时,查找顺序为: ```txt zh-cn -> zh -> en-us -> en ``` ## 翻译 - [English](README.md)