本文讲解如何使用 React-Intl 库给你的 React 应用做国际化。
安装
React 项目默认你已经搭建好了。可以用过气的 CRA,流行的 vite,或者用 React 推荐的 Next.js。
安装 React-Intl。
或者用 yarn。
使用
接着我们需要将国际化能力注入到 React 应用中。
使用的是经典的 context 上下文方案:
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(
<IntlProvider locale={locale} messages={messageMap[locale]}>
<App />
</IntlProvider>
);
IntlProvider 是一个 context.provider 组件,用来注入一些国际化需要的信息。其中最重要的信息是 locale 和 messages。
locale 是一个字符串,代表一个语言标识,使用了 BCP 47 语言标记规范。比如中文用 zh
,英文用 en
,简体用 zh-CN
。
标识符是会提供给浏览器自带的 Intl 对象使用的,Intl 对象的方法会通过它确定如何做转换,比如不同国家数字的分隔符是不同的。
可以看到,原点、逗号、空格,什么都有。所以你要传一个正确的语言标识符。
messages 就简单多了,就是一个 id 到 value 的映射,组件中传一个 id,就能拿到对应的语言文案了。比如
const zh = {
'command.undo': '撤销',
'command.redo': '重做',
'arrange.forward': '上移一层',
'arrange.backward': '下移一层',
'arrange.front': '移到顶部',
'arrange.back': '移到底部',
'zoom.zoomIn': '放大',
'zoom.zoomOut': '缩小'
};
const en = {
'command.undo': 'Undo',
'command.redo': 'Redo',
'arrange.forward': 'Forward',
'arrange.backward': 'Backward',
'arrange.front': 'Bring to Front',
'arrange.back': 'Send to Back',
'zoom.zoomIn': 'Zoom In',
'zoom.zoomOut': 'zoom Out',
};
更具体的引入写法:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { IntlProvider } from 'react-intl';
import { zh } from './locale/zh';
import { en } from './locale/en';
const messageMap = {
zh,
en,
};
const getLocale = () => {
const locale = navigator.language;
return locale.startsWith('zh') ? 'zh' : 'en'; // en 是兜底语言
};
const locale = getLocale();
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(
<IntlProvider locale={locale} messages={messageMap[locale]}>
<App />
</IntlProvider>
);
- 计算一个语言标识符,因为网站支持的语言可能就两三种,当用户客户端的语言不在范围内时,给一个兜底的。
- 确认语言后,找出对应的 messages 对象。
然后是一些其他的点:如果语言很多,可以考虑做语言包懒加载。另外还要做用户修改语言后,更新 locale 和 messages props。
组件
然后是在业务组件中使用国际化文案。
最常用的就是 FormattedMessage 组件了,给 id props 提供 id 即可。
<FormattedMessage id="command.undo" />
如果你需要得到文案字符串,传入到一些组件中,比如 tooltip,你可以用主流的 hook 写法:
import { useIntl } from 'react-intl';
const intl = useIntl();
<ToolBtn tooltipCnotallow={intl.formatMessage({ id: 'tool.select' })}><IconSelect /></ToolBtn>
或者可以用 HOC(高阶组件),我没用过,我不写类组件。
类型安全
如果你用 TypeScript,你会希望传入的 id 是有类型的,反正写错 id。React-Intl 提供了全局类型的设置。你只需要这样写:
import { en } from './locale/en';
import { zh } from './locale/zh';
declare global {
namespace FormatjsIntl {
interface Message {
ids: (keyof typeof zh) & (keyof typeof en)
}
}
}
keyof typeof zh 就是将中文 message 对象的 key 提取为一个联合类型。
这里我巧妙地用了一个 & 交叉类型,这样不在二者 id 的交集内的 key 会被排除在外,防止使用一个没有在所有语言中都被定义的 id。
locale 也可以设置类型,防止错误设置一些不支持的语言标识。如下:
declare global {
namespace FormatjsIntl {
interface IntlConfig {
locale: 'en' | 'zh'
}
}
}
可能你希望 en 和 zh 的 key 要一致,我这里想到一个比较巧妙的做法。en 作为兜底语言,用 typeof 拿到类型,作为其他语言的类型,防止 id 漏写。
const en = {
'delete': 'Delete',
}
// zh 对象的类型是 typeof en
const zh: (typeof en) = {
'delete': '删除',
}
message 不支持嵌套
React-Intl 的 message 是不支持嵌套的。
你可以这样写:
const zh = {
'command.undo': '撤销',
'command.redo': '重做',
}
但不能这样写:
const zh = {
'command': {
'undo': '撤销',
'redo': '重做',
}
}
这个我是支持的。
嵌套并无必要,我们加前缀就好了,并且方便全局搜索,能够快速定义一个国际化 id 的文本位置。类似 Vue 框架会做大量的驼峰和连线符的变换,全局搜索非常不友好,我不喜欢。
如果你喜欢嵌套的风格,可以考虑写一些脚本,支持将嵌套的转换为拍平格式。
结尾
以上是 React-Intl 的一些入门用法。