Vite6+React18+Ts项目模板-06.集成i18next多语言
01. 安装依赖
首先安装必要的依赖:
npm install i18next react-i18next i18next-http-backend i18next-browser-languagedetector
02. 配置 i18n
创建一个 src\langs\i18n.ts
配置文件:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(Backend) // 启用从后端加载翻译文件
.use(LanguageDetector) // 启用浏览器语言检测
.use(initReactI18next) // 绑定 React
.init({
fallbackLng: 'en-GB', // 默认语言,初次加载时,首先会加载默认语言,然后加载当前语言,也就是说初次加载时会加载两种语言
load: 'currentOnly', // 仅加载当前语言
interpolation: {
escapeValue: false, // React 已经处理了 XSS 防护
},
backend: {
loadPath: 'http://demo.com/lang.php?code={{lng}}', // 从后端加载翻译文件的路径
},
detection: {
// 自定义 localStorage 的 key,这里面存的是当前语言类型,不是语言翻译内容
lookupLocalStorage: 'my_custom_lng_key',
// 自定义 cookie 的 key,这里面存的是当前语言类型,不是语言翻译内容
lookupCookie: 'my_custom_lng_cookie_key',
// 语言检测的选项,当前语言会按照顺序依次从以下选择中查询当前语言
order: ['localStorage', 'cookie', 'navigator'],
// 缓存检测到的语言
caches: ['localStorage', 'cookie'],
// 语言代码转换,可以将当前语言代码进行转换,比如将 en 转换为 en-GB
convertDetectedLanguage: (code) => {
//alert(code);
return code;
},
},
});
export default i18n;
03. 在 React 应用中使用
在应用的入口文件(main.ts
)中引入 i18n:
import ReactDOM from "react-dom/client";
// 引入 i18n 配置
import "@/langs/i18n";
// 引入 App 组件
import App from "@/app.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
04. 创建语言切换组件
创建一个语言切换组件 LanguageSwitcher.js
:
import { useTranslation } from 'react-i18next';
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onClick={() => changeLanguage('en-GB')}>English</button>
<button onClick={() => changeLanguage('zh-CN')}>中文简体</button>
</div>
);
};
export default LanguageSwitcher;
05. 在组件中使用翻译
在任何组件中使用翻译:
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation();
return (
<div>
<h1>{t('welcome')}</h1>
</div>
);
};
export default MyComponent;
06. 后端返回的语言文件结构
确保后端提供正确的语言文件结构,例如:
{
"welcome": "Welcome to our application",
"Sys.MsgInfo": "You hava {{name}} messages。"
}
07. 命名空间
可以使用命名空间来组织翻译:
后端返回的语言格式为嵌套对象方式
{
"homepage": {
"title": "首页",
"description": "欢迎来到我们的网站"
},
"product": {
"title": "产品",
"description": "我们的产品列表"
}
}
在代码中使用React + react-i18next 示例
import { useTranslation } from 'react-i18next';
function Component() {
const { t } = useTranslation('homepage');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
08.插值表达式
可以在翻译文件中使用插值表达式
// 翻译文件中
{
"greeting": "Hello, {{name}}!"
}
// 组件中
t('greeting', { name: 'John' });
09.复数形式
复数形式是国际化(i18n)中处理不同数量下文本变化的重要特性,因为不同语言对数量的表达方式差异很大。是指根据数量不同而变化的文本表达。例如:
英语: "1 apple" vs "2 apples"
中文: "1个苹果" vs "2个苹果" (中文通常不需要复数变化)
俄语: 有更复杂的复数规则
常见实现方式
1. 简单键值对
{
"apple": "apple | apples",
"banana": "banana | bananas"
}
2. 结构化形式
{
"apple": {
"one": "apple",
"other": "apples"
}
}
3.在代码中使用
const { t } = useTranslation();
// 简单形式
t('apple', { count: 1 }); // "apple"
t('apple', { count: 3 }); // "apples"
// 复杂形式
t('apple', { count: 1, plural: 'one' }); // "apple"
t('apple', { count: 3, plural: 'other' }); // "apples"
10.将i18n的语言保存到状态管理
大多数场景不建议将i18n的语言保存到状态管理,因为 i18n 自身已有管理状态,避免重复,微前端架构可能需要保存到状态管理。
1.创建用于保存状态的store文件 src\stores\lang\lang.store.ts
import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
// type passport
interface Lang {
code: string;
}
// define the initial state
const init: Lang = {
code: 'zh-CN',
};
// 存储store的key
const storeKey = 'store-lang';
// create your store
const useLangStore = create<Lang>()(
immer(
persist(
() => ({
...init,
}),
{
// name of the item in the storage (must be unique)
name: storeKey,
// (optional) by default, 'localStorage' is used
storage: createJSONStorage(() => localStorage),
}
)
)
);
export default useLangStore;
// 设置语言
export const setLang = (state: Lang) => {
useLangStore.setState(state);
};
// 重置语言
export const resetLang = () => {
useLangStore.setState(init);
};
// 监听 localStorage 变化
window.addEventListener('storage', (event) => {
if (event.key === storeKey && event.newValue !== null) {
const val = JSON.parse(event.newValue);
// 这里判断用户状态,如果用户变化了,则进入登录页面或进入首页,根据具体业务自行处理
if (JSON.stringify(useLangStore.getState()) != JSON.stringify(val.state)) {
useLangStore.setState(val.state);
}
}
});
2.在初始化i18n后,将语言代码保存到状态管理里
// src/i18n.ts
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { setLang } from '@/stores/lang/lang.store';
i18n
.use(Backend) // 启用从后端加载翻译文件
.use(LanguageDetector) // 启用浏览器语言检测
.use(initReactI18next) // 绑定 React
.init({
fallbackLng: 'en-GB', // 默认语言,初次加载时,首先会加载默认语言,然后加载当前语言,也就是说初次加载时会加载两种语言
load: 'currentOnly', // 仅加载当前语言
interpolation: {
escapeValue: false, // React 已经处理了 XSS 防护
},
backend: {
loadPath: 'http://demo.com/lang.php?code={{lng}}', // 从后端加载翻译文件的路径
},
detection: {
// 自定义 localStorage 的 key,这里面存的是当前语言类型,不是语言翻译内容
lookupLocalStorage: 'my_custom_lng_key',
// 自定义 cookie 的 key,这里面存的是当前语言类型,不是语言翻译内容
lookupCookie: 'my_custom_lng_cookie_key',
// 语言检测的选项,当前语言会按照顺序依次从以下选择中查询当前语言
order: ['localStorage', 'cookie', 'navigator'],
// 缓存检测到的语言
caches: ['localStorage', 'cookie'],
// 语言代码转换,可以将当前语言代码进行转换,比如将 en 转换为 en-GB
convertDetectedLanguage: (code) => {
//alert(code);
return code;
},
},
});
// 大多数场景不建议将i18n的语言保存到状态管理,因为 i18n 自身已有管理状态,避免重复,微前端架构可能需要保存到状态管理,也看具体情况
i18n.on('initialized', () => {
setLang({
code: i18n.language,
});
});
export default i18n;
3.封装修改语言的hook函数,在修改语言的同时,也需要修改状态管理里的语言代码
import { useTranslation } from 'react-i18next';
import { setLang } from '@/stores/lang/lang.store';
export const useChangeLanguage = () => {
const { i18n } = useTranslation();
const changeLanguage = async (lng: string) => {
await i18n.changeLanguage(lng);
setLang({
code: lng,
});
};
return {
changeLanguage,
};
};
4.在组件中使用
function LanguageSwitcher() {
const { changeLanguage } = useChangeLanguage();
return (
<div>
<button onClick={() => changeLanguage('en-GB')}>English</button>
<button onClick={() => changeLanguage('zh-CN')}>中文简体</button>
</div>
);
}
11.使用常量及注释
通常情况下,我们可以定义需要翻译的内容为常量,在需要翻译的地方调用常量,而不是字符串,同时,不同模块也可以建立不同的文件夹管理
模块sys下的翻译 src\langs\sys\index.ts
const Sys = {
/**
* 菜单是必填的
*/
MenuIsRequire: 'Sys.MenuIsRequire',
/**
* 消息提示
*/
MsgInfo: 'Sys.MsgInfo'
};
export default Sys;
将常量导出 src\langs\index.ts
import Sys from "./sys";
const Lang = {
Sys: Sys
}
export default Lang;
在组件中使用
import Lang from '@/langs';
import { useTranslation } from 'react-i18next';
export default function Demo() {
const { t } = useTranslation();
return (
<div>
<h1>{t(Lang.Sys.MenuIsRequire)}</h1>
<h1>{t(Lang.Sys.MsgInfo, { name: name })}</h1>
</div>
);
}
鼠标悬浮在常量上, 可以轻松看到注释信息

12.编写脚本,从后端同步常量文件
通常情况下,我们需要翻译的文本都是后端统一管理,故常量一般不需要我们手动编写,可以直接从后端同步过来,比如下面的node脚本就可以实现这一点
后端文件返回内容
{
// index.ts 的内容,内容经过base64编码
"main": "aW5kZXggY29udGVudA==",
// 各个模块的内容
"list": [
{
"name": "sys",
// 模块文件内容,内容经过base64编码
"content": "c3lzIGNvbnRlbnQ="
}
]
}
脚本文件路径: run\sync.js
// 控制台终端运行代码: node run/sync.js 即可同步服务器配置的语言翻译
import fs from 'fs';
import path from 'path';
import axios from 'axios';
import { Buffer } from 'buffer';
import { fileURLToPath } from 'url';
// 请求URL
const url = 'http://demo.com/lang-const.php';
// 目标目录
const dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../lang');
// 写入文件函数
function writeFile(filePath, content) {
const s = path.dirname(filePath);
if (!fs.existsSync(s)) {
fs.mkdirSync(s, { recursive: true });
}
fs.writeFileSync(filePath, content);
}
// 主函数
async function run() {
try {
// 请求数据
const response = await axios.get(url);
const data = response.data;
// 写入 index 内容
writeFile(path.join(dir, 'index.ts'), Buffer.from(data.main, 'base64').toString('utf-8'));
// 遍历 list 并写入各模块内容
data.list.forEach(item => {
writeFile(path.join(path.join(dir, item.name), 'index.ts'), Buffer.from(item.content, 'base64').toString('utf-8'));
});
console.log('Sync Language Successfully!');
} catch (error) {
console.error('Sync Language Failed:', error);
}
}
// 执行主函数
run();