i18next批量导入多语言翻译文件及http-backend本地读取问题
关于i18next-http-backend读取本地文件的结论
不要在React Native场景用这个插件读本地locales文件。
这个插件的核心逻辑是发起HTTP请求加载资源,你看到的loadPath: '/locales/{{lng}}/{{ns}}.json'配置仅在Web端生效:Web打包后locales目录会作为静态资源托管在服务下,配置的路径实际是HTTP请求地址。React Native环境没有内置静态HTTP服务,直接用这个配置会触发失败的网络请求,根本读不到本地打包进bundle的JSON文件。
如果需要懒加载翻译资源,用i18next-resources-to-backend配合动态import即可,不需要发网络请求。
免手动逐个导入翻译文件的实现方案
你已经有构建前拉取翻译的Node脚本,下面两种方案都可以彻底解决手动导入漏写的问题,新增翻译文件、新增语言都不需要改i18n配置。
方案1:Metro 批量动态匹配导入(零额外脚本,最省事)
React Native 自带的Metro打包工具v0.70及以上版本默认支持require.context,可以直接在i18n配置文件里遍历匹配locales目录下所有JSON文件,自动组装i18n需要的resources对象,不需要写任何单独的import语句:
import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; // 递归匹配locales目录下所有.json结尾的文件 const localesContext = require.context('../../public/locales', true, /\.json$/); const resources = {}; localesContext.keys().forEach((filePath: string) => { // 解析文件路径,例如 ./en-US/common.json 拆分出语言码en-US、命名空间common const pathSegs = filePath.split('/'); const lng = pathSegs[1]; const ns = pathSegs[2].replace('.json', ''); if (!resources[lng]) resources[lng] = {}; resources[lng][ns] = localesContext(filePath); }); i18n.use(initReactI18next).init({ interpolation: { escapeValue: false, }, lng: 'en-US', fallbackLng: 'en-US', debug: true, defaultNS: 'common', react: { useSuspense: true, }, resources, // 直接挂载自动生成的资源对象 compatibilityJSON: 'v3', }); export default i18n;
后续不管新增多少语言、多少个翻译JSON文件,只要符合你现有的locales目录结构,Metro打包时会自动识别导入,完全不需要手动修改i18n配置。
方案2:复用现有构建脚本自动生成资源索引
如果项目Metro版本低于v0.70不支持require.context,直接在你现有的构建前拉取翻译的Node脚本里加几行逻辑,自动生成翻译资源的索引文件即可。
脚本逻辑很简单:遍历locales下所有语言目录、所有JSON翻译文件,自动生成导入语句和resources结构,写入locales/index.ts文件,示例代码如下:
const fs = require('fs'); const path = require('path'); // 放在你原有拉取翻译、生成JSON文件的逻辑之后执行 const localesRoot = path.join(__dirname, '../public/locales'); // 获取所有语言目录 const languages = fs.readdirSync(localesRoot).filter(entry => { return fs.statSync(path.join(localesRoot, entry)).isDirectory(); }); let importContent = ''; let resourceContent = ''; languages.forEach(lng => { // 获取当前语言下所有翻译JSON文件 const nsFiles = fs.readdirSync(path.join(localesRoot, lng)).filter(file => file.endsWith('.json')); resourceContent += ` '${lng}': {\n`; nsFiles.forEach(nsFile => { const ns = nsFile.replace('.json', ''); // 生成合法的导入变量名 const importVarName = `${lng.replace('-', '_')}_${ns.replace('-', '_')}`; importContent += `import ${importVarName} from './${lng}/${nsFile}';\n`; resourceContent += ` '${ns}': ${importVarName},\n`; }); resourceContent += ` },\n`; }); // 写入索引文件 const indexContent = `${importContent}\nexport default {\n${resourceContent}};\n`; fs.writeFileSync(path.join(localesRoot, 'index.ts'), indexContent);
之后i18n配置里只需要直接导入生成的resources即可:
import resources from '../../public/locales'; // 后续init配置里直接挂载resources就行
每次构建前脚本拉取完最新翻译,会自动更新索引文件,同样不需要手动维护导入语句。
懒加载场景适配
如果不想把所有翻译文件都打包进首屏bundle,需要按需加载翻译,不要用i18next-http-backend,换成i18next-resources-to-backend配合动态import实现,不会发起网络请求,直接读本地打包资源:
import ChainedBackend from 'i18next-chained-backend'; import resourcesToBackend from 'i18next-resources-to-backend'; i18n .use(ChainedBackend) .use(initReactI18next) .init({ // 其他配置和之前保持一致 ns: ['common', 'auth', 'forms'], // 声明所有用到的命名空间 defaultNS: 'common', backend: { backends: [ resourcesToBackend((lng, ns) => import(`../../public/locales/${lng}/${ns}.json`)) ] } })
内容的提问来源于stack exchange,提问作者joshuaaron

