在之前文章中,我们开发 AI 应用时,采用了 Tauri
+ Next.JS
架构,且当时设想是制作国际版本,并不局限于中文环境。在当时的开发中期,我们就开始评估国际化文案。
我们前端采用的是 Next.JS
,并且使用了 App Router
,所以我们主要针对支持 Next.JS
的国际化框架进行分析。
next-intl
通过关键字 nextjs
和 i18n
,我们首先发现的国际化框架就是 next-intl
,其官网为:next-intl-docs.vercel.app
(由于国内网络较难访问 vercel.app,我部署了一个代理地址:https://next-intl.deno.dev
)。
next-intl
做得非常好的就是区分了 App Router
和 Page Router
,且配置步骤非常清晰,所以我们首选了它进行国际化适配和验证。
然而实践后我们发现,虽然 next-intl
的国际化方案非常简洁,但是在 Tauri
框架限制下,前端运行环境是不能使用 cookies
的,这导致无法动态切换界面语言的功能。
此方案抛弃。
next-i18next
next-i18next
是一个非常丰富的国际化框架,其官网为:next.i18next.com
。
但是可能因为我不是一个专业的前端开发,不论我照着官方的示例配置还是找别人分享的示例代码,都没法成功的实现国际化。
折腾很久以后,此方案放弃。
放弃以上两个最流行的方案后,我们又查找了很多类似的国际化方案。但是都不能找到一个合用的方案。最后我们决定参照 next-intl
的国际化文件读取,结合 AI 指导,自己开发一个国际化方案。
我们的方案
资源文件
首先在 src
目录下创建目录 locales
,对应语言定义中对应的 json 文件,如:en.json
。
然后每个文件中,定义具体的资源 key 与国际化文字的对应,如下图:
GlobalProvider
由于我们使用了 AntD
作为组件样式组件,所以我们在国际化的同时还要支持主题切换,自然我们就需要构建了一个全局的 Provider。
而国际化的实现在 Provider 中加一个 i18n
对象,在各个要进行国际化的页面中,引用全局的 i18n
对象就能解析出对应的国际化文字。
- 在 Provider 中,我们先定义一个类用于存储相应的国际化串:
interface Translations {
[key: string]: string;
}
- 定义语言的值:
const [locale, setLocale] = useState("zh");
- 在 Provider 定义对应语言国际化资源的读取函数:
const loadI18nData = async () => {
const jsonModule = await import(`../../locales/${locale}.json`);
setTranslation(jsonModule);
};
- 在 Provider 初始中执行初始语言的国际化资源读取:
useEffect(() => {
loadI18nData();
}, [locale]);
注意:这里我们增加了读取函数依赖于语言,这样,当发生语言变更后,就能动态刷新读取的国际化资源内容。
- 我们定义全局的
i18n
对象,如下:
//用于客户端组件获取国际化文字的函数
const i18n = (key: string, param?: PlaceParam): string => {
const v = i18nInternal(key, translation);
if (param != undefined) {
return i18nReplaceParam(v, param);
}
return v;
};
//国际化带参数解析
//示例:{"key":"我的名字是{name}"}
//i18n("key",{name:"value"})
const i18nReplaceParam = (str: string, param: PlaceParam) => {
return str.replace(/{(\w+)}/g, (match, key) => {
return key in param ? param[key] : match;
});
};
//支持多层json,i18n("frist.two.three")
const i18nInternal = (allKey: string, translation: any) => {
if (allKey == undefined) {
return allKey;
}
const keys = allKey.split(".");
let result = translation;
for (let key of keys) {
if (result && key in result) {
result = result[key];
} else {
return allKey;
}
}
return result;
};
在页面中使用
引用:
const { theme, setTheme, locale, setLocale, i18n } =
useContext(GlobalContext);
使用:
{i18n("sider.app.name")}
小结
通过以上简单的方案,我们实现了项目的国际化方案。也许因为我们不是专业的前端工程师,所以不太会使用成熟的国际化方案。
虽然如前一篇文章所说,我们的项目失败了,但开发中的小小心得在此分享给大家。与君共勉。