@skyroc/web-admin-i18n
Skyroc Admin 的管理端多语言运行时,统一 i18next 初始化、语言状态、语言切换组件和语言副作用入口
@skyroc/web-admin-i18n 是 Skyroc Admin 管理端的多语言基础包。它把后台应用通用的 i18next 初始化、语言状态、语言切换 UI、内置翻译资源和语言切换副作用入口收口到一个包里。
这个包不试图做所有平台的通用 i18n 抽象。它面向 Web 管理后台,默认语言模型是 zh-CN / en-US,并且和 React、Jotai、i18next、react-i18next 这套运行时配合使用。
解决什么问题
后台应用里多语言逻辑通常会分散在几个位置:
- 启动阶段要初始化 i18next、加载语言资源、读写本地缓存。
- Header 或登录页要提供语言切换按钮。
- 菜单、主题抽屉、通知、表单校验和页面文案要共享同一套翻译资源。
- Ant Design、Dayjs、
document.lang等第三方能力要跟着当前语言变化。 - 应用侧要保留自己的
localStorage适配、默认语言配置和业务级语言选项。
@skyroc/web-admin-i18n 的职责是把这些能力拆清楚:
| 能力 | 放在哪里 | 说明 |
|---|---|---|
| i18next 实例、资源加载、语言 atom | @skyroc/web-admin-i18n | 多个后台应用可复用。 |
useLang()、LangSwitch、LangEffect | @skyroc/web-admin-i18n | React 侧读取和切换语言。 |
| 当前应用的默认语言、语言缓存 | apps/admin/src/locales/index.ts | 应用决定从哪里读写语言。 |
| Dayjs locale 映射 | apps/admin/src/locales/sync.ts | 第三方库的 locale key 通常是应用约定。 |
| Ant Design locale 对象 | apps/admin/src/locales/antd.ts | AntD locale 通过 Provider reactive 渲染。 |
| 菜单、路由、页面等业务文案 key | packages/web/admin-i18n/src/langs/* + app 类型 | 翻译资源在包内,业务类型在应用侧约束。 |
分层边界
bootstrap.tsx
setupI18n()
初始化 i18next,解析初始语言,写入 Jotai language atoms。
App.tsx
<AntdProvider>
<RouterProvider />
<GlobalEffect />
</AntdProvider>
GlobalEffect.tsx
<LangEffect onLocaleChange={syncLocales} />
document.lang 和 Dayjs 等响应式语言副作用集中在这里。
AntdProvider.tsx
useLang().locale -> antdLocales[locale]
Ant Design locale 通过 React Provider 跟随当前语言重渲染。关键原则:
setupI18n()是启动初始化,不应该承接 DOM 或第三方 UI 库的响应式副作用。LangEffect.onLocaleChange是 React 挂载后的语言副作用入口,适合同步document.lang、Dayjs 等需要跟随语言切换的能力。- Ant Design 不需要放进
LangEffect,它已经通过useLang().locale和 Provider 自然重渲染。 @skyroc/web-admin-runtime只负责 Dayjs 插件注册,不负责语言切换后的 Dayjs locale 映射。
快速接入
1. 初始化 i18n
应用侧保留一个很薄的 locales/index.ts,把本应用的配置适配给共享包:
import { setupI18n as setupCoreI18n } from '@skyroc/web-admin-i18n';
import type { LocaleSetupOptions } from '@skyroc/web-admin-i18n';
import { globalConfig } from '@/config';
import { localStg } from '@/utils/storage';
export { $t } from '@skyroc/web-admin-i18n';
export async function setupI18n(options: LocaleSetupOptions<I18n.LangType> = {}) {
await setupCoreI18n({
defaultLocale: globalConfig.defaultLang,
fallbackLocale: 'en-US',
localeOptions: globalConfig.defaultLangOptions,
missingWarn: import.meta.env.DEV,
storage: {
getLocale: () => localStg.get('lang'),
setLocale: lang => localStg.set('lang', lang)
},
...options
});
}这里的应用侧职责只有三件事:
- 决定默认语言从哪里来,例如
globalConfig.defaultLang。 - 决定语言列表展示什么,例如
中文/English。 - 决定语言缓存写到哪里,例如
localStg.get('lang')/localStg.set('lang', lang)。
2. 在启动阶段调用
setupI18n() 是异步初始化,应该在 React render 前完成:
async function setupApp() {
const container = document.getElementById('app');
if (!container) return;
setupTheme({ buildTime: BUILD_TIME });
setupAdminLayouts({ routeTree, storage: localStg });
setupAdminPlugins();
await setupI18n();
createRoot(container).render(<App />);
}这个顺序保证组件首次渲染时已经有当前语言、fallback 语言和语言选项。React 组件内部可以直接使用 useTranslation()、useLang() 或 $t。
3. 同步第三方 locale
第三方语言同步集中放在应用侧 locales/sync.ts:
// oxlint-disable import/no-unassigned-import
import { locale } from 'dayjs';
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/en';
const localeMap: Record<I18n.LangType, string> = {
'zh-CN': 'zh-cn',
'en-US': 'en'
};
export function syncLocales(lang: I18n.LangType) {
locale(localeMap[lang]);
}然后在全局副作用组件里接入:
import { LangEffect } from '@skyroc/web-admin-i18n';
import { ThemeEffect } from '@skyroc/web-admin-theme';
import { syncLocales } from '@/locales/sync';
const GlobalEffect = () => {
return (
<>
<ThemeEffect />
<LangEffect onLocaleChange={syncLocales} />
</>
);
};
export default GlobalEffect;LangEffect 自身会同步 document.documentElement.lang,传入的 onLocaleChange 用来承接 Dayjs 这类应用级第三方映射。
4. 同步 Ant Design locale
Ant Design 的语言对象通过 Provider 传入,不走 LangEffect:
import { useLang } from '@skyroc/web-admin-i18n';
import { AntdProvider } from '@skyroc/web-admin-theme';
import type { PropsWithChildren } from 'react';
import { antdLocales } from '@/locales/antd';
const AppAntdProvider = (props: PropsWithChildren) => {
const { children } = props;
const { locale } = useLang();
return <AntdProvider locale={antdLocales[locale]}>{children}</AntdProvider>;
};这种方式更符合 React 数据流:语言状态变化后 Provider 重渲染,DatePicker、Pagination 等 AntD 组件自然拿到新的 locale。
包内结构
packages/web/admin-i18n/src
├─ atoms/lang.ts 当前语言、fallback、语言选项和当前选项
├─ config/default.ts 默认 zh-CN / en-US 配置
├─ features/lang/LangEffect.tsx DOM 和第三方语言副作用入口
├─ features/lang/LangSwitch.tsx Ant Design Dropdown 语言切换按钮
├─ hooks/use-lang.ts React 语言状态 hook
├─ i18n.ts i18next 初始化、资源加载、setLng
├─ langs/ 分语言、分 namespace 的 JSON 资源
├─ locales.ts 兼容 i18next resources 形状的内部资源描述
├─ types.ts LangType、LocaleSetupOptions 等类型
└─ utils/helpers.ts 语言缓存和 label helper公共入口从 src/index.ts 导出稳定 API。不要从 config/i18n、features/lang/use-lang 之类内部路径导入;这些中间 re-export 层已经被移除。
API 参考
setupI18n(options)
初始化 i18next、配置运行时语言选项,并加载初始语言资源。
await setupI18n({
defaultLocale: 'zh-CN',
fallbackLocale: 'en-US',
localeOptions: [
{ key: 'zh-CN', label: '中文' },
{ key: 'en-US', label: 'English' }
],
missingWarn: import.meta.env.DEV,
storage: {
getLocale: () => localStg.get('lang'),
setLocale: lang => localStg.set('lang', lang)
}
});常用字段:
| 字段 | 说明 |
|---|---|
defaultLocale | 初始语言。通常由应用缓存或默认配置解析。 |
fallbackLocale | i18next fallback 语言。当前 admin 默认是 en-US。 |
localeOptions | 语言切换组件展示的选项。 |
missingWarn | 开发环境下缺失 key 时输出警告。 |
storage | 应用持有的语言缓存适配器。 |
resources | 额外预置给 i18next 的资源。 |
i18nextOptions | 透传给 i18next 的其他初始化配置。 |
onLocaleChange | 底层语言变化回调。应用内第三方同步优先放到 LangEffect.onLocaleChange。 |
useLang()
读取和切换当前语言。
import { useLang } from '@skyroc/web-admin-i18n';
const LanguageStatus = () => {
const { currentOption, isCurrentLang, locale, localeOptions, setLocale } = useLang();
function changeToEnglish() {
setLocale('en-US');
}
return (
<button disabled={isCurrentLang('en-US')} type="button" onClick={changeToEnglish}>
{currentOption?.label ?? locale} / {localeOptions.length}
</button>
);
};返回值:
| 字段 | 说明 |
|---|---|
locale | 当前语言。 |
currentOption | 当前语言对应的展示项。 |
localeOptions | 可切换语言列表。 |
fallbackLang | 当前 fallback 语言。 |
changeLocale / setLocale | 切换语言并加载对应资源。 |
isCurrentLang(lang) | 判断某个语言是否为当前语言。 |
LangSwitch
内置语言切换按钮,使用 Ant Design Dropdown 和 @skyroc/web-ui-antd 的 ButtonIcon。
import { LangSwitch } from '@skyroc/web-admin-i18n';
const HeaderActions = () => {
return <LangSwitch showTooltip visible />;
};Props:
| 字段 | 默认值 | 说明 |
|---|---|---|
className | undefined | 传给图标按钮的 class。 |
showTooltip | true | 是否展示 icon.lang 对应的 tooltip。 |
visible | true | 是否渲染语言切换按钮,适合由布局配置控制。 |
LangEffect
监听当前语言变化,并执行 DOM / 第三方同步。
import { LangEffect } from '@skyroc/web-admin-i18n';
import { syncLocales } from '@/locales/sync';
const GlobalEffect = () => {
return <LangEffect onLocaleChange={syncLocales} />;
};LangEffect 会做两件事:
- 设置
document.documentElement.lang = locale。 - 调用可选的
onLocaleChange(locale)。
这个组件应该只挂载一次,通常放在 App 的全局副作用层。
$t / setLng / getCurrentLang
这些 API 适合非组件代码使用:
import { $t, getCurrentLang, setLng } from '@skyroc/web-admin-i18n';
const message = $t('common.confirm');
const current = getCurrentLang();
await setLng('en-US');使用建议:
- React 组件优先使用
useTranslation()或useLang()。 - service、通知回调、路由配置等非组件代码可以使用
$t。 setLng()会加载资源、切换 i18next 语言、更新 Jotai atom,并触发底层onLocaleChange。
翻译资源维护
当前资源按语言和 namespace 拆分:
packages/web/admin-i18n/src/langs
├─ en-us
│ ├─ common.json
│ ├─ form.json
│ ├─ icon.json
│ ├─ notification.json
│ ├─ page.json
│ ├─ route.json
│ ├─ system.json
│ └─ theme.json
├─ zh-cn
│ ├─ common.json
│ ├─ form.json
│ ├─ icon.json
│ ├─ notification.json
│ ├─ page.json
│ ├─ route.json
│ ├─ system.json
│ └─ theme.json
└─ index.ts维护规则:
- 中英文资源要同时补齐,避免某个语言缺 key。
- 新增 namespace 时,在
langs/en-us和langs/zh-cn下各加一个 JSON 文件。 - 在
packages/web/admin-i18n/src/langs/index.ts中导入并加入enUS/zhCN聚合对象。 - 如果应用侧需要
I18n.I18nKey类型约束,同时更新apps/admin/src/types/locales/*.d.ts。 - 页面、路由、菜单、主题等已有 namespace 不要随意混用。优先把 key 放到语义最接近的 namespace。
示例:新增 report namespace。
import enUSReport from './en-us/report.json';
import zhCNReport from './zh-cn/report.json';
const enUS = {
// ...
report: enUSReport
};
const zhCN = {
// ...
report: zhCNReport
};与其他包的关系
| 包 | 关系 |
|---|---|
@skyroc/web-admin-layouts | Header 中可以直接使用 LangSwitch,菜单标题通过 I18nLabel 或 useTranslation() 读取翻译。 |
@skyroc/web-admin-theme | AntdProvider 接收 app 侧根据 useLang().locale 解析出的 AntD locale。 |
@skyroc/web-admin-runtime | 注册 Dayjs 插件等同步 runtime 能力,但不处理语言切换后的 locale 映射。 |
@skyroc/web-admin-vite | 自动导入 React、react-i18next 等开发体验能力,但不拥有语言状态。 |
apps/admin | 拥有启动顺序、语言缓存、Dayjs locale 映射、AntD locale 映射和业务类型约束。 |
常见问题
为什么 Dayjs 不在 setupI18n() 里同步?
setupI18n() 是启动初始化。Dayjs locale 需要在语言切换后继续变化,属于 React 挂载后的响应式副作用。把它放到 LangEffect.onLocaleChange 后,document.lang 和 Dayjs 同步都从同一个语言变化入口触发。
为什么 Ant Design 不在 syncLocales() 里同步?
Ant Design 的 locale 是 React Provider props。AntdProvider 读取 useLang().locale 后传入 antdLocales[locale],组件树会自然重渲染,不需要额外 imperative 同步。
什么时候可以用 LocaleSetupOptions.onLocaleChange?
这个字段保留给更底层或非 React 的集成场景。apps/admin 这类 React 后台应用优先使用 LangEffect.onLocaleChange,因为它和组件生命周期一致,也能避免初始化阶段副作用和渲染后副作用混在一起。
为什么 locales.ts 保留但不从入口导出?
locales.ts 是兼容 i18next resource 形状的内部描述。普通应用只需要 setupI18n()、useLang()、LangSwitch、LangEffect 和 $t。不把 locales 作为公共 API 暴露,可以减少外部对内部资源结构的耦合。
为什么语言类型还在应用侧有一份 I18n namespace?
共享包定义的是运行时语言能力,应用侧的 I18n.I18nKey 还要约束业务翻译 key。这个类型和路由、页面、业务枚举有关,仍然属于应用侧类型系统。
验证建议
修改 i18n 包或 app 侧语言接线后,建议至少执行:
pnpm --filter @skyroc/web-admin-i18n typecheck
pnpm --filter @skyroc/web-admin-i18n build
pnpm --filter skyroc-admin typecheck
pnpm --filter skyroc-admin build:test
pnpm --dir docs/web-kit-docs types:check
pnpm exec oxfmt --check docs/web-kit-docs/content/docs/admin-i18n.mdx
git diff --check涉及语言切换行为时,还应做一次浏览器验证:
- 打开登录页或后台 Header。
- 点击语言切换按钮,从
中文切到English。 - 确认页面文案切换为英文。
- 确认
document.documentElement.lang变为en-US。 - 确认 Dayjs 当前 locale 变为
en,AntD 日期类组件显示英文 locale。