一种Tauri+NextJS国际化方案

237 阅读3分钟

在之前文章中,我们开发 AI 应用时,采用了 Tauri + Next.JS 架构,且当时设想是制作国际版本,并不局限于中文环境。在当时的开发中期,我们就开始评估国际化文案。

我们前端采用的是 Next.JS,并且使用了 App Router,所以我们主要针对支持 Next.JS 的国际化框架进行分析。

next-intl

通过关键字 nextjsi18n,我们首先发现的国际化框架就是 next-intl,其官网为:next-intl-docs.vercel.app(由于国内网络较难访问 vercel.app,我部署了一个代理地址:https://next-intl.deno.dev)。

PixPin_2024-12-04_20-08-51.png

next-intl 做得非常好的就是区分了 App RouterPage Router,且配置步骤非常清晰,所以我们首选了它进行国际化适配和验证。

然而实践后我们发现,虽然 next-intl 的国际化方案非常简洁,但是在 Tauri 框架限制下,前端运行环境是不能使用 cookies 的,这导致无法动态切换界面语言的功能。

此方案抛弃。

next-i18next

next-i18next 是一个非常丰富的国际化框架,其官网为:next.i18next.com

PixPin_2024-12-04_20-15-02.png

但是可能因为我不是一个专业的前端开发,不论我照着官方的示例配置还是找别人分享的示例代码,都没法成功的实现国际化。

折腾很久以后,此方案放弃。

放弃以上两个最流行的方案后,我们又查找了很多类似的国际化方案。但是都不能找到一个合用的方案。最后我们决定参照 next-intl 的国际化文件读取,结合 AI 指导,自己开发一个国际化方案。

我们的方案

资源文件

PixPin_2024-12-04_20-22-18.png

首先在 src 目录下创建目录 locales,对应语言定义中对应的 json 文件,如:en.json

然后每个文件中,定义具体的资源 key 与国际化文字的对应,如下图:

PixPin_2024-12-04_20-24-29.png

GlobalProvider

由于我们使用了 AntD 作为组件样式组件,所以我们在国际化的同时还要支持主题切换,自然我们就需要构建了一个全局的 Provider。

而国际化的实现在 Provider 中加一个 i18n 对象,在各个要进行国际化的页面中,引用全局的 i18n 对象就能解析出对应的国际化文字。

  1. 在 Provider 中,我们先定义一个类用于存储相应的国际化串:
interface Translations {
  [key: string]: string;
}
  1. 定义语言的值:
const [locale, setLocale] = useState("zh");
  1. 在 Provider 定义对应语言国际化资源的读取函数:
  const loadI18nData = async () => {
    const jsonModule = await import(`../../locales/${locale}.json`);
    setTranslation(jsonModule);
  };
  1. 在 Provider 初始中执行初始语言的国际化资源读取:
  useEffect(() => {
    loadI18nData();
  }, [locale]);

注意:这里我们增加了读取函数依赖于语言,这样,当发生语言变更后,就能动态刷新读取的国际化资源内容。

  1. 我们定义全局的 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")}

小结

通过以上简单的方案,我们实现了项目的国际化方案。也许因为我们不是专业的前端工程师,所以不太会使用成熟的国际化方案。

虽然如前一篇文章所说,我们的项目失败了,但开发中的小小心得在此分享给大家。与君共勉。