前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >身在外企,如何实现 React 应用国际化?

身在外企,如何实现 React 应用国际化?

作者头像
神说要有光zxg
发布2024-05-31 20:41:37
1630
发布2024-05-31 20:41:37
举报
文章被收录于专栏:神光的编程秘籍

国际化是前端应用的常见需求,比如一个应用要同时支持中文和英文用户访问

如果你在外企工作,那基本要天天做这件事情,比如我待过韩企和日企,我们的应用要支持韩文和英文,或者日文和英文。

那如何实现这种国际化的需求呢?

用 react-intl 这个包。

这个包周下载量很高:

我们来用一下。

创建个项目:

代码语言:javascript
复制
npx create-vite

我们先安装 antd 来写个简单的页面:

代码语言:javascript
复制
npm install

npm install --save antd

去掉 main.tsx 里的 StrictMode 和 index.css

然后写下 App.tsx

代码语言:javascript
复制
import React from 'react';
import type { FormProps } from 'antd';
import { Button, Checkbox, Form, Input } from 'antd';

type FieldType = {
  username?: string;
  password?: string;
  remember?: string;
};

const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
  console.log('Success:', values);
};

const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => {
  console.log('Failed:', errorInfo);
};

const App: React.FC = () => (
  <Form
    name="basic"
    labelCol={{ span: 8 }}
    wrapperCol={{ span: 16 }}
    style={{ maxWidth: 600 }}
    initialValues={{ remember: true }}
    onFinish={onFinish}
    onFinishFailed={onFinishFailed}
    autoComplete="off"
  >
    <Form.Item<FieldType>
      label="Username"
      name="username"
      rules={[{ required: true, message: 'Please input your username!' }]}
    >
      <Input />
    </Form.Item>

    <Form.Item<FieldType>
      label="Password"
      name="password"
      rules={[{ required: true, message: 'Please input your password!' }]}
    >
      <Input.Password />
    </Form.Item>

    <Form.Item<FieldType>
      name="remember"
      valuePropName="checked"
      wrapperCol={{ offset: 8, span: 16 }}
    >
      <Checkbox>Remember me</Checkbox>
    </Form.Item>

    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
      <Button type="primary" htmlType="submit">
        Submit
      </Button>
    </Form.Item>
  </Form>
);

export default App;

这里是直接从 antd 官网复制的代码。

把服务跑起来:

代码语言:javascript
复制
npm run dev

浏览器访问下:

那如果这个页面要同时支持中文、英文呢?

只要把需要国际化的文案转成一个 key,然后根据当前 locale 是中文还是英文来读取不同的语言包就好了:

locale 是“语言代码-国家代码”,可以从 navigator.language 拿到:

语言包就是一个 json 文件里面有各种 key 对应的不同语言的文案,比如 zh-CN.json、en-US.json 等。

我们用 react-intl 实现下:

在 main.tsx 引入下 IntlProvider,它是用来设置 locale 和 messsages 语言包的:

代码语言:javascript
复制
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import { IntlProvider } from 'react-intl'
import enUS from './en-US.json';
import zhCN from './zh-CN.json';

const messages: Record<string, any> = {
  'en-US': enUS,
  'zh-CN': zhCN
}
const locale = navigator.language;

ReactDOM.createRoot(document.getElementById('root')!).render(
  <IntlProvider messages={messages[locale]} locale={locale} defaultLocale="zh_CN">
    <App />
  </IntlProvider>
)

然后写一下 zh-CN.json 和 en-US.json

代码语言:javascript
复制
{
    "username": "Username",
    "password": "Password",
    "rememberMe": "Remember Me",
    "submit": "Submit",
    "inputYourUsername": "Please input your username!",
    "inputYourPassword": "Please input your password!"
}
代码语言:javascript
复制
{
    "username": "用户名",
    "password": "密码",
    "rememberMe": "记住我",
    "submit": "提交",
    "inputYourUsername": "请输入你的用户名!",
    "inputYourPassword": "请输入你的密码!"
}

把 App.tsx 里的文案换成从语言包取值的方式:

defineMessages 和 useIntl 都是 react-intl 的 api。

defineMessages 是定义 message,这里的 id 就是语言包里的 key,要对应才行。

此外还可以定义 defaultMessage,也就是语言包没有对应的 key 的时候的默认值:

useIntl 有很多 api,比如 formatMessage 的 api 就是根据 id 取不同 message 的。

代码语言:javascript
复制
import React from 'react';
import type { FormProps } from 'antd';
import { Button, Checkbox, Form, Input } from 'antd';
import { useIntl, defineMessages } from 'react-intl';

type FieldType = {
  username?: string;
  password?: string;
  remember?: string;
};

const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
  console.log('Success:', values);
};

const onFinishFailed: FormProps<FieldType>['onFinishFailed'] = (errorInfo) => {
  console.log('Failed:', errorInfo);
};

const messsages = defineMessages({
  username: {
    id: "username",
    defaultMessage: '用户名'
  },
  password: {
    id: "password"
  },
  rememberMe: {
    id: 'rememberMe'
  },
  submit: {
    id: 'submit'
  },
  inputYourUsername: {
    id: 'inputYourUsername'
  },
  inputYourPassword: {
    id: 'inputYourPassword'
  }
})

const App: React.FC = () => {

  const intl = useIntl();

  return <Form
    name="basic"
    labelCol={{ span: 8 }}
    wrapperCol={{ span: 16 }}
    style={{ maxWidth: 600 }}
    initialValues={{ remember: true }}
    onFinish={onFinish}
    onFinishFailed={onFinishFailed}
    autoComplete="off"
  >
    <Form.Item<FieldType>
      label={intl.formatMessage(messsages.username)}
      name="username"
      rules={[{ required: true, message: intl.formatMessage(messsages.inputYourUsername) }]}
    >
      <Input />
    </Form.Item>

    <Form.Item<FieldType>
      label={intl.formatMessage(messsages.password)}
      name="password"
      rules={[{ required: true, message: intl.formatMessage(messsages.inputYourUsername) }]}
    >
      <Input.Password />
    </Form.Item>

    <Form.Item<FieldType>
      name="remember"
      valuePropName="checked"
      wrapperCol={{ offset: 8, span: 16 }}
    >
      <Checkbox>{intl.formatMessage(messsages.rememberMe)}</Checkbox>
    </Form.Item>

    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
      <Button type="primary" htmlType="submit">
      {intl.formatMessage(messsages.submit)}
      </Button>
    </Form.Item>
  </Form>
}

export default App;

试一下:

可以看到,现在文案就都变成中文了。

然后改下 locale:

现在界面又都是英文了:

其他语言也是同理。

但国际化可不只是替换下文案这么简单,日期、数字等的格式也都不一样。

react-intl 包也支持:

代码语言:javascript
复制
<div>
  日期:
  <div>{intl.formatDate(new Date(), { weekday: 'long' })}</div> 
  <div>{intl.formatDate(new Date(), { weekday: 'short' })}</div> 
  <div>{intl.formatDate(new Date(), { weekday: 'narrow' })}</div>
  <div>{intl.formatDate(new Date(), {  dateStyle: 'full' })}</div>
  <div>{intl.formatDate(new Date(), {  dateStyle: 'long' })}</div>
</div>
<div>
  相对时间:
  <div>{intl.formatRelativeTime(200, 'hour')}</div> 
  <div>{intl.formatRelativeTime(-10, 'minute')}</div> 
</div>
<div>
  数字:
  <div>{intl.formatNumber(200000, {
    style: 'currency',
    currency: 'USD'
  })}</div> 
  <div>
    {
      intl.formatNumber(10000, {
        style: 'unit',
        unit: 'meter'
      })
    }
  </div>
</div>

然后换成 zh-CN 再看下:

可以看到,确实不同语言的表示方式不一样:

但这里金额没有切换过来,需要改一下:

代码语言:javascript
复制
<div>{intl.formatNumber(200000, {
    style: 'currency',
    currency:  intl.locale.includes('en') ? 'USD' : 'CNY'
})}</div> 

根据 locale 来分别设置为美元符号 USD 或者人民币符号 CNY。

现在就都对了。

当然,可以国际化的东西还有很多,用到的时候查文档就行:

我们主要用的 useIntl 的 api,然后调用 formatXxx 方法。

其实这些 api 都有组件版本:

代码语言:javascript
复制
<div>
  <div><FormattedDate value={new Date} dateStyle='full'></FormattedDate></div>
  <div><FormattedMessage id={messsages.rememberMe.id}></FormattedMessage></div>
  <div><FormattedNumber style='unit' unit='meter' value={2000}></FormattedNumber></div>
</div>

哪种方便用哪种。

回过头来再看下 message 的国际化。

message 支持占位符,比如这样:

用的时候传入具体的值:

代码语言:javascript
复制
<div>
  <div>{intl.formatMessage(messsages.username, { name: '光'})}</div>
  <div><FormattedMessage id={messsages.username.id} values={{name: '东'}}></FormattedMessage></div>
</div>

此外,国际化的消息还可以用一些 html 标签,也就是支持富文本。

这样:

在 IntlProvider 的 defaultRichTextElements 这里定义所有的富文本标签:

代码语言:javascript
复制
<IntlProvider 
    messages={messages[locale]}
    locale={locale}
    defaultLocale="zh_CN"
    defaultRichTextElements={
      {
        bbb: (str) => <b>{str}</b>,
        strong: (str) => <strong>{str}</strong>
      }
    }
>
    <App />
</IntlProvider>

这样,运行时就会把他们替换成具体的标签:

掌握这些功能,国际化需求就足够用了。

此外,还要注意下兼容性问题:

react-intl 的很多 api 都是对浏览器原生的 Intl api 的封装:

而 Intl 的 api 在一些老的浏览器不支持,这时候引入下 polyfill 包就好了:

那如果我想在组件外用呢?

也可以,用 createIntl 的 api:

src/getMessage.ts

代码语言:javascript
复制
import { createIntl, defineMessages } from "react-intl"
import enUS from './en-US.json';
import zhCN from './zh-CN.json';

const messages: Record<string, any> = {
  'en-US': enUS,
  'zh-CN': zhCN
}

const locale = 'zh-CN'
const intl = createIntl({
    locale: locale,
    messages: messages[locale]
});

const defines = defineMessages({
    inputYourUsername: {
        id: 'inputYourUsername',
        defaultMessage: ''
    }
});

export default function() {
    return intl.formatMessage(defines.inputYourUsername);
}

在 App.tsx 里引入下:

代码语言:javascript
复制
useEffect(() => {
    setTimeout(() => {
      alert(getMessage());
    }, 2000)
}, []);

可以看到,在非组件里也可以做文案的国际化。

还有一个问题,不知道大家有没有觉得把所有需要国际化的地方找出来,然后在语言包里定义一遍很麻烦?

确实,react-intl 提供了一个工具来自动生成语言包。

我们用一下:

代码语言:javascript
复制
npm i -save-dev @formatjs/cli

用这个工具需要所有 message 都有默认值,前面我们省略了,这里改一下:

代码语言:javascript
复制
const messsages = defineMessages({
  username: {
    id: "username",
    defaultMessage: '用户名'
  },
  password: {
    id: "password",
    defaultMessage: '密码'
  },
  rememberMe: {
    id: 'rememberMe',
    defaultMessage: '记住我'
  },
  submit: {
    id: 'submit',
    defaultMessage: '提交'
  },
  inputYourUsername: {
    id: 'inputYourUsername',
    defaultMessage: '请输入用户名!'
  },
  inputYourPassword: {
    id: 'inputYourPassword',
    defaultMessage: '请输入密码!'
  }
})

然后执行 extract 命令从 ts、vue 等文件里提所有 defineMessage 定义的消息:

代码语言:javascript
复制
npx formatjs extract "src/**/*.tsx" --out-file temp.json

然后可以看到我们 defineMessage 定义的所有 message 都提取了出来,key 是 id:

接下来再执行 compile 命令生成语言包 json:

代码语言:javascript
复制
npx formatjs compile 'temp.json' --out-file src/ja-JP.json

可以看到它用所有的 message 的 id 和默认值生成了新的语言包。

这样,只要把这个语言包交给产品经理或者设计师去翻译就好了。

最后把刚才的临时文件删除:

代码语言:javascript
复制
rm ./temp.json

这个 cli 工具对于项目中 defineMessage 定义了很多国际化消息,想要全部提取出来生成一个语言包的场景还是很有用的。

案例代码上传了github

总结

很多应用都要求支持多语言,也就是国际化,如果你在外企,那几乎天天都在做这个。

我们用 react-intl 包实现了国际化。

它支持在 IntlProvider 里传入 locale 和 messages,然后在组件里用 useIntl 的 formatMessage 的 api 或者用 FormatMessage 组件来取语言包中的消息。

定义消息用 defineMessages,指定不同的 id。

在 en-US.json、zh-CN.json 语言包里定义 message id 的不同值。

这样,就实现了文案的国际化。

此外,message 支持占位符和富文本,语言包用 {name}、<xxx></xxx>的方式来写,然后用的时候传入对应的文本、替换富文本标签就好了。

如果是在非组件里用,要用 createIntl 的 api。

当然,日期、数字等在不同语言环境会有不同的格式,react-intl 对原生 Intl 的 api 做了封装,可以用 formatNumber、formatDate 等 api 来做相应的国际化。

如果应用中有很多 defineMessage 的国际化消息,想要批量提取出来生成语言包,可以用 @formatjs/cli 的 extract、compile 命令来做。

掌握了这些功能,就足够实现前端应用中各种国际化的需求了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 神光的编程秘籍 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档