如何使用 i18next 实现多种语言的国际化(从新建 vite ts 项目开始)

预览

你可以拉取指定分支项目的代码预览。该项目目前支持 20 多种语言。且数据格式为 json 数据。

bash 复制代码
git clone -b vite-ts-i18n https://github.com/reoreo-zyt/vue3-ts-demo.git

安装依赖并运行

bash 复制代码
npm i
npm run dev

1、搭建项目

首先我们需要明确我们实现功能需要什么

  • vite 构建工具
  • i18next 多语言切换
  • 需要支持 json 文件
  • 需要自动识别用户语言并设置为默认语言
  • 需要支持记住缓存语言功能

1.1 构建前端,使用 vite 和 ts

当前的 create-vite@8.0.2 需要限制的 node 版本。这里我们使用 nvm 安装 22.12.0 并切换版本

bash 复制代码
nvm install 22.12.0
nvm use 22.12.0

使用 vite 新建一个项目

bash 复制代码
npm create vite

选择框架时选 others 以及 ts,这样我们就可以拉取到代码模板

安装依赖运行项目

bash 复制代码
npm install
npm run dev

1.2 实现多语言切换

先安装下多语言 i18next 以及相关依赖

bash 复制代码
npm install i18next i18next-browser-languagedetector i18next-http-backend i18next-korean-postposition-processor --save

在 tsconfig.json 中,我们需要设置下 paths,确保使用别名路径导入。

json 复制代码
{
  "compilerOptions": {
    "target": "es2022",
    "useDefineForClassFields": true,
    "module": "esnext",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,

    /* Linting */
    "strict": false,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,

    "paths": {
      "#utils/*": ["./src/utils/*.ts"],
      "#package.json": ["./package.json"],
      "#plugins/*": ["./src/plugins/*.ts"]
    }
  },
  "include": ["src"]
}

新建 language/i18n.ts 在 main.ts 中引入它,在这里我们只定义了两种语言。通过在 supportedLngs 数据里添加文件夹同名的语言,可以在切换时加载到对应语言的 json 数据。

ts 复制代码
import i18next from "i18next";
// 自动检测设备使用的语言,通过缓存里面的设备选择需要使用的语言
import LanguageDetector from "i18next-browser-languagedetector";
// 从后端加载资源
import HttpBackend from "i18next-http-backend";
// 处理韩语后缀的插件
import processor, {
  KoreanPostpositionProcessor,
} from "i18next-korean-postposition-processor";
import { namespaceMap } from "./utils-plugins";
import { toKebabCase } from "#utils/strings";
import pkg from "#package.json";

// 使用插件
i18next.use(LanguageDetector);
i18next.use(HttpBackend);
i18next.use(processor);
i18next.use(new KoreanPostpositionProcessor());

const nsEn: string[] = [];

await i18next.init({
  // 回退语言,决定于用户使用的语言
  // 当无法提供用户的首选语言时,可以指定另外一个语言作为备用
  fallbackLng: {
    "es-419": ["es-ES", "en"],
    default: ["en"],
  },
  // 支持的语言
  supportedLngs: [
    "en", // 英文
    "zh-Hans", // 中文
  ],
  // i18next-browser-languagedetector 插件参数
  detection: {
    lookupLocalStorage: "prLang", // 通过 localStorage 缓存选择的语言
  },
  // i18next-http-backend 插件参数
  backend: {
    // 资源加载的地址
    loadPath(lng: string, [ns]: string[]) {
      console.log(lng, ns, "==loadPath==");
      // Use namespace maps where required
      let fileName: string;
      if (namespaceMap[ns]) {
        fileName = namespaceMap[ns];
      } else if (ns.startsWith("mysteryEncounters/")) {
        fileName = toKebabCase(ns + "-dialogue"); // mystery-encounters/a-trainers-test-dialogue
      } else {
        fileName = toKebabCase(ns);
      }
      // ex: "./locales/en/move-anims"
      return `./locales/${lng}/${fileName}.json?v=${pkg.version}`;
    },
  },
  // 默认的命名空间字符串
  defaultNS: "menu",
  // TODO: 要加载的命名空间字符串或者数组。
  // 配合 vite 插件使用,需要实现按需加载
  ns: nsEn,
  // ns: ["menu", "egg"], // assigned with #app/plugins/vite/namespaces-i18n-plugin.ts
  interpolation: {
    // 默认情况下,值会被转义以缓解 XSS 攻击。
    // 关闭转义
    escapeValue: false,
  },
  debug: true, // 将信息级别记录到控制台输出。有助于查找加载失败的问题。
  postProcess: ["korean-postposition"],
});

document.querySelector<HTMLDivElement>("#app")!.innerHTML = `
  <div>${i18next.t("menu:loadingAsset", { assetName: "111" })}</div>
  <button id="test1">切换中文</button>
  <button id="test2">切换英文</button>
`;

document.getElementById("test1").addEventListener("click", function () {
  i18next.changeLanguage("zh-Hans");
  location.reload();
});

document.getElementById("test2").addEventListener("click", function () {
  i18next.changeLanguage("en");
  location.reload();
});

这里需要注意的是,backend 中的加载将 ns 中定义的数组即 json 文件全部通过网络请求获取到数据。

你可能会感到奇怪,nsEn 明明定义的是空数组,为什么会获取到该文件夹下的全部数据。

这是因为,自定义的 vite 插件 namespaces-i18n-plugin.ts 做了额外的处理,它将匹配到的文件夹下面的所有 json 文件名获取到,在运行代码之前替换掉了这个空数组代码。

ts 复制代码
transform: {
  handler(code, id) {
    if (id.endsWith("i18n.ts")) {
      return code.replace(
        "const nsEn = [];",
        `const nsEn = ${JSON.stringify(namespaces)};`
      );
    }
    return code;
  },
},

我们也可以手动写入 json 数据对应的数组数据,但这会增加维护成本。

相关推荐
veneno8 小时前
大量异步并发请求控制并发解决方案
前端
i***t9198 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
oden9 小时前
2025博客框架选择指南:Hugo、Astro、Hexo该选哪个?
前端·html
小光学长9 小时前
基于ssm的宠物交易系统的设计与实现850mb48h(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
云中飞鸿9 小时前
函数:委托
javascript
小小前端要继续努力9 小时前
渐进增强、优雅降级及现代Web开发技术详解
前端
老前端的功夫10 小时前
前端技术选型的理性之道:构建可量化的ROI评估模型
前端·javascript·人工智能·ubuntu·前端框架
狮子座的男孩10 小时前
js函数高级:04、详解执行上下文与执行上下文栈(变量提升与函数提升、执行上下文、执行上下文栈)及相关面试题
前端·javascript·经验分享·变量提升与函数提升·执行上下文·执行上下文栈·相关面试题
爱学习的程序媛10 小时前
《JavaScript权威指南》核心知识点梳理
开发语言·前端·javascript·ecmascript
乐观主义现代人11 小时前
go 面试
java·前端·javascript