前端i18n最佳实践在React中使用i18next
本合集文章,授权转载,侵权必究。
来源:代码与野兽
本文将会介绍i18n的业界最佳实践。
聊聊什么是i18n?JavaScript中的i18n基本概念
目前业界最流行的i18n方案都需要处理两个东西:资源文件路由资源文件
i18n这个问题在前后端未分离的时代就一直存在。
实现i18n,可以分为后端runtime的策略和前端runtime的策略。
先讲一下后端runtime的策略。
比如在JSP和PHP模板引擎中就有大量基于这种策略实现的i18n。
它会把网页中要显示的内容提前翻译成多种语言。
当用户请求网页时,由用户自己选择语言,或者在第一次用户没选择语言时使用服务端接收到的acceptlanguage请求头字段来确定用户的语言。
之后去加载写死的模板字符串资源文件,去替换文本内容。
拿代码举个大概的例子。
这是原来没有做国际化的代码。这是一段简单的文本
做完国际化后。{t(simpleContent)}
其实就是提供了一个t函数(就是translation的缩写),它可以翻译文本。
资源文件可以是JSON,或者是其他格式的文件,内容大概如下:{en:{simpleContent:Justsimplecontent},zh:{simpleContent:这是一段简单的文本}}
前端runtime的策略原理和上面的类似,就是提前定义一堆JavaScript对象,然后去动态获取它的值而已。
这么看来似乎前端runtime策略似乎更加简单,但在实际上几乎没有什么人会用这种方案,因为它有一个致命缺点,就是不支持SEO。
国际化的目的本来就是服务更多的用户群体,考虑做国际化的站点,基本上也会考虑做SEO。路由
早期的网站很多都没有考虑路由的问题,所有国家的用户都访问同一个网站。但是当中国的用户分享了他老婆网址,他老婆精通中英双语,但是她老婆的浏览器默认是英语,对方想直接看到中文的内容,还需要手动切换语言,很麻烦。而且有些语言的书写和排列都和英语、汉语这类语言不一样,需要单独调整样式。
基于这些需求,现代的网站在做国际化时都喜欢区分路由,比如web。dev的网址,中文访问是这个地址:web。devi18nzhweb,英文访问是这个地址:web。devi18nenweb。
甚至还可以转发网址,比如中国用户访问xxx。cn,美国用户访问xxx。com。最流行的i18n库
从npm周下载量上看,i18next无疑是最流行的i18n库。
i18next之所以如此受欢迎,是因为i18next不仅仅是针对React或者某个框架而设计的,甚至都不是针对Web这个平台而设计的。它可以用在Node。js、Deno、。NET、php、ruby、IOS、Android等一系列平台上。
而且i18next也足够老,从2011年末就开源了,比React、Vue这些前端框架都要老,经过了很长时间的考验,基本上没有什么i18n领域的问题是它解决不了的。在React中使用i18next
为了更好的适配React这个框架,社区又基于i18n开发了reacti18next这个库。
接下来我会完成一个简单的案例,带大家快速学习reacti18next。基本用法
首先创建项目。npxcreatereactappreacti18n
然后安装i18next的依赖项。npmii18nextreacti18nexti18nextbrowserlanguagedetector复制代码i18next提供了翻译的基本能力。reacti18next是i18next的一个插件,用来降低react的使用成本。i18nextbrowserlanguagedetector是用来检测浏览器语言的插件。
创建i18n。js,具体的注释都在代码中。importi18nfromi18next;import{initReactI18next}fromreacti18next;importLanguageDetectorfromi18nextbrowserlanguagedetector;i18n检测用户当前使用的语言文档:https:github。comi18nexti18nextbrowserlanguageDetector。use(LanguageDetector)注入reacti18next实例。use(initReactI18next)初始化i18next配置参数的文档:https:www。i18next。comoverviewconfigurationoptions。init({debug:true,fallbackLng:en,interpolation:{escapeValue:false,},resources:{en:{translation:{这里是我们的翻译文本}}}});exportdefaulti18n;
在index。js中导入i18n。js。import。i18n。js
重新编写App。js中的内容。import{useTranslation,Trans}fromreacti18next;functionApp(){const{t}useTranslation();return(mainp{t(welcome)}Transi18nKeyauthor作者是:code{Date。now()}Transmain);}exportdefaultApp;code
其中使用到了useTranslation这个Hooks,以及Trans这个组件。
useTranslation返回的对象包含一个t方法,这个方法可以翻译文本。
Trans可以翻译一个组件树。
打开浏览器,这时显示下面的内容:
我们去i18n。js中添加翻译。importi18nfromi18next;import{initReactI18next}fromreacti18next;importLanguageDetectorfromi18nextbrowserlanguagedetector;i18n检测用户当前使用的语言文档:https:github。comi18nexti18nextbrowserlanguageDetector。use(LanguageDetector)注入reacti18next实例。use(initReactI18next)初始化i18next配置参数的文档:https:www。i18next。comoverviewconfigurationoptions。init({debug:true,fallbackLng:en,interpolation:{escapeValue:false,},resources:{en:{translation:{这里是我们的翻译文本welcome:Welcometomywebsite,author:Authoris:1codeandbeast1,}},zh:{translation:{welcome:欢迎来到我的网站,author:作者是:1代码与野兽1,}}}});exportdefaulti18n;
现在显示正常了。
接下来我们再来测试浏览器是英文的情况。
我用的是Chrome浏览器,其他浏览器操作步骤类似。
在浏览器设置中搜索languages。
点击addlanguages,选择English。
然后需要Movetothetop,把English设置为首选语言。
重启浏览器。
这时就被翻译成英语了。
语言翻译器
接下来我们实现一个语言切换的功能。import{useTranslation,Trans}fromreacti18next;constlngs{en:{nativeName:English},zh:{nativeName:中文}};functionApp(){const{t,i18n}useTranslation();return(headerselectonChange{(evt){i18n。changeLanguage(evt。target。value)}}{Object。keys(lngs)。map((lng)(optionkey{lng}value{lng}label{lngs〔lng〕。nativeName}style{{fontWeight:i18n。resolvedLanguagelng?bold:normal}}))}selectheadermainp{t(welcome)}Transi18nKeyauthor作者是:code{Date。now()}Transmain);}exportdefaultApp;code
核心代码是useTranslation返回的i18n。changeLanguage方法,这个方法可以修改用户的当前语言。
不过i18nextbrowserlanguagedetector会自动尝试检测浏览器的默认语言,我们可以把用户上次手动选择的语言存储到localStorage中,下次访问页面时使用上次存储的语言作为首选语言。插值表达式
这时你可能会问了,当我们碰到要翻译的内容中存在变量时该怎么办?
i18next提供了插值的用法。
我们在t函数中传递第二个参数,它是一个对象。
比如我们要显示当前时间。
首先安装dayjs。npmiday。js
添加一个元素。p{t(currentTime,{time:dayjs()。format(MMDDYYYY)})}
然后在i18n。js中添加翻译的文本。{resources:{en:{translation:{这里是我们的翻译文本welcome:Welcometomywebsite,author:Authoris:1codeandbeast1,currentTime:Currenttimeis{{time}},}},zh:{translation:{welcome:欢迎来到我的网站,author:作者是:1代码与野兽1,currentTime:当前时间是{{time}},}}}}
插值表达式的语法是使用两个大花括号包裹属性名。格式化
如果你想对插值进行格式化,比如中文的日期显示20220905,英文日期显示050922。该怎么做呢?
i18next提供了格式化的能力。
首先差值表达式支持第二个值,它是格式化器的名字。{resources:{en:{translation:{这里是我们的翻译文本welcome:Welcometomywebsite,author:Authoris:1codeandbeast1,currentTime:Currenttimeis{{time,DDMMYY}},}},zh:{translation:{welcome:欢迎来到我的网站,author:作者是:1代码与野兽1,currentTime:当前时间是{{time,YYYYMMDD}},}}}}
然后添加两个格式化器。i18n。services。formatter。add(DDMMYY,(value,lng,options){returndayjs(value)。format(DDMMYY)});i18n。services。formatter。add(YYYYMMDD,(value,lng,options){returndayjs(value)。format(YYYYMMDD)});
这样就实现了插值文本的格式化。
除了上面介绍的用法外,i18next还有更多的功能,比如语境、复数、命名空间等。这里就不多做介绍了,详细的内容可以参考i18next的文档。将翻译文件与代码进行拆分
通常来说,翻译的工作不是程序员干的,而是有专业的业务团队去负责翻译,因为他们对业务术语等更加了解。
为了方便维护,我们可以选择使用json文件来作为保存翻译文本的资源文件。
首先需要安装一个库:i18nexthttpbackend。npminstalli18nexthttpbackend复制代码
然后修改i18n。js的内容,将这个插件用上,并且删除掉原来的resources字段。importi18nfromi18next;import{initReactI18next}fromreacti18next;importLanguageDetectorfromi18nextbrowserlanguagedetector;importdayjsfromdayjsimportBackendfromi18nexthttpbackend;i18n。use(Backend)检测用户当前使用的语言文档:https:github。comi18nexti18nextbrowserlanguageDetector。use(LanguageDetector)注入reacti18next实例。use(initReactI18next)初始化i18next配置参数的文档:https:www。i18next。comoverviewconfigurationoptions。init({debug:true,fallbackLng:en,interpolation:{escapeValue:false,},});newusagei18n。services。formatter。add(DDMMYY,(value,lng,options){returndayjs(value)。format(DDMMYY)});i18n。services。formatter。add(YYYYMMDD,(value,lng,options){returndayjs(value)。format(YYYYMMDD)});exportdefaulti18n;复制代码
然后在public下面创建locales目录,在这个目录下创建和语言缩写对应的文件夹,其中放置translation。json文件。这个命名是约定好的,backend插件会去按照这个路径请求资源文件。
翻译的内容和resources的内容一模一样。
比如zhtranslation。json的内容如下:{welcome:欢迎来到我的网站,author:作者是:1代码与野兽1,currentTime:当前时间是{{time,YYYYMMDD}}}复制代码
但是这样会让资源文件以异步的方式进行请求,如果网络存在波动,那么有可能请求资源文件失败。
所以我们还要使用Suspense组件处理加载出错。import{Suspense}fromreact;functionApp(){。。。}exportdefaultfunctionWrappedApp(){return(SuspensefallbackloadingSuspense);}复制代码locize平台
i18next有自己的翻译平台locize。app。
这个平台可以帮助我们整合翻译服务、追踪翻译的更改、版本、机器翻译等一系列功能,它会让我们管理翻译资源文件就像管理代码一样。
当我们的全球化业务非常庞大时,采用这个平台来处理i18n问题会更加轻松。
文中介绍的代码部分均为客户端runtime翻译,在更多的实际情况下我们会选择服务端runtime翻译,这时可能会使用nextjs这种SSR框架来处理i18n。
头条创作挑战赛