React Intl: 将你的React应用程序国际化

7,276 阅读17分钟

随着全球化程度的提高,为不同地区和地域的广泛受众编写React应用程序意味着要让它可以跨语言访问。

通过国际化功能,React Intl库提供了将文件文本正确翻译成其他语言的机制。

在本指南中,我们将学习如何使用React Intl库来在React项目中设置国际化功能。我们将创建一个简单的应用程序,允许用户在查看应用程序时选择并看到他们喜欢的语言。

我们还将在浏览器的存储中持久化所选择的语言,这样在页面刷新后或后续访问时,内容仍然可用。

下面是我们将共同建立的最终产品。

React Intl End Product

最终完成的网页互动,以熟悉界面,有了这个,我们就开始吧

国际化和本地化

如前所述,React Intl允许我们在React应用中设置国际化。但究竟什么是国际化?

国际化是指设计一个产品--在这里是指React应用--以便在不同地区使用的过程。它通常被缩写为Intl或i18n。

相比之下,本地化,缩写为l10n,重点是将国际化的应用程序从其原始语言翻译成特定的语言。

但是,嘿,翻译不仅仅是将文本或信息转换为目标语言。文化差异也必须加以考虑,比如数字和日期是如何写的,或者不同地区的单位和货币是如何放置的。

幸运的是,有了React Intl库,我们可以无缝地实现我们想要的结果。

设置React项目

让我们先从这个GitHub仓库中获取我们的简单React项目--其默认内容为英文。然后,我们可以添加对另外三种语言的支持。法语、德语和日语。

因此,前往终端,切换到一个目录来保存项目,并运行以下命令来下载启动文件。

git clone https://github.com/Ibaslogic/i18n_react_intl_starter

一旦项目文件被启动,用一个代码编辑器打开它们。我们必须在项目目录内,运行npm install ,为项目创建一个node_modules 的文件夹。

确保计算机上安装了Node.js以完成本教程。

现在,使用npm start ,启动开发服务器。我们应该在浏览器中看到我们的应用程序被加载到http://localhost:3000

App loaded in the browser

如果我们看一下项目的文件结构,我们应该有以下内容。

project_folder
    ├── node_modules
    ├── public
    ├── src
    │    ├── components
    │    │      ├── App.js
    │    │      ├── Content.js
    │    │      ├── Footer.js
    │    │      └── Header.js
    │    ├── index.css
    │    └── index.js
    ├── .gitignore
    ├── package-lock.json
    ├── package.json
    ├── README.md
    └── yarn.lock

很好。现在我们已经在同一个页面上了,让我们看看如何在我们的应用程序中使用React Intl库。

设置React Intl库

要使用这个库,我们必须通过停止开发服务器并从终端运行以下命令来安装它。

npm install react-intl

这个库为我们提供了在应用程序中实现国际化所需的所有API和组件。

让我们用库中的一个provider 组件来包装我们的顶级应用或根组件;在这种情况下,我们将使用<IntlProvider>

什么是IntlProvider 组件?

顾名思义,IntlProvider ,确保为树上的子组件提供或提供重要的配置。

这些来自React Intl的子组件被称为formatted 组件。它们负责在运行时进行适当的翻译和格式化。这些组件是。

  • FormattedMessage
  • FormattedNumber
  • FormattedDate
  • FormattedPlural
  • 还有很多

对于使用React Intl库的国际化项目,经常使用FormattedMessage 组件,允许用户对简单到复杂的字符串和消息进行翻译和格式化。我们一会儿就会使用这个。

回到我们的项目中,让我们打开父组件文件src/components/App.js ,并用IntlProvider 来包装子组件。

确保在文件的顶部从react-intl ,导入IntlProvider

我们的文件应该看起来像这样。

...
import { IntlProvider } from "react-intl";

const App = () => {
  return (
    <IntlProvider>
      <div>
        <Header />
        <Content />
        <Footer />
      </div>
    </IntlProvider>
  );
};

export default App;

让我们花点时间,检查一下IntlProvider 组件返回的内容。首先,记录该组件。

...
const App = () => {
  console.log(IntlProvider);

  return (
    ...
  );
};
...

然后,检查浏览器的控制台。

Default Configuration Props

通过关注defaultProps ,我们看到IntlProvider 所使用的所有默认配置道具。该组件还告诉我们要配置一个locale。

因此,让我们通过更新该组件来添加所需的道具,以识别和显示翻译到IntlProvider ,包括以下道具。

return (
  <IntlProvider messages={{}} locale="en" defaultLocale="en">
    <div>
      <Header />
      <Content />
      <Footer />
    </div>
  </IntlProvider>
);

通过添加locale 道具并保存,错误立即消失了。

这个locale ,它接受一个字符串,决定了我们的应用程序是以什么语言呈现的。我们将根据用户在前台选择的内容动态地添加这个值。

messages 对象包含一组准备在前台显示的翻译字符串。这些也将根据当前的语言环境动态地添加。

最后,defaultLocale 道具是默认的locale,应该与应用程序的默认语言相匹配。

在我们为我们的项目加载翻译好的信息之前,让我们玩一玩,熟悉一下格式化的组件。

打开src/components/Footer.js 文件,在return 语句中添加这些格式化组件。

// ...
import { FormattedDate, FormattedNumber, FormattedPlural } from "react-intl";

const Footer = () => {
  // ...
  return (
    <div className="container mt">
      {/* ... */}

      <FormattedDate value={Date.now()} />
      <br />
      <FormattedNumber value={2000} />
      <br />
      <FormattedPlural value={5} one="1 click" other="5 clicks" />
    </div>
  );
};

// ...

接下来,保存该文件并检查前端。我们应该有这样的显示。

5/29/2021 // your current date
2,000
5 clicks

locale 如果我们把IntlProvider 的道具改为de ,并再次保存文件,我们就有了德语格式的数据。

29.5.2021

2.000

这真是太棒了。React Intl正在通过格式化组件(FormattedDateFormattedNumber, 分别)根据所选区域对日期和数字进行格式化。

这些格式化组件有一个value 道具,接受要格式化的数据。

我们可以通过利用它的其他道具来进一步定制这些组件。例如,我们可以有以下内容。

return (
  <div className="container mt">
    {/* ... */}

    <FormattedDate
      value={Date.now()}
      year="numeric"
      month="long"
      day="2-digit"
    />
    <br />
    <FormattedNumber value={2000} style={`currency`} currency="USD" />
    <br />
    {/* ... */}
  </div>
);

然后,我们会得到这样的英语(en)。

May 29, 2021 // your current date

$2,000.00

然后我们会得到这样的德语(de)。

29. Mai 2021

2.000,00 $

除了这两个组件外,我们还有FormattedPlural 组件,它允许我们在我们的应用程序中处理复数。正如在上面的例子中所看到的,该组件根据它收到的值选择复数类别。

使用React Intl API

另一种方法是通过React Intl API来格式化我们的数据。虽然在React中使用组件方法是首选,因为它可以与其他React组件无缝工作,但在有些情况下需要使用API方法,例如当一个元素的placeholder,title, 或aria-label 必须被翻译。

任何时候我们使用格式化组件来渲染React元素,它们都会在幕后使用React Intl API。

让我们看看这个API是如何工作的。还是在Footer.js 文件中,从react-intl 中导入useIntl Hook 。

import { FormattedDate, FormattedNumber, FormattedPlural, useIntl } from "react-intl";

确保这是在一个功能性的React组件中完成的。

如果我们在控制台中记录useIntl Hook,我们应该看到所有需要格式化我们数据的可用函数。

// ...
const Footer = () => {
  // ...
  const intl = useIntl();
  console.log(intl);

  return (
    <div className="container mt">
      {/* ... */}
    </div>
  );
};
// ...
>

其中一个函数是formatDate ;让我们来应用它。

更新Footer.js 文件中的return 语句以包括该函数。

// ...
const Footer = () => {
  // ...
  const intl = useIntl();
  return (
    <div className="container mt">
      {/* <... */}
      <br />
      <input placeholder={intl.formatDate(Date.now())} />
    </div>
  );
};
// ...

检查前台,看到输入字段。locale 记住要改变IntlProvider 的值,看看它是如何工作的。

我们可以通过添加可选的配置来进一步定制。

<input
  placeholder={intl.formatDate(Date.now(), {
    year: "numeric",
    month: "long",
    day: "2-digit",
  })}
/>

现在我们已经熟悉了这些格式器,让我们回到我们的项目中。

翻译应用程序的源文本字符串

为了支持其他地区,我们必须翻译源文本。对于本教程,让我们使用谷歌翻译

src 目录中创建一个名为i18n 的文件夹。在这个文件夹中,创建两个不同的文件,名为locales.jsmessages.jslocales.js 文件将保存我们支持的语言,而messages.js 文件将保存相应的翻译。

i18n/locales.js 文件中添加这个内容。

export const LOCALES = {
  ENGLISH: "en-US",
  JAPANESE: "ja-JA",
  FRENCH: "fr-FR",
  GERMAN: "de-DE",
};

这是必要的,以避免手动添加本地化。要访问en-US ,例如,我们将使用[LOCALES.ENGLISH]

代码从哪里来?

代码en-US 来自于language-COUNTRY 代码,尽管我们也可以直接使用 [language](http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/gettext.html#Language-Codes)代码,而不添加 [COUNTRY](http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/gettext.html#Country-Codes)代码。

然而,在多个国家有相同语言的情况下,添加COUNTRY 澄清器可能是有用的。例如,美国的英语将是en-US ,英国的英语将是en-GB

React中的翻译

现在,在i18n/messages.js 文件中,添加以下翻译。

import { LOCALES } from "./locales";

export const messages = {
  [LOCALES.ENGLISH]: {
    learn_to: "Hello, let's learn how to use React-Intl",
    price_display:
      "How {n, number, ::currency/USD} is displayed in your selected language",
    number_display:
      "This is how {n, number} is formatted in the selected locale",
    start_today: "Start Today: {d, date}",
    // menu
    about_project: "About the project",
    contact_us: "Contact us",
  },
  [LOCALES.FRENCH]: {
    learn_to: "Bonjour, apprenons à utiliser React-Intl",
    price_display:
      "Comment {n, number, ::currency/USD} $ s'affiche dans la langue sélectionnée",
    number_display:
      "Voici comment {n, number} sont formatés dans les paramètres régionaux sélectionnés ",
    start_today: "Commencez aujourd'hui: {d, date}",
    // menu
    about_project: "À propos du projet",
    contact_us: "Contactez-nous",
  },
  [LOCALES.GERMAN]: {
    learn_to: "Hallo, lass uns lernen, wie man React-Intl benutzt",
    price_display:
      "Wie {n, number, ::currency/USD} in Ihrer ausgewählten Sprache angezeigt wird",
    number_display:
      "Auf diese Weise werden {n, number} im ausgewählten Gebietsschema formatiert",
    start_today: "Beginnen Sie heute: {d, date}",
    // menu
    about_project: "Über das Projekt",
    contact_us: "Kontaktiere uns",
  },
  [LOCALES.JAPANESE]: {
    learn_to: "こんにちは、React-Intlの使い方を学びましょう",
    price_display:
      "選択した言語で{n, number, ::currency/USD}がどのように表示されるか",
    number_display:
      "これは、選択したロケールで{n, number}がフォーマットされる方法です。",
    start_today: "今日から始める:{d, date}",
    // menu
    about_project: "プロジェクトについて",
    contact_us: "お問い合わせ",
  },
};

这个文件包括我们的应用程序源文本和所支持的地区的翻译。注意每一个独特的ID和键,它们可以被命名为任何东西;我们将使用它们将其相应的字符串注入我们的应用程序。

现在,让我们专注于简单的字符串,忽略大括号中的参数,也称为占位符。

接下来,让我们在IntlProvider 组件的message 道具中加载数据。

我们可以从数据中看到,我们可以像这样访问英语翻译对象。

messages[LOCALES.ENGLISH]

这同样适用于其他地区。

所以,让我们把这个英文翻译分配给provider 组件的messages 道具。在本指南的后面,我们将定义一个逻辑,根据用户选择的语言环境,动态地注入翻译的信息。

components/App.js 文件中,在文件的顶部导入LOCALESmessages

import { LOCALES } from "../i18n/locales";
import { messages } from "../i18n/messages";

然后,更新<IntlProvider> ,所以我们有以下内容。

const App = () => {
  const locale = LOCALES.ENGLISH;

  return (
    <IntlProvider
      messages={messages[locale]}
      locale={locale}
      defaultLocale={LOCALES.ENGLISH}
    >
      ...
    </IntlProvider>
  );
};

export default App;

从代码中,我们可以推断出英语数据是在provider 组件中加载的,可以通过子组件访问。

如前所述,我们将使用FormattedMessage 来格式化这些复杂的字符串。如果我们的信息结合了日期、数字或只是简单的信息,这个组件是理想的。

我们将从转换简单的字符串开始,"你好,让我们学习如何使用React-Intl"。

前往components/Content.js 文件并导入FormattedMessage

import { FormattedMessage } from "react-intl";

然后,用该组件替换该字符串。

import { FormattedMessage } from "react-intl";

const Content = () => {
  return (
    <div className="container hero">
      <h1><FormattedMessage id="learn_to" /></h1>
      {/* ... */}
    </div>
  );
};

export default Content;

就这么简单。为了测试我们的工作,我们可以手动将App.js 文件中的locale 改为JAPANESE

const locale = LOCALES.JAPANESE;

保存并在前台看到变化。

Return The Corresponding Key To The Current Locale

上面代码中使用的FormattedMessage ,需要一个id ,其值与翻译文件中的一个特定键相匹配,然后返回当前locale的相应字符串。记住,这个id 在翻译文件中所有支持的语言中是唯一的。

使用参数

当我们看到一个包含日期、金额或数字的消息时,我们要用参数来代替它们。

早些时候,我们学习了如何使用FormattedDateFormattedNumber 来格式化这些类型的值。但在这里我们将使用FormattedMessage ,因为这些值是字符串的一部分。

如果我们看一下翻译文件,我们是按照这个模式来替换这些值类型的:{ key, type, format }

例如,我们有这样的东西。

price_display:
  "How {n, number, ::currency/USD} is displayed in your selected language",

第一个元素,n ,是关键,它向数据对象查找适当的数据。第二个元素是要解释的数据类型,它符合特定的地域性。

第三个参数--是可选的--允许我们指定关于元素类型的额外信息。

在上面的案例中,我们说number 的占位符必须被格式化为美元货币。然后,React Intl定位货币。你可以在这里找到货币代码的列表

使用FormattedMessage 来转换这种类型的字符串,我们会有这样的结果。

<FormattedMessage id="price_display" values={{ n: 59.99 }} />

正如预期的那样,id 找到了当前地区的翻译,占位符被key 的值所取代。

如果我们在我们的项目中应用这个逻辑,components/Content.js 文件现在看起来像这样。

import { FormattedMessage } from "react-intl";

const Content = () => {
  return (
    <div className="container hero">
      <h1><FormattedMessage id="learn_to" /></h1>
      <p><FormattedMessage id="price_display" values={{ n: 59.99 }} /></p>
      <p><FormattedMessage id="number_display" values={{ n: 2000 }} /></p>
      <p><FormattedMessage id="start_today" values={{ d: new Date() }} /></p>
    </div>
  );
};

export default Content;

保存该文件,并通过将component/App.js 文件中的locale改为另一种支持的语言来测试该应用程序。

const locale = LOCALES.JAPANESE;

我们的应用程序应该看起来像这样。

Change The Locale To Another Language

使用React Intl的复数化

早些时候,我们学习了如何使用<FormattedPlural> 组件来处理一个简单的复数文本。在本节中,我们将使用<FormattedMessage> 来实现富文本信息中的复数。

目前,如果我们在前台点击计数按钮,当计数为单数时,也就是在1 ,我们没有显示正确文本的逻辑。

当我们使用{ key, type, format } 模式对货币进行格式化时,我们可以使用相同的模式,但使用pluralmatches 来分别替换typeformat

通过应用这个模式,我们可以在我们的英语翻译对象中添加以下内容。

click_count: "You clicked {count, plural, one {# time} other {# times}}"

复数类别,oneother ,与第一和第二个大括号内的单数和复数形式相匹配,前面是# ,代表计数编号。

如果我们将消息翻译成其他地区的语言,并应用同样的逻辑,我们会有以下的德语。

click_count:
      "Sie haben {count, plural, one {# Mal} other {# Mal}} geklickt",

对于法语。

click_count:
  "Vous avez cliqué {count, plural, one {# fois} other {# fois}}",

对于日语。

click_count: "{count, plural, one {# 回} other {# 回}}クリックしました",

保存文件并打开components/Footer.js 文件,使用FormattedMessage 组件渲染翻译。

然后,找到这个元素。

<p>You clicked {count} times</p>

将上述内容替换为以下内容。

<p>
  <FormattedMessage id="click_count" values={{ count: count }} />
</p> 

我们还必须从react-intl ,导入FormattedMessage 组件。

import {
  // ...
  FormattedMessage,
} from "react-intl";

再次保存该文件。让我们重新加载前端并测试我们的工作。

我们现在可以为菜单项添加翻译支持。首先,像这样导入components/Header.js 文件中的FormattedMessage

import { FormattedMessage } from "react-intl";

我们需要访问翻译文件中使用的各个菜单key 。要做到这一点,让我们更新menu 数组以包括key

const menu = [
  {
    key: "about_project",
    // ...
  },
  {
    key: "contact_us",
    // ...
  },
];

我们现在可以使用键来访问FormattedMessage 中的翻译。

首先,找到下面的代码。

{menu.map(({ title, path }) => (
  <li key={title}>
    <a href={path}>{title}</a>
  </li>
))}

然后,用这个替换。

{menu.map(({ title, path, key }) => (
  <li key={title}>
    <a href={path}>
      <FormattedMessage id={key} />
    </a>
  </li>
))}

保存该文件并测试该应用程序。

为了使前端按钮及其行动呼吁文本本地化,让我们在i18n/message.js 文件中为按钮分配键。

对于英语对象,添加以下内容。

click_button: "Please click the button below",
click_here: "click here",

对于法语,添加这个。

click_button: "Veuillez cliquer sur le bouton ci-dessous",
click_here: "Cliquez ici",

使用上述代码和谷歌翻译,为其他地区添加翻译,如日语和德语。

一旦我们完成,让我们打开components/Footer.js ,用FormattedMessage 替换文本字符串。

return (
  <div className="container mt">
    {/* Footer content here */}
    <p><FormattedMessage id="click_button" /></p>
    <button onClick={onChange}>
      <FormattedMessage id="click_here" />
    </button>
    {/* ... */}
  </div>
);

保存文件并重新加载前台。注意,按钮的描述和按钮的文字都以法语更新。

Updated in French app

在前台添加切换语言的选项

为了在前端提供一个切换语言的选项,我们必须打开components/Header.js 文件,并在return 语句上方添加以下内容。

// Languages
const languages = [
  { name: "English", code: LOCALES.ENGLISH },
  { name: "日本語", code: LOCALES.JAPANESE },
  { name: "Français", code: LOCALES.FRENCH },
  { name: "Deutsche", code: LOCALES.GERMAN },
];

然后,在文件的顶部导入LOCALES

import { LOCALES } from "../i18n/locales";

接下来,我们将循环浏览languages 数组,生成我们的下拉菜单。

还是在文件中,找到这个div 容器元素。

<div className="switcher">
  {/* Language switch dropdown here */}
</div>

然后,更新它,看到以下内容。

<div className="switcher">
  {/* Language switch dropdown here */}
  Languages{" "}
  <select>
    {languages.map(({ name, code }) => (
      <option key={code} value={code}>
        {name}
      </option>
    ))}
  </select>
</div>

保存它并在src/index.css 文件中添加这个简单的样式。

.switcher select {
  width: 99px;
  height: 30px;
}

再次保存该文件,并在前端查看下拉菜单。目前,在不同的语言之间切换并不改变页面内容,所以让我们来解决这个问题。

如果熟悉React的表单处理,这个过程应该是小菜一碟。如果不熟悉,那也没关系,因为我们会一起做。

在React中,表单输入应该是一个受控的输入,与HTML表单不同。

为了做到这一点,我们将在select 元素上添加一个value 的道具和一个onChange 事件。value 道具被指定为当前的语言环境,而onChange 则根据用户的选择来更新数值。

同时,更新select 元素以包括这些道具。

<select onChange="" value="">
  ..
</select>

由于IntlProvider 是在父组件中,App.js 也必须知道当前的locale,所以让我们在文件中设置好逻辑。

我们可以通过props将必要的数据下传到Header 子组件中。这个过程被称为道具钻取。

打开components/App.js ,添加一个默认语言为英语的状态。

import { useState } from "react";
...
const App = () => {
  const locale = LOCALES.ENGLISH;

  const [currentLocale, setCurrentLocale] = useState(locale);

  return (
    <IntlProvider
      messages={messages[currentLocale]}
      locale={currentLocale}
      defaultLocale={LOCALES.ENGLISH}
    >
      ...
    </IntlProvider>
  );
};

export default App;

不要忘记从React导入useState Hook,以获得当前的locale。

现在,让我们把locale传递给Header 子组件,并在select 元素中使用它。

App.js 文件中,更新<IntlProvider /> 中的<Header /> 实例,所以我们有以下内容。

<Header currentLocale={currentLocale}/>

保存文件并打开Header.js ,以访问数据;将其传递给select 元素的value 道具。

...
const Header = (props) => {
  ...
  return (
    <header>
      <div className="container header_content">
        ...
        <nav>
          ...
        </nav>
        <div className="spacer"></div>
        <div className="switcher">
          {/* Language switch dropdown here */}
          Languages{" "}
          <select onChange="" value={props.currentLocale}>
           ...
          </select>
        </div>
      </div>
    </header>
  );
};

export default Header;

调用props ,如下所示。

const Header = (props) => { 

接下来,我们将使用onChange 事件来处理更新。

App.js 文件中,在return 语句上方添加以下处理程序,并通过道具将其向下传递给Header 组件。

const handleChange = (e) => {
  setCurrentLocale(e.target.value);
};

现在的代码看起来像这样。

...
const App = () => {
  ...
  const handleChange = (e) => {
    setCurrentLocale(e.target.value);
  };

  return (
    ...
      <div>
        <Header currentLocale={currentLocale} handleChange={handleChange} />
        ...
      </div>
    ...
  );
};

保存文件并访问Header 组件内的处理程序。

简单地更新select 元素,所以我们有以下内容。

<select onChange={props.handleChange} value={props.currentLocale}>

现在我们可以测试我们的应用程序了。它成功了!很好。

确保将下拉列表旁边的语言文本也翻译出来。

在浏览器的本地存储中保留所选的语言环境

目前,如果我们从默认的语言环境中切换出来并重新加载页面,内容就会恢复到默认状态。我们不希望这样。

在这一节中,我们将看到如何轻松地将所选的区域设置持久化到浏览器的存储中。这意味着,在重新加载页面时,以及在以后的访问中,用户所喜欢的语言内容仍然可以在页面上看到,从而进一步改善用户的体验。

首先,打开App.js 文件,在return 语句上方添加这段代码。

//localstorage
function getInitialLocal() {
  // getting stored items
  const savedLocale = localStorage.getItem("locale");
  return savedLocale || LOCALES.ENGLISH;
}

然后,更新状态以使用这个函数。

const [currentLocale, setCurrentLocale] = useState(getInitialLocal());

最后,更新handleChange ,以包括存储对象。

const handleChange = (e) => {
  setCurrentLocale(e.target.value);
  // storing locale in the localstorage
  localStorage.setItem("locale", e.target.value);
};

App.js 文件中删除这一行,因为它现在是多余的。

const locale = LOCALES.ENGLISH;

现在,保存该文件并测试该应用程序。确保重新加载页面,看看数据是否持续存在于存储中。

因为我们开始使用getItem() 方法访问本地存储,以检查任何保存的locale或返回一个回退,返回的locale被分配到状态变量currentLocale

同样地,我们也使用setItem() 方法将用户选择的locale保存到Storage 对象中。

阅读关于如何在本地存储中持久化你的React组件的状态来了解更多。

总结

我们已经涵盖了关于React Intl库的几乎所有我们需要知道的东西。我们已经学会了如何将该库应用到React应用程序中,以及如何在本地存储中持久化用户选择的地区。

我希望你觉得这篇指南对你有帮助。如果你有问题或贡献,我在评论区。你也可以在这个GitHub仓库里找到整个项目的源代码

The postReact Intl: Internationalize your React appsappeared first onLogRocket Blog.