本土化

了解如何使用 expo-localization 在 Expo 项目中开始和配置本地化。


如果你希望你的应用对讲不同语言或来自不同文化的用户来说易于使用,你应该进行本地化。应用本地化可以使其适应用户设备的区域设置。应用会显示用户熟悉和理解的翻译和货币。数字、列表等也会按用户习惯的方式进行格式化。

🌐 If you want your app to be easy to use for users who speak different languages or come from different cultures, you should localize it. Localizing an app makes it adapt to the locale of the user's device. The app will show translations and currencies that the user knows and understands. Numbers, lists, and more will be formatted in a way that the user is used to.

本指南使用 expo-localization 库来访问用户语言设置并添加多语言支持。它以 i18n-js 为例添加多语言支持。

🌐 This guide uses the expo-localization library for accessing user language settings and adding support for multiple languages. It uses i18n-js as an example to add multi-language support.

获取用户的语言

🌐 Getting the user's language

使用 expo-localization 库获取用户的当前语言。通过运行以下命令来安装该包:

🌐 Use the expo-localization library to get the user's current language. Install the package by running the following command:

Terminal
npx expo install expo-localization

然后,你将能够在应用中访问本地化方法和数据:

🌐 Then, you will be able to access localization methods and data in your app:

import { getLocales } from 'expo-localization'; const deviceLanguage = getLocales()[0].languageCode;

getLocales 方法返回基于设备系统设置的当前区域设置。在更新的 Android 和 iOS 版本中,应用语言可以按应用设置,因此通常不需要为用户在应用内更改当前区域设置构建自定义界面。

🌐 The getLocales method returns the current locale based on the system settings of the device. On newer Android and iOS versions, app language can be set per app, so you usually don't need to build a custom UI to allow users to change the current locale inside of your app.

有时,为用户提供一个界面以便在每个应用级别设置其他本地化偏好是有意义的。一般来说,你应该允许用户更改以下内容:

🌐 Sometimes, it makes sense to build a UI to allow the user to set other localization preferences on a per-app basis. As a general rule, you should allow the user to change the following:

  • 本地化单位(如果你的应用至少适度使用它们)(例如公制/英制测量、货币、温度等)
  • 其他偏好设置,如果没有 API 可以在你想支持的平台上获取默认值(详情请查看 expo-localization API 文档)

通过系统设置启用每个应用的语言选择

🌐 Enabling per-app language selection via system settings

Android 和 iOS 都允许用户通过系统设置为单个应用选择首选语言。为了支持此功能,你的应用必须向系统声明其支持的语言环境。

🌐 Both Android and iOS allow users to choose a preferred language for individual apps via the system settings. To support this feature, your app must declare its supported locales to the system.

为此,请使用 expo-localization 配置插件,并将 supportedLocales 属性传递给 expo-localization 配置插件。你可以直接提供支持的本地化数组,或者使用 supportedLocales.iossupportedLocales.android 字段来指定特定平台的值:

🌐 To do so, use the expo-localization config plugin and pass the supportedLocales property to the expo-localization config plugin. You can either provide an array of supported locales directly, or use the supportedLocales.ios and supportedLocales.android fields to specify platform-specific values:

app.json
{ "expo": { "plugins": [ [ "expo-localization", { "supportedLocales": { "ios": ["en", "ja"], "android": ["en", "ja"] } } ] ] } }

在 Android 上,请参考 区域设置命名指南最常用的区域设置列表

在 iOS 上,请使用语言名称或 ISO 语言标识符。

翻译应用

🌐 Translating an app

创建和管理翻译很快就会变成一项大任务。你可以手动处理翻译,但最好使用库来为你处理这件事。

🌐 Creating and managing translations quickly becomes a large task. You can handle translations manually, but it's best to use a library to handle this for you.

让应用支持英语和日语。为此,本指南使用了 i18n-js 包:

🌐 Let's make the app support English and Japanese. To achieve that, this guide uses the i18n-js package:

Terminal
npx expo install i18n-js

看看其他翻译库,找到最适合你需求的那个。

🌐 Take a look at other translation libraries to find one that best suits your needs.

然后,为你的应用配置语言:

🌐 Then, configure the languages for your app:

import { getLocales } from 'expo-localization'; import { I18n } from 'i18n-js'; // Set the key-value pairs for the different languages you want to support. const i18n = new I18n({ en: { welcome: 'Hello' }, ja: { welcome: 'こんにちは' }, }); // Set the locale once at the beginning of your app. i18n.locale = getLocales().at(0)?.languageCode ?? 'en'; // you can also do getLocales()[0].languageCode ?? 'en' console.log(i18n.t('welcome'));

现在,你可以使用 i18n.t 函数在你的应用中翻译字符串。

🌐 Now, you can use the i18n.t function to translate strings throughout your application.

对于某些内容,例如名称,你可以不进行本地化。在这种情况下,你可以在默认语言中只定义一次,然后用 i18n.enableFallback = true; 重复使用它们。

🌐 You can refrain from localizing text for certain things, for example, names. In this case, you can define them once in your default language and reuse them with i18n.enableFallback = true;.

在 Android 上,当用户更改设备语言时,应用不会重置。你可以使用 AppState API 来监听应用状态的变化,并在每次应用状态变化时调用 getLocales() 函数。

🌐 On Android, when a user changes the device's language, the app will not reset. You can use the AppState API to listen for changes to the app's state and call the getLocales() function each time the app's state changes.

在 iOS 上,当用户更改设备语言时,应用会重置。这意味着你可以只设置一次语言,而无需更新任何 React 组件来应对语言更改。

🌐 On iOS, when a user changes the device's language, the app will reset. This means you can set the language once without updating any of your React components to account for the language changes.

完整示例

🌐 Complete example

Localization
import { View, StyleSheet, Text } from 'react-native'; import { getLocales } from 'expo-localization'; import { I18n } from 'i18n-js'; // Set the key-value pairs for the different languages you want to support. const translations = { en: { welcome: 'Hello', name: 'Charlie' }, ja: { welcome: 'こんにちは' }, }; const i18n = new I18n(translations); // Set the locale once at the beginning of your app. i18n.locale = getLocales()[0].languageCode ?? 'en'; // When a value is missing from a language it'll fall back to another language with the key present. i18n.enableFallback = true; // To see the fallback mechanism uncomment the line below to force the app to use the Japanese language. // i18n.locale = 'ja'; export default function App() { return ( <View style={styles.container}> <Text style={styles.text}> {i18n.t('welcome')} {i18n.t('name')} </Text> <Text>Current locale: {i18n.locale}</Text> <Text>Device locale: {getLocales()[0].languageCode}</Text> </View> ); } const styles = StyleSheet.create({ container: { backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', flex: 1, }, text: { fontSize: 20, marginBottom: 16, }, });

其他翻译库

🌐 Other translation libraries

本指南以 i18n-js 为例,但其他库也可以帮助你完成这项任务。创建翻译是一项重大工作。选择库时需要考虑的一些要点如下:

🌐 This guide uses i18n-js as an example, but other libraries can also help you with the task. Creating translations is a major effort. Some pointers to consider when choosing a library:

  • 与翻译管理工具集成,以便更轻松地管理字符串和自动化操作。
  • 能够为字符串提供上下文,以便 AI 翻译工具和/或人工审校者能够理解字符串的上下文并提供更好的翻译。
  • 开发者体验——在 React / JSX 的上下文中使用。一些库提供 ESLint 插件和其他工具来辅助开发。
  • 不用担心本地化日期或数字——为此使用标准化的 Intl API。

以下是你可以考虑的其他库的非详尽列表:

🌐 Here is a non-exhaustive list of other libraries you can consider:

  • Lingui 是一个成熟的库,提供一流的 React(包括 React 服务器组件 (RSC))支持,并且能够很好地与翻译管理工具集成。
  • fbtee 是一个用于 JavaScript 和 React 的国际化框架,旨在强大、灵活且直观。
  • React i18next 是一个稳定、维护良好的库,基于 i18next

翻译应用元数据

🌐 Translating app metadata

如果你打算将应用发布到不同的国家或地区,或者希望它支持多种语言,你可以为显示名称和系统对话框等内容提供本地化字符串。这可以在应用配置文件中轻松设置。首先,设置 ios.infoPlist.CFBundleAllowMixedLocalizations: true,然后提供 locales 的文件路径列表。

🌐 If you plan on shipping your app to different countries or regions or want it to support various languages, you can provide localized strings for things like the display name and system dialogs. This is easily set up in the app config file. First, set ios.infoPlist.CFBundleAllowMixedLocalizations: true, then provide a list of file paths to locales.

app.json
{ "expo": { "ios": { "infoPlist": { "CFBundleAllowMixedLocalizations": true } }, "locales": { "ja": "./languages/japanese.json" } } }

提供给 locales 的键应该是 语言标识符,由你所需语言的 两字母语言代码 组成,带可选的区域代码(例如,en-USen-GB),其值应指向一个类似下面的 JSON 文件:

🌐 The keys provided to locales should be the language identifier, made up of a 2-letter language code of your desired language, with an optional region code (for example, en-US or en-GB), and the value should point to a JSON file that looks something like below:

japanese.json
{ "ios": { "CFBundleDisplayName": "こんにちは", "NSContactsUsageDescription": "日本語のこれらの言葉", "Localizable.strings": { "HELLO_NOTIFICATION_KEY": "こんにちは世界" } }, "android": { "app_name": "こんにちは", "HELLO_NOTIFICATION_KEY": "こんにちは世界" } }

现在,每当你的应用安装在语言设置为日语的设备上时,应用的显示名称都会设置为 こんにちは

🌐 Now, the display name of your app is set to こんにちは whenever it's installed on a device with the language set to Japanese.

在 SDK 55 及更高版本中,有一个仅适用于 iOS 的选项,可以指定一个 Localizable.strings 对象,其条目用于创建本地化的原生文件。这些条目可以用于 iOS 本地化通知

🌐 In SDK 55 and later, there is an iOS-only option to specify a Localizable.strings object whose entries are used to create native localization files. The entries can be used in iOS localized notifications.

启用 RTL 支持

🌐 Enabling RTL support

世界上的一些地区是从右向左书写文本的。如果你想对应用进行本地化,使其在从右到左的语言中显示正常,你需要确保应用能相应地处理这些布局和文本方向的变化。

🌐 Several regions around the world write text from right to left. If you want to localize your app, so it looks as expected in RTL languages, you need to make sure your app handles these layout and text direction changes accordingly.

要启用 RTL 支持,请使用 expo-localization 配置插件,并在应用配置中启用 extra.supportsRTL 属性:

🌐 To enable RTL support, use the expo-localization config plugin and enable extra.supportsRTL property in app config:

app.json
{ "expo": { "extra": { "supportsRTL": true }, "plugins": ["expo-localization"] } }

这将在你的应用在 Expo Go、Expo 开发客户端以及使用 EAS Build 或 npx expo prebuild 构建的应用中加载时启用 RTL。

🌐 This enables RTL when your app is loaded in Expo Go, in Expo dev Client, and in applications built using EAS Build or npx expo prebuild.

当应用启动时,Expo 会检查当前设备的语言环境是否应以从右到左(RTL)布局呈现,以确保显示正确。例如,在应用配置文件中标记支持 RTL 的应用将在希伯来语或阿拉伯语环境中以 RTL 模式呈现。

🌐 When an application starts, Expo checks if the current device locale should render in RTL layout to look correctly. For example, an app marked to support RTL in the app config file will render in RTL mode in Hebrew or Arabic locale.

强制 RTL 布局

🌐 Forcing RTL layout

你也可以通过在应用配置中启用 extra.forcesRTL 属性来强制使用 RTL 布局,以便进行测试或用于仅针对 RTL 区域设置本地化的应用:

🌐 You can also force the RTL layout for testing or for applications that are only localized for RTL locales by enabling extra.forcesRTL property in the app config:

app.json
{ "expo": { "extra": { "supportsRTL": true, "forcesRTL": true }, "plugins": ["expo-localization"] } }
动态覆盖RTL设置

如果你想在应用代码中动态覆盖默认的 RTL 检测,就不能使用应用配置中的静态配置。相反,应从应用代码中动态应用这些更改。

🌐 If you want to override the default RTL detection from your application code dynamically, you cannot use the static configuration in app config. Instead, apply these changes dynamically from your application code.

这在 Expo Go 中不起作用,因为 Expo Go 在打开启动器或单个项目时会重置 RTL 设置。

🌐 This does not work in Expo Go, as Expo Go resets RTL preferences when opening the launcher or individual projects.

Overriding RTL settings
import { Text, View, StyleSheet, I18nManager, Platform } from 'react-native'; import Constants from 'expo-constants'; import * as Updates from 'expo-updates'; export default function App() { const shouldBeRTL = true; if (shouldBeRTL !== I18nManager.isRTL && Platform.OS !== 'web') { I18nManager.allowRTL(shouldBeRTL); I18nManager.forceRTL(shouldBeRTL); Updates.reloadAsync(); } return ( <View style={styles.container}> <Text style={styles.paragraph}>{I18nManager.isRTL ? ' RTL' : ' LTR'}</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', paddingTop: Constants.statusBarHeight, padding: 8, }, paragraph: { fontSize: 18, fontWeight: 'bold', textAlign: 'left', width: '50%', backgroundColor: 'pink', }, });

让应用在 RTL(从右到左)语言环境下正常运行

🌐 Making an app behave correctly on RTL locales

布局和视图

🌐 Layouts and views

你无需根据语言环境手动调整 <View> 的样式属性。你可以使用 justifyContentalignItems 等属性。它们的属性值会根据需要改变行为。

🌐 You don't need to manually adjust <View> styling properties based on locale. You can use properties like justifyContent, alignItems, and others. Their property values change behavior as required.

  • 在从左到右的语言环境中,startendleftright 相同。
  • 在从右到左的语言环境中,startendrightleft 相同。

信息 有关 React Native 中 RTL 的工作原理的更多详细信息,请查看 React Native 介绍 RTL 支持的 博客文章

Web 支持

🌐 Web support

Web 支持 RTL 布局无需更改应用配置。

🌐 Web support for RTL layouts requires no app config changes.

Expo 使用 react-native-web 在浏览器中运行 Expo 项目。要让 react-native-web 自动适应本地方向,请在根 <View> 组件中添加 dir 属性。

🌐 Expo uses react-native-web for running Expo projects in the browser. To make react-native-web automatically adapt to locale direction, add a dir property to your root <View> component.

App.tsx
import { View } from 'react-native'; import { getLocales } from 'expo-localization'; // ... return <View dir={getLocales()[0].textDirection || 'ltr'}>...</View>;

警告 textDirection 在 Firefox 和较旧版本的浏览器中不可用。如有需要,请手动检测

文本对齐

🌐 Text alignment

React Native 的 textDirection 属性不接受 startend 这些可以在 flex 属性中使用的值。相反,left 实际上起到了 start 的作用(在 LTR 时左对齐,在 RTL 时右对齐),而 right 起到了 end 的作用。

🌐 The React Native's textDirection property does not accept start or end values that you can use in flex properties. Instead, left effectively works as start (aligns to the left on LTR and the right on RTL), and right works as end.

然而,textDirection 属性的默认未设置值表示实际的左对齐(在从左到右和从右到左的布局中都向左对齐)。这意味着如果你希望每个 <Text> 标签正确对齐,应为其设置 textDirection: lefttextDirection: right 样式。

🌐 However, the default unset value of textDirection property signifies the actual left (aligns to the left both on LTR and RTL). This means each <Text> tag should have the textDirection: left or textDirection: right style set if you want it to be aligned correctly.

最好在你自定义的可重用 <Text> 组件中定义这种样式,然后你可以在需要渲染文本字符串的地方导入它。

🌐 It's best to define this style in your custom reusable <Text> component that you can then import everywhere you need to render text strings.

MobileText.tsx
import { Text as RNText, TextProps as RNTextProps } from 'react-native'; const MobileText = (props: RNTextProps) => { return <RNText style={{ textAlign: 'left', ...props.style }} {...props} />; }; export default MobileText;

Web 支持

🌐 Web support

对于每个文本标签,你需要添加带有当前区域标识符的 lang 属性。最好在自定义可重用组件中定义此样式。

🌐 For each text tag, you need to add the lang property with the current locale identifier. It's best to define this style in a custom reusable component.

WebText.tsx
import { getLocales } from 'expo-localization'; const deviceLanguage = getLocales()[0].languageCode; const WebText = (props: RNTextProps) => { return <RNText lang={deviceLanguage} {...props} />; }; export default WebText;

然后你可以根据当前平台选择移动端或网页端的文本组件。

🌐 You can then pick either the mobile or web Text component based on the current platform.

Text.tsx
const Text = Platform.OS === 'web' ? WebText : MobileText; export default Text;

根据区域方向选择资源

🌐 Selecting assets based on locale direction

如果你需要为从左到右/从右到左使用不同的图标,或根据此设置更改样式,你可以使用 I18nManager.isRTL 来获取当前的布局方向。

🌐 If you need to use different icons for LTR/RTL or change styles based on this setting, you can use I18nManager.isRTL to get the current layout direction.

import { I18nManager } from 'react-native'; const isRTL = I18nManager.isRTL;

区域设置和单位

🌐 Locale settings and units

Expo 提供了 expo-localization 库,允许你读取用户的语言环境和其他偏好设置。你可以使用同步的 getLocales()getCalendars() 方法来获取用户设备的当前语言环境设置:

🌐 Expo provides the expo-localization library to allow you to read the user's locale and other preferences. You can use synchronous getLocales() and getCalendars() methods to get the current locale settings of the user device:

  • getLocales() 会根据用户的偏好顺序返回一个本地化列表。列表中至少会有一个本地化。
  • getCalendars() 会根据用户的偏好顺序返回一个日历列表。列表中至少会有一个日历。
import { getLocales, getCalendars } from 'expo-localization'; const { languageTag, languageCode, textDirection, digitGroupingSeparator, decimalSeparator, measurementSystem, currencyCode, currencySymbol, regionCode, } = getLocales()[0]; const { calendar, timeZone, uses24hourClock, firstWeekday } = getCalendars()[0];
局限性

There are a few limitations to keep in mind when relying on auto-detected locale preferences from expo-localization.

  • 尚未有从用户偏好中读取温度单位的方法。在 Android 上,你可以根据区域设置使用查找表。然而,在 iOS 上,用户可以在设备偏好设置中更改它。
  • 当当前平台无法提供时,某些属性可以为 null。

国际化 API

🌐 Intl API

如果你在应用中使用 Hermes,你可以在所有平台上使用 Intl API。

🌐 If you're using Hermes in your app, you can use the Intl API on all platforms.

它提供了一套工具,你可以用来格式化列表、日期、数字、货币金额、单位、复数形式等。

🌐 It provides a set of utilities you can use to format lists, dates, numbers, monetary amounts, units, plural forms, and more.

如果你将 default 作为区域设置字符串传递,Intl API 将使用设备的区域设置,因此你不需要依赖 expo-localization 来获取当前的区域设置(例如 "en-US")。

🌐 If you pass default as the locale string, the Intl API will use the device's locale, so you don't need to rely on expo-localization to get the current locale (such as "en-US").

new Intl.NumberFormat('default', { style: 'currency', currency: 'EUR' }).format(5.0);

一旦你知道用户期望看到的内容,就可以使用 Intl API 来格式化字符串和数值。

Intl API 不提供设备或当前区域设置信息,因此你不能使用 Intl API 来获取当前区域的单位、货币或计量系统。

为此,你需要使用 expo-localization、网页上的 JS 代码,或者在 Android 和 iOS 上使用第三方或自定义原生代码。