背景
房东最近接到一个新项目。从0开始,需要多语言国际化。如今,基本达到了预期的效果。让我们在这里简单分享一下:
一些探索
我不能说是探索。我只是谷歌了一波,去gayHub找了一个成熟的库,反应过来-i18next,写了一些代码。现在我用详细的代码分享一下过程,教大家如何实现国际化。
渴望看到
让我们看看最终结果:
//.从“@src/i18n”导入i18n;//xxx组件console . log(' hahahahahahaha i18n来发送: ',i18n . t(' INVALID _ ORDER '));//.render () {//.button { i18n . t(' invalid _ order ')} button }控制台:
json中的相应信息:
开始
原则
原理其实很简单:字符串替换。
将远程国际化json文件拉到本地,然后根据语言进行映射。
不用多说,让我们看看代码。
让我们简单看一下目录结构:
首先查看配置中的相关代码:
env.js:
使用"严格";const fs=require(' fs ');const path=require(' path ');const paths=require(' ./path’);const languages=require(' ./languages ');//确保在环境. js之后包含paths.js将被读取环境变量。删除要求。缓存[需要。解析(' ./path ')];const NODE _ ENV=进程。ENV。NODE _ ENVif(!NODE _ ENV){ 0引发新错误(' NODE_ENV环境变量是必需的,但未指定。');}//https://github。com/b keepers/dot NV # what-other-env-files-can-I-usevar dot NV files=[` $ { path。dotenv }].${NODE_ENV}。本地`,`$ { paths.dotenv } .${NODE_ENV} `,//不要将` .' env.local '用于测试环境//,因为通常情况下,您希望测试为每个人NODE_ENV生成相同的//结果!==“test ”`$ { path。dot env }。本地`,路径。dot env,].过滤器(布尔型);//从加载环境变量环境*文件。如果此文件丢失,请使用无声/隐藏警告溺爱永远不会修改任何已经设置的环境变量//。中支持变量扩展。环境文件//https://github.com/motdotla/dot env//https://github。com/motdotla/dot env-expanddotenvfiles。foreach(dot env file={ if(fs。existssync(点环境文件)){ require('点环境-展开')(require('点环境').config({ path : DotEnfile,});}});//我们支持根据“节点_路径”解析模块。//这使您可以在大型mono repots ://https://github。com/face bookincubator/create-react-app/issues/253中的导入中使用绝对路径。//它的工作方式类似于结节自身://https://nodejs。组织/应用编程接口/模块。html # modules _ loading _ from _ global _ folders//请注意,与结节中不同的是,仅使用了来自" NODE_PATH "的*相对*路径。//否则,我们有可能将Node.js核心模块导入应用程序,而不是Webpack垫片//https://github。com/faceboncubator/create-react-app/issues/1023 # issuecomment-265344421//我们还解决了这些问题,以确保使用它们的所有工具都能一致地工作。const appDirectory=fs。realpath同步(进程。CWD());过程。环境。NODE _ PATH=(进程。环境。NODE _ PATH | | ' ').拆分(路径。分隔符)。过滤器(文件夹=文件夹!path.isAbsolute(文件夹))。地图(文件夹=路径。解析(appDirectory,文件夹)).联接(路径。分隔符);//抓取NODE_ENV和REACT_APP_*环境变量,准备通过web包配置。const react _ app=/^react_app_/i中的定义替换将其//注入到应用程序中;函数getClientEnvironment(公共URL){ const raw=object。键(过程。环境).过滤器(键=REACT_APP.test(键))。reduce((env,key)={ env[key]=process。env[key];返回环境;},{ //用于确定我们是否在生产模式下运行。//最重要的是,它将反应切换到正确的模式NODE _ ENV:进程。ENV。NODE _ ENV | | ' development ',//用于在公共的中解析静态资产的正确路径。//例如,img src={ process。环境。PUBLIC _ URL '/img/徽标。png ' }/.//这只能作为逃生口。通常情况下,您会将//图像放入“src”中,并用代码"导入"它们,以获取它们的路径PUBLIC_URL: publicUrl,语言: {参考资料:语言。资源,默认LNG :语言。默认国家/地区:流程。环境。国家});//Stringify所有值,这样我们就可以馈入web pack definepreplugin const stringified={ ' process。env ' :对象。钥匙(原始的).reduce((env,key)={ env[key]=JSON。stringify(原始[key]);返回环境;}, {}), };返回{原始字符串化};}模块。exports=GetClientEnvironment主要看语言相关的代码就好了,其他的都创建-反应-应用的相关配置,不用管。
再看下语言。射流研究…里面的逻辑:
const path=require(' path ');const paths=require(' ./path’);const localesHash=require('./i18n/localesash ');const resourcesHash=require('./i18n/resources hash ');const COUNT=进程。环境。COUNT | | ' SGconst COunt=(COunt).toUpperCase();const defaultLng=localesHash国家/地区][0];const langs=[ 'en ',' id '];const前缀langs=[];常数条目={ };用于(设i=0,len=langs.length我透镜;I){ const前缀lang=` dict _ $ { langs[I]} `前缀langs。push(前缀lang)条目[前缀lang]=路径。解析(路径。appsrc './i18n/locales/$ { langs[I]}。JSON `)} const resources={[DefaultLng]: { common : resources hash[DefaultLng]} }导出。资源=资源;exports.defaultLng=defaultLng逻辑也比较简单,根据语言列表把对应的数据内容加进来。作为示例,这里我设置的是英文和印尼语。
下面看i18n文件里面的内容:
现场里面放的是语言的数据文件,内容大概是:
{ ' msg _ Created ' : ' Pesanan telah terbuat '//.} localesHash.js:
module.exports={ SG: ['en'],id :[' id ']} resources hash。js :
模块。exports={ ' en ' : require(' ./locales/en.json '),id':要求('。/locales/id.json')}index.js
const path=require(' path ')const fs=require(' fs ')const fetch=require('同构-fetch ')常量localesash=require ' ./localesash ')const argv=process。argv。slice(2)const country=(argv[0]| ' ').toUpperCase()const i18nServerURI=locale={ const关键字={ 'en': 'en ',' id': 'id' }常量关键字=关键字[区域设置]返回关键字=='en '?XXX/JSON/download ' : `/$ { keyword }/JSON/download ` } const fetchKeys=async(区域设置)={ const uri=i18nServerURI(区域设置)控制台。日志(`下载$ { locale }密钥. n $ { uri } `)const response=wait fetch(uri)const key=wait response。JSON()返回密钥} const access=async(文件路径)={返回新的Promise((解析,拒绝)={ fs.access(文件路径),(err)={ if(err。代码==' EXIST '){ resolve(true)} resolve(false)} resolve(true)})} const run=async()={ const locales=localesash[country]| | Object .值(localesHash).如果(区域设置===未定义){ console.error('此国家/地区不在服务范围内,则减少(((上一个,当前)=上一个。concat(当前),[])))return } for(const locale of locales){ const keys=wait fetchKeys(locale)const data=JSON。stringify(key,null,2)const directoryPath=路径。解析(_ dirname,' locales') if(!fs.existsSync(目录路径)){ fs.mkdirSync(目录路径)} const文件路径=path。解析(_ dirname,` locales/$ { locale })。JSON `)const isExist=等待访问(文件路径)const operation=isExist?更新' : '创建'控制台。日志(操作)fs。writelefilesync(文件路径,`$ { data } n `)控制台。log(` $ { operation } t $ { file path } `) } } run();再看下科学研究委员会中的配置:
i18nn.js
从i18next '导入i18next '从“首字母大写”导入{第一个字母大写} ./common/helper/util ';const env=process.env让LANguage=process。环境。LANguageLANguage===' string '的类型?JSON。parse(LANguage): LANguage const { DefaultLng,resources }=LANGUAGEi18next .init({ lng: defaultLng,fallbackLng: defaultLng,defaultNS: 'common ',keySeparator: false,debug: env .NODE _ ENV=='开发',资源,插值: {擒纵值: false },react: { wait: false,宾迪18n : '语言已更改已加载',bindStore: '已添加已删除',nsMode: '默认' })函数isMatch(str,substr){ return str。indexof(substr)-1 | | str。tolowercase().indexOf(substr)-1 }导出常量更改语言=(区域设置)={ i18next.changeLanguage(区域设置)}//大写每个单词的第一个字母abcd=Abcd或Abcd EFG=Abcd Efgexport const Tupper=(str,所有单词=true)={返回第一个字母大写(i18next。t(str,所有单词))}//大写所有字母. ABCD=abcdex port const Tupperace=(str)={ return i18next。t(字符串).toUpperCase()} export const load resource=LNG={ let p;返回新的承诺(解析,拒绝)={ if(IsMatch(DefaultLing,LNG))resolve()switch(LNG){ case ' id ' : p=import('./i18n/locales/id。JSON ')break默认值: p=import('./i18n/locales/en。JSON ')} p .然后(数据={ i18next。addresourcebund(LNG,' common ',data) changeLanguage(lng) }).然后(解决)。捕捉(拒绝)})导出默认值i18next//first letterupexport const first letter upper=(str,所有单词=true)={ let tmp=str.replace(/^(.)/g,$ 1=$ 1。touppercase())if(所有单词){ tmp=tmp。替换(/ s(.)/g,$1=$1.toUpperCase()) }返回tmp}这些准备工作做好后,还需要把i18n注入到应用中:
index.js:
从“反应”导入做出反应;从“反应世界”导入{ render };从“反应-还原”导入{提供程序};从""导入rootReducer ./common/redux/reduce ';从""导入{ configureStore } ./common/redux/store ';从“react-router-dom”导入{路由器};从"历史记录/创建浏览故事"导入创建浏览故事;从" react-i18next "导入{ i18 next provider };从'导入i18n ./i18n ';"进口"。/公共/样式/索引。“少”;从""导入应用程序/App ';export const history=createBrowserHistory();const ROOT=文档。GetElEMENTBYID(' ROOT ');render(i18next Provider i18n={ i18n } Provider store={ configureStore(rootdreduce)}路由器历史记录={ history } App//Router/Provider/I18nextProvider,ROOT);如何使用
加入上面的代码后,控制台会有一些原木信息,表示语言已经加载好了。
在具体的业务组件中,使用方法是:
//.从" @src/i18n "导入i18nconsole.log('哈哈哈哈哈i18n来一发: ',i18n。t(' INVALID _ ORder ');控制台中:
对应数据中的信息:
后面你就可以愉快的加各种词条了。
技巧
我们在科学研究委员会中的文件中引入了科学研究委员会目录外的文件,这是创建-反应-应用做的限制,编译会报错,把它去掉就好了:
结语
这里作为例,就是把语言的数据文件下载下来放到现场目录里,如果想实时拉取,要保证文件下载完之后再渲染应用程序。
类似:
load ReSource(GetLocaL()).然后(()={ import(' ./app.js') })当然你也可以免了这一步,直接下载好放到工程里来。
大概就是这样,以上就是实现国际化的全部代码,希望对大家有所帮助。也希望大家多多支持我们。