在 React 项目中使用 React-intl 实现多语言支持

最近在项目中添加了语言国际化的功能。

语言国际化,也有人说成是语言本地化,其实就是为Web App添加多语言,我们的项目当前包含了中文版和英文版,按理来说『逐字替换』也不是多大事儿,但是,这么Low的做法,有钱途吗?

一开始的时候,我考虑的是传统的为整个项目添加config文件,根据不同的语言和地区,加载不同的config文件,就能够达到界面语言切换的目的。当然,也正是因为这个想法太过于幼稚,所以才被称为『一开始』的想法。语言的国际化不仅仅是将界面上的UI文字翻译成另一种语言,还包括了日期&时间显示,数字显示(英文环境下每隔3位一个逗号:1,000),量词的显示(一个苹果是apple,两个苹果就应该是apples),甚至还有一个字符串中间插了一个变量的情况("今天午饭吃了{count}个鸡腿")...

所以这并不只是一个简单的字符替换问题,并且,我们要很方便的导出一个目录,放到word或者page当中,给到其他同事对照着进行翻译工作,这个非常重要!!难道你要让产品经理直接在代码里改么?或者你想一个一个搜索替换?不考虑清楚就干的话,相信我,You'll pay for this。

作为一个有追求的代码家,你肯定不希望在index.html当中增加一行<Script>引用吧?另外,UI中的文字全部都是使用图片的那个同学,请起立,滚。如果想要在一个React项目中,优雅的import something from somewhere,然后将界面中的文字用<首字母大写 /> 组件替代,最后通过简单的配置实现语言的国际化,那我们就用React-intl吧。

注意:本文说的是用法,源码我也没有拜读过,太深的东西去github给作者留言吧。

React-intl

项目地址:https://github.com/yahoo/reac...

React-intl是雅虎的语言国际化开源项目FormatJS的一部分,通过其提供的组件和API可以与ReactJS绑定。上面这句话援引了官方文档的说辞,主要表达的是,这是一个很屌的开源项目,有大团队支持,使用量也很大,不会太坑爹,你们放心用。虽然雅虎都快被收购了。

React-intl提供了两种使用方法,一种是引用React组建,另一种是直接调取API,官方更加推荐在React项目中使用前者,只有在无法使用React组件的地方,才应该调用框架提供的API,事实上,我在项目的过程中真的遇到了无法使用组件的情况,这个我会另外写一篇文章来描述。

React-intl提供的React组件有如下几种:

<IntlProvider /> 包裹在需要语言国际化的组建的最外层,为包含在其中的所有组建提供包含id和字符串的键值对。(如:"homepage.title":"Hommily";

日期时间

a. <FormattedDate /> 用于格式化日期,能够将一个时间戳格式化成不同语言中的日期格式。

传入时间戳作为参数:

<FormattedDate 
    value={new Date(1459832991883)}
/>

输出结果:

<span>4/5/2016</span>

b. <FormattedTime> 用于格式化时间,效果与<FormattedDate />相似。

传入时间戳作为参数:

<FormattedTime 
   value={new Date(1459832991883)}
/>

输出结果:

<span>1:09 AM</span>

c. <FormattedRelative /> 通过这个组件可以显示传入组件的某个时间戳和当前时间的关系,比如 “ 10 minutes ago"。

传入时间戳作为参数:

<FormattedRelative 
    value={Date.now()}
/>

输出结果:

<span>now</span>

10秒之后的输出结果:

<span>10 seconds ago</span>

1分钟之后的输出结果:

<span>1 minute ago</span>

数字量词

a. <FormattedNumber /> 这个组件最主要的用途是用来给一串数字标逗号,比如10000这个数字,在中文的语言环境中应该是1,0000,是每隔4位加一个逗号,而在英语的环境中是10,000,每隔3位加一个逗号。

传入数字作为参数:

<FormattedNumber 
    value={1000}
/>

输出结果:

<span>1,000</span>

b. <FormattedPlural /> 这个组件可用于格式化量词,在中文的语境中,其实不太会用得到,比如我们说一个鸡腿,那么量词就是‘个’,我们说两个鸡腿,量词还是‘个’,不会发生变化。但是在英文的语言环境中,描述一个苹果的时候,量词是apple,当苹果数量为两个时,就会变成apples,这个组件的作用就在于此。

传入组件的参数中,value为数量,其他的为不同数量时对应的量词,在下面的例子中,一个的时候量词为message,两个的时候量词为messages。实际上可以传入组件的量词包括 zero,one,two,few,many,other 已经涵盖了所有的情况。

<FormattedPlural
    value={10}
    one='message'
    other='messages'/>

传入组件的量词参数可以是一个字符串,也可以是一个组件,我们可以选择传入<FormattedMessage />组件,就可以实现量词的不同语言的切换。

输出结果:

<span>messages</span>

字符串的格式化

a. <FormattedMessage /> 这个组件用于格式化字符串,是所有的组件中使用频率最高的组件,因为基本上,UI上面的每一个字符串都应该用这个组件替代。这个组件的功能丰富,除了可以根据配置输出不同语言的简单字符串之外,还可以输出包含动态变化的参数的复杂字符串,具体的用法在后面的例子中会慢慢叙述。

比如我们在locale配置文件中写了如下内容:

const app = {
    greeting:'Hello Howard!",}

export default app;

使用这个组件的时候,我们这么写:

<FormattedMessage
    id='app.greeting'
    description='say hello to Howard'
    defaultMessage='Hello,Howard!'
    />

id指代的是这个字符串在locale配置文件中的属性名,description指的是对于这个位置替代的字符串的描述,便于维护代码,不写的话也不会影响输出的结果,当在locale配置文件中没有找到这个id的时候,输出的结果就是defaultMessage的值。

输出的结果:

<span>Hello,Howard!</span>

b. <FormattedHTMLMessage /> 这个组件的用法和<FormattedMessage />完全相同,唯一的不同就是输出的字符串可以包含HTML标签,但是官方不太推荐使用这个方法,如果可以想办法用<FormattedMessage />的话,就不应该使用这个组件,我揣测应该是性能方面不如<FormattedMessage />,这个组件的用法我就不举例了。

Well,到此为止,已经把React-intl提供的所有组件介绍完了,下面就给大家介绍一下具体如何去使用吧。

React-intl 使用步骤

(本文例子运行在OSX环境,Window操作方法的终端在安装的时候要注意用管理员身份运行)

1. 安装React-intl

假设你已经在你的系统中安装了node.js和npm,如果你还不知道这两个是什么东西,请自行百度,对,在百度都能找到答案。

打开终端,进入项目根目录,输入以下指令安装React-intl:

npm install react-intl -save

注意:为了兼容Safari各个版本,需要同时安装 intl,intl在大部分的『现代』浏览器中是默认自带的,但是Safari和IE11以下的版本就没有了,这里需要留个心眼。

安装intl需要在终端中输入以下指令:

npm install intl --save

这里还有一个注意:由于React-intl的每一个组件的使用方法大同小异,和ReactJS的语法完全一致,所以我就仅仅描述如何使用<FormattedMessage />这个组件的用法,借此抛砖引玉,相信看完之后已经足够帮助你迅速的去使用这个开源框架了。

2. 引用React-intl

import { FormattedMessage } from 'react-intl';

由于我使用的是ES6 的语法,所以是支持直接引用组件的。你当然可以使用ES5的方式引用,但是,这样有前途么?

require ReactIntl from 'react-intl';

3. 创建locale配置文件

这里,我们将文件命名为zh_CN.js和en_US.js,代表中文和美式英语的配置包。

在zh_CN.js编写如下代码:

const zh_CN = {
            hello:"你好,方浩!",
            superHello:"你好,{ someone } !"
        }
export default zh_CN;

在en_US.js编写如下代码:

const en_US = {
            hello:"Hello,Howard!",
            superHello:"Hello,{ someone } !"
        }    
export default en_US;

于是,我们就创建好了locale文件,但是,在实际的项目中配置文件不会这么简单,您可能需要根据业务需求按照不同的页面或者不同的功能块创建不同的文件树,然后用模块化的方法将不同的配置文件进行组织,已达成你的目标,这里我也就没能力逼逼太多了。

你可能想问,{ someone } 是什么鬼?其实悟性高一些的话就应该已经猜到,这个应该就是前面提到过的在字符串中插入动态参数的用法,事实上也是这样的。

4. 使用<IntlProvider />

使用<IntlProvider /> 组件包裹住需要您需要进行语言国际化的组件,用法和React-redux的<Provider />差不多,当<IntlProvider /> 包裹住某个组件的时候,这个组件本身和组件内部包含的子组件就可以获得所有React-intl提供的接口以及在<IntlProvider /> 中引入的locale配置文件的内容。

import React from 'react';
import { render } from 'react-dom';
//引入locale配置文件,具体路径根据实际情况填写
import zh_CN from './zh_CN';
import en-US from './en-US';
//如果浏览器没有自带intl,则需要在使用npm安装intl之后添加如下代码
import intl from 'intl';
addLocaleDate([...en,...zh]);

...
...

render(    
        <IntlProvider 
            locale={'en'} 
            messages={en_US}
        >
            <App />
        </IntlProvider>,document.getElementById('container')
);

<IntlProvider />需要传递两个参数:

locale是传递需要国际化的语言的缩写,通过这个参数可以确定格式化日期,数字,量词的时候按照哪一种语言的规则,这个是规则是intl提供的,一般浏览器会内置这个库,但是在Safari和IE11之前需要自己安装,安装的方法前面已经提及,请自己翻阅。

messages是用于传递刚刚我们在第3步中定义的配置文件的,从示例代码中我们可以看出,首先我们使用Import语句引入了配置文件,然后将配置文件的内容传递给了messages这个参数,此时<App />组件中的所有组件都可以拿到配置文件中的内容了。

那个跳起来的同学,请先坐下,我猜你是想问,是不是每次都要手动修改这两个参数以适配不同语言呢?

其实不然,我们完全可以按照下面的做法自动识别当前浏览器的语言:

chooseLocale(){
    switch(navigator.language.split('_')[0]){
        case 'en':
            return 'en_US';
            break;
        case 'zh':
            return 'zh_CN';
            break;
        ...
        ...
        ...
        default:
            return 'en_US';
            break;
    }
}
render(    
        <IntlProvider 
            locale={navigator.language} 
            messages={chooseLocale()}
            >
            <App />
        </IntlProvider>,document.getElementById('container')
);

您还需要知道的是,<IntlProvider />是可以嵌套使用的,也就是说,在一个<IntlProvider />内部还可以有N个<IntlProvider />,这个功能的实际意义就是可以在英文网站中嵌套一个中文的或者德语的或者法语的板块,应用起来会更加灵活一些。

5. 使用<FormattedMessage />

前面的几个步骤其实都是为了这个步骤做铺垫的,在添加了<IntlProvoder />之后,我们就可以在其包裹的<App />及<App />包含的所有组件中获取到配置文件的信息,传入<FormattedMessage />组件的id参数也能其在配置文件中对应的字符串了。

使用的方法如下:

<FormattedMessage
    id='hello'
    description='say hello to Howard.'
    defaultMessage='Hello,Howard'
    />

在Js执行的时候,组件就会找到配置文件中,‘hello'键名对应的字符串'Hello,Howard!'.

输出的结果为:

<span>Hello,Howard!</span>

那么如何输出含有动态参数的字符串呢?比如Hello,Johnson!,如果我要问候的对象是一个变量呢?

那就这么写呗。。

<FormattedMessage
    id='superHello'
    description='say hello to Howard.'
    defaultMessage='Hello,{someone}'
    values={
        someone:this.props.name,}
    />

以上的例子中,赋给someone的就是一个变量(假设这个变量是通过参数传进这个组件的),注意,如果是这样的话,那么locale配置文件中就要这么写。

superHello:"你好,{ someone } !"

前面其实提过了,怕你忘了...我已经悄无声息的把id换成了superHello。

更牛逼的是,这个someone还可以包含HTML标签!

<FormattedMessage
    id='superHello'
    description='say hello to Howard.'
    defaultMessage='Hello,{someone}'
    values={
        someone:<b>this.props.name</b>,}
    />

输出结果:

<span>Hello,<b>Howard</b>!</span>

于是,这个名字就被加粗了。

眼尖的同学又要跳起来了,“webFunc,为什么所有的输出都带一个<span>标签,我就不能换成别的么?”

不要着急,我正要说这个,对于这个问题,官方的文档是这么说的。

By default <formattedMessage> will render the formatted string into
a <span>. If you need to customize rendering,you can either wrap it
with another React element (recommended),specify a different tagName
(e.g.,'div'),or pass a function as the child.

翻译过来就是,默认的是会包裹在<span>标签中的,如果想要让输出的字符串包裹在其他标签中的话,比如你想包裹在<div>中,你就把<FormattedMessage />组件包含在一对<div>中间,这是一种官方更加推荐的做法。

<div>
<FormattedMessage
    id='hello'
    description='say hello to Howard.'
    defaultMessage='Hello,Howard!"
    />
</div>

Well,that's stupid...

或者你可以给<FormattedMessage>传入一个tagName的参数。比如:

<FormattedMessage
    id='hello'
    tagName = 'div'
    description='say hello to Howard.'
    defaultMessage='Hello,Howard!'
    />

就会输出:

<div>Hello,Howard!</div>

比较奇葩的是,也是我揣测作者不推荐使用这种方法的原因是...只要你高兴,tagName可以传入任意字符串,比如 shit:

<FormattedMessage
    id='hello'
    tagName = 'shit'
    description='say hello to Howard.'
    defaultMessage='Hello,Howard!'
    />

就会输出:

<shit>Hello,Howard!</shit>

Yes,shit happens.

看到这里,你应该已经会使用React-intl对你的项目进行语言国际化了,没有进一步描述的地方,请自行查阅官方文档(项目地址:https://github.com/yahoo/reac...,或者给我留言,虽然我不一定会及时回复。

--写在后面:

语言国际化应该是一个比较经常遇到的需求,但是我在完成项目的过程中,看到的中文的资料却相当少,虽然这不是一篇非常牛叉的技术文章,但是可能会帮到很多人,如若如此,也便满足了。
——方浩(webFunc)

对了,你可以关注一下我的微信公众号:webcoding

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom