什么是 i18n?
国际化 (i18n) 是设计软件的过程,以便它可以适应不同的语言和地区,而无需更改代码。“i18n”这个昵称来自于国际化中的“i”和“n”之间有18个字母。
认为 i18n 为以下方面奠定了基础:
- 本地化(l10n):针对特定市场翻译和调整您的内容。
- 全球化 (g11n):协调跨市场的产品准备和运营。
实际上,i18n 是为了确保您的应用程序能够处理:
- 任何语言的文本
- 正确格式的日期、时间、数字和货币
- 符合本地语法的复数规则
- 从右到左的布局(例如阿拉伯语或希伯来语)
为什么 i18n 很重要
预先投资 i18n 可以为您的工程和产品团队带来切实的好处:
- 降低本地化成本:如果从一开始就内置翻译逻辑,那么提取和替换字符串会更容易。
- 更快的开发:用于管理字符串的共享基础设施减少了未来的返工。
- 更好的用户体验:用户可以看到以他们的语言呈现的内容,并且格式自然。
- 可扩展性:只需较少的代码更改即可在新市场推出应用程序。
现代应用程序中的 i18n 实现模式
以下是在生产级应用程序中实现 i18n 的一些常见模式。我们将介绍React (前端) 、 iOS和Android 。
无论平台如何,翻译通常都来自像 Smartling 这样的翻译管理系统 (TMS),以 JSON 或资源文件的形式。然后,您的应用程序在运行时通过 i18n 框架或本机本地化 API 加载这些文件,确保根据用户的语言或语言环境显示正确的字符串。
前端(React)
现代 React 应用程序通常使用专用的 i18n 库。其中最受欢迎的两个是i18next和FormatJS ,它们都是与框架无关的。在 React 中,它们各自的实现是react-i18next (基于函数和钩子)和react-intl (基于组件和钩子,基于 ICU MessageFormat 构建)。两者都与企业管道完美集成,并处理复数、变量和区域感知格式。
1. 使用 i18next + react-i18next 进行 React
i18next是最流行的 JavaScript i18n 库之一,当与react-i18next绑定配对时,它为 React 应用程序提供了一个强大的、钩子友好的 API,用于加载和渲染翻译。
在下面的示例中,我们将介绍常见的企业 i18n 模式。我们将定义翻译文件,在应用程序的入口点初始化i18next ,然后在组件中呈现翻译。这种方法适用于各种框架,可以与大多数翻译管理系统 (TMS) 完美集成,并且可以随着应用程序的增长而很好地扩展 - 无论您显示的是静态字符串还是动态的、变量驱动的消息(如计数)。
i18n 模式示例:i18next + reacti18next
您可能会从翻译管理系统收到 JSON 文件形式的内容。下面的 JSON 文件示例显示了如何存储英语翻译。每个条目都有一个唯一的键,并且复数形式是单独定义的,因此库可以根据您传递的计数选择正确的形式。您的 TMS 将为每种支持的语言生成一个匹配的文件。
{
"welcome": "Welcome",
"visits_one": "您已访问 {{count}} 次。",
"visits_other": "您已访问 {{count}} 次。"
}
接下来,您将看到一个如何配置 i18next 的示例,以便它知道您的应用支持哪些语言、在哪里可以找到翻译以及如果缺少翻译该怎么办。此安装文件在您的应用程序启动时运行一次(通常在入口点文件(如index.js或main.tsx )中)这样,翻译在任何 UI 呈现之前就已准备好。将配置集中在一个地方可以使您的应用程序的本地化逻辑保持一致。
从'i18next'导入i18next;
从'react-i18next'导入{initReactI18next};
import en from './locales/en/translation.json';
从'./locales/fr/translation.json'导入fr;
const locale = 'fr'; // 在生产环境中,动态解析
i18next
.use(initReactI18next)
.init({
lng: locale,
fallbackLng: 'en',
资源: {
en: { translation: en },
fr: { 翻译:fr }
},
插值:{ escapeValue: false }
});
导出默认 i18next;
一旦 i18next 初始化,您就可以在应用程序的任何地方使用它。在下面的示例中, useTranslation钩子检索t函数,该函数采用键和可选变量来呈现正确的字符串。当您将count作为变量传递时,i18next 会根据翻译文件自动选择正确的复数形式。
从'react-i18next'导入{ useTranslation };
import './i18n';
导出默认函数 App() {
const { t } = useTranslation();
常量计数=3;
返回 (
<>
<h1>{t('welcome')}</h1>
<p>{t('visits', { count })}</p>
</>
);
}
2. 使用 FORmatJS + react-intl 进行 React
react-intl是FormatJS生态系统的一部分,并为 React 中的 i18n 提供了基于组件的方法。它基于 ICU MessageFormat 标准构建,因此您可以获得内置的复数形式、日期/数字格式和语言环境回退。
在下面的示例中,我们将设置翻译文件,将应用程序包装在IntlProvider中,并使用FormattedMessage呈现本地化文本。这种方法非常适合那些想要使用声明式、组件驱动风格来处理 React 中的 i18n 的团队。
i18n 模式示例:FormatJS + react-intl
下面的 JSON 文件包含使用 ICU MessageFormat 语法的翻译,该语法将复数逻辑放入字符串本身内。这样可以将所有语言规则集中在一个地方,并让翻译人员完全控制语法,而无需开发人员干预。您的 TMS 管理这些每个语言环境的文件。
{
"welcome": "Welcome",
{% raw %} "visits": "您已访问 {count, plural, one {# time} other {# times}}。"{%结束原始%}接下来,您将看到在IntlProvider组件中包装应用程序的示例。这是一次性设置,通常在根组件(如Root.tsx或index.jsx )中完成。它使活动语言环境及其消息在整个应用程序中可用,因此任何组件都可以使用它们而无需额外导入。
从'react-intl'导入{IntlProvider};
import en from './locales/en.json';
从'./locales/fr.json'导入fr;
const MESSAGES = { en, fr };
const locale = 'fr';
导出默认函数 Root() {
返回 (
<IntlProvider locale={locale} messages={MESSAGES[locale]}>
<App />
</IntlProvider>
);
}
最后,请阅读下文,了解FormattedMessage组件如何通过 ID 查找翻译并自动处理复数形式。您只需传递计数,库就会从您的 JSON 文件中应用正确的语言规则。
从'react-intl'导入{FormattedMessage};
导出默认函数 App() {
常量计数=3;
返回 (
<>
<h1><FormattedMessage id="welcome" defaultMessage="Welcome" /></h1>
<p><FormattedMessage id="visits" values={{ count }} /></p>
</>
);
}
对于生产使用,还有一些额外的提示:
- 区域设置源:在实际应用中,集中确定区域设置(例如,从/fr/*之类的 URL、用户的个人资料或服务器提供的设置)并将其传递给<IntlProvider> 。
- 消息组织:为了便于扩展,可以按域或功能分解消息文件(例如auth.json ,仪表板.json )并将它们合并到活动区域。
- 后备方案: defaultMessage是您在推出期间的安全网 - 将其保留为您的源语言。
- 异步加载(可选):对于大型 bundles,在渲染<IntlProvider>之前,按语言环境动态导入消息文件(代码拆分)。
iOS
iOS 为您提供了开箱即用的可靠本地化工具,但要顺利扩展到多种语言环境,需要深思熟虑地实施 i18n 最佳实践。否则,如果没有清晰的结构,您最终可能会得到重复的键、不一致的格式以及翻译文件,而这些都会使维护变得令人头疼。关键是尽早做出一些结构性决策,以便您的本地化保持井然有序,并在新市场加入时易于扩展。
技巧 1:以可扩展的方式组织资源
一个好的起点是使用 Xcode 15 及更高版本中的字符串目录( .xcstrings ),或者.strings / .stringsdict如果您使用的是较旧的设置,则文件。它们与 TMS 配合良好,通常会以 XLIFF 格式向您发送翻译。您可以将它们直接导入到您的目录中,系统将处理合并和管理它们的繁重工作。
如果您按功能或模块组织目录,您可能会发现更容易保持整洁。更小、更有针对性的文件使得查找密钥、审查更改以及将工作交给翻译人员变得更加简单。以下是您可能希望如何组织它们的示例:
资源
国际化/
身份验证.xcstrings
结帐.xcstrings
配置文件.xcstrings
提示 2:在目录中处理复数,而不是在代码中
多元化是结构发挥作用的另一个地方。最好在目录中而不是在 Swift 中定义所有复数规则,这样您的代码只需传递变量,并为每种语言自动选择正确的短语。目录中的内容可能如下所示:
键: unread_messages
格式:复数
一条:“%d 条未读消息”
其他:“%d 条未读消息”
以下是在 Swift 中使用它的方法:
让 unreadCount = 3
让格式 = 字符串(本地化:“unread_messages”)
让消息 = String.localizedStringWithFormat(格式,未读计数)
// “3 条未读消息”
这样,您就不必在代码中硬编码语法,翻译人员就可以获得每种语言的正确细节。
提示 3:集中设置数字、日期和货币的格式
您还需要集中数字、日期和货币格式,以便应用程序的每个部分都感觉一致。共享实用程序或服务可以帮助解决这个问题。下面是一个使用 Swift 现代FormatStyle API 的简单示例:
价格 = 19.99
让显示=价格.格式化(.货币(代码:“欧元”)
//“19.99欧元”或“19,99 €”,具体取决于地区
通过保持字符串资源的有序性、在目录中处理复数形式以及集中所有可感知语言环境的格式,您可以让您的 iOS 应用在不产生额外维护工作的情况下不断发展。一旦这些做法到位,添加新语言的过程就会变得更加可预测,并且压力也会小得多。现在让我们来看看 Android,它提供了自己的内置本地化工具。
Android
Android 的资源系统在设计时已经考虑到了本地化,但要使其能够跨多种语言进行维护则需要一些规划。如果将所有内容都放在一个大文件中,或者将语法规则分散在代码中,那么很快就会变得混乱。相反,优先考虑文件分段组织,在 XML 中定义所有语法规则,并确保您的格式和布局适用于您计划支持的每个书写系统。
提示 1:按功能组织资源
对于大多数团队来说,翻译将以 XLIFF 文件的形式来自 TMS。您将它们导入到每个语言环境的res/values目录中,Android 会负责将正确的字符串与用户的语言进行匹配。
根据功能将资源分成单独的文件是一种让生活更轻松的简单方法。文件越小,审查更改的速度就越快,并且有助于避免合并冲突。
应用程序/src/main/res/
values/strings_auth.xml
values/strings_checkout.xml
values/plurals_checkout.xml
技巧 2:在资源中定义语法规则,而不是代码
与 iOS 一样,复数化是最好在资源中处理的事情之一,因此翻译人员可以根据语言进行调整,而无需更改代码。以下是英语复数资源的示例:
<plurals name="checkout_cart_items_count">
<item quantity="one">%1$d item</item>
<item amount="other">%1$d items</item>
</plurals>
以下是在 Kotlin 中使用它的方法:
val msg = resources.getQuantityString(
R.plurals.checkout_cart_items_count,数数,数数
)
这样,我们的代码保持干净,并且 Android 会根据语言环境自动选择正确的形式。
技巧 3:使用区域感知格式
对于布局,使用开始和结束而不是左和右是一个好习惯,这样它们就可以适应从右到左的语言,如阿拉伯语或希伯来语:
<LinearLayout
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
在格式化数字或货币时,传递应用程序的当前语言环境,以便一切看起来一致:
val appLocale = LocaleListCompat.getAdjustedDefault()[0] ?: Locale.getDefault()
val 价格 = NumberFormat.getCurrencyInstance(appLocale).format(1234.56)
//“$1,234.56”或“1 234,56 欧元”
最后,正确实现 Android i18n 主要就是让平台承担起重任。通过将所有文本保存在资源中,使用特定于语言环境的文件夹构建这些资源,并从第一天开始构建 RTL 和语言环境感知格式,您可以避免导致本地化变得脆弱的常见陷阱。其中许多原则与 iOS 的最佳实践相呼应,但 Android 的资源限定符系统意味着您只需添加正确的文件即可支持新语言。如果做得好,扩展到新地区就会成为一个可预测的、低努力的过程。
常见的 i18n 陷阱
不幸的是,即使是构建良好的系统有时也会遇到可以避免的问题。下表列出了一些最常见的失误、它们造成麻烦的原因以及如何防止它们。将其视为一个快速参考,您可以在发货前使用它来检查您自己的设置。
|
错误 |
如何避免 |
|
硬编码字符串 |
将所有面向用户的文本提取到资源文件或翻译键中。 |
|
假设所有文本都是从左到右 |
使用从右到左的语言(如阿拉伯语或希伯来语)测试布局。 |
|
忽略多元化 |
使用支持复数规则的库(例如,ICU 格式、i18next)。 |
|
未本地化的单位或格式 |
使用区域感知格式(例如,Intl.NumberFormat、Intl.DateTimeFormat)。 |
|
跳过文本扩展检查 |
使用伪语言环境进行测试以模拟更长的翻译。 |
|
不完整的字符串提取 |
在准备阶段使用伪语言环境来显示缺失或未标记的字符串。 |
为大规模本地化做好准备
一旦您的应用程序设置好国际化,下一步就是尽可能使本地化高效且维护成本低。一些自动化工具和工作流程可以让您从“我们可以翻译”转变为“我们可以快速推出新语言,而无需额外的开发负担”。考虑:
- 使用具有企业级 API 的翻译管理系统 (TMS)(如Smartling )来同步翻译文件并管理审核工作流程。
- 使用 CI/CD 管道自动提取和传送字符串。
- 使用 AI 工具(如 Smartling 的AI Hub )进行快速的首次翻译,并内置质量检查,如回退处理和幻觉缓解。
最后的想法
国际化是任何产品走向全球的基础实践,越早实施,以后遇到的麻烦就越少。将可靠的 i18n 实践与翻译自动化和测试工作流程相结合,您的团队将能够更快、更自信地交付国际化的软件。
想要更深入地了解吗?查看 Smartling 的 全球就绪会议关于 i18n 的网络研讨会 人工智能时代的战略。