如何国际化你的 AngularJS 应用程序

已发表: 2022-03-11

将您的应用程序国际化会使软件开发成为一种痛苦的经历,尤其是如果您没有从一开始就开始这样做,或者您对它采取了一种任性的方法。

前端和后端明显分开的现代应用程序在国际化方面可能更难处理。 突然之间,您再也无法使用大量久经考验的工具,这些工具曾经帮助您将传统的服务器端页面生成的 Web 应用程序国际化。

因此,AngularJS 应用程序需要将国际化 (i18n) 和本地化 (l10n) 数据按需交付给客户端,以便在适当的语言环境中呈现自身。 与传统的服务器端渲染应用不同,您不能再依赖服务器来交付已经本地化的页面。 您可以在此处了解如何构建多语言 PHP 应用程序

在本文中,您将了解如何将您的 AngularJS 应用程序国际化,并将了解可用于简化流程的工具。 使您的 AngularJS 应用程序多语言化可能会带来一些有趣的挑战,但某些方法可以更容易地解决大多数这些挑战。

一个简单的支持 i18n 的 AngularJS 应用程序

为了允许客户根据用户偏好动态更改语言和区域设置,您需要做出一些关键的设计决策:

  • 您如何将您的应用设计为从一开始就与语言和区域设置无关?
  • 你如何构造 i18n 和 l10n 数据?
  • 您如何有效地将这些数据交付给客户?
  • 您如何抽象出尽可能多的低级实现细节以简化开发人员的工作流程?

尽早回答这些问题有助于避免后续开发过程中的障碍。 本文将解决这些挑战中的每一个; 一些通过强大的 AngularJS 库,另一些通过某些策略和方法。

AngularJS 的国际化库

有许多 JavaScript 库是专门为国际化 AngularJS 应用程序而构建的。

angular-translate是一个 AngularJS 模块,它提供过滤器和指令,以及异步加载 i18n 数据的能力。 它通过MessageFormat支持多元化,并且被设计为高度可扩展和可配置的。

如果您在项目中使用angular-translate ,您可能会发现以下一些包非常有用:

  • angular-sanitize :可用于防止翻译中的 XSS 攻击。
  • angular-translate-interpolation-messageformat :支持性别敏感文本格式的多元化。
  • angular-translate-loader-partial :用于将翻译后的字符串传递给客户端。

为了获得真正动态的体验,您可以将angular-dynamic-locale添加到该组中。 该库允许您动态更改区域设置,包括日期、数字、货币等的格式。

入门:安装相关软件包

假设您已经准备好 AngularJS 样板,您可以使用 NPM 安装国际化包:

 npm i -S angular-translate angular-translate-interpolation-messageformat angular-translate-loader-partial angular-sanitize messageformat

安装软件包后,不要忘记将模块添加为应用程序的依赖项:

 // /src/app/core/core.module.js app.module('app.core', ['pascalprecht.translate', ...]);

请注意,模块的名称与包的名称不同。

翻译你的第一个字符串

假设您的应用程序有一个带有一些文本的工具栏和一个带有一些占位符文本的字段:

 <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Hello</a> </div> <div class="collapse navbar-collapse"> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" ng-model="vm.query" placeholder="Search"> </div> ... </div> </div> </nav>

上面的视图有两个可以国际化的文本:“Hello”和“Search”。 就 HTML 而言,一个显示为锚标记的内部文本,而另一个显示为属性的值。

为了使它们国际化,您必须将两个字符串文字替换为标记,然后 AngularJS 可以根据用户的偏好将其替换为实际翻译的字符串,同时呈现页面。

AngularJS 可以通过使用您的令牌在您提供的翻译表中执行查找来做到这一点。 模块angular-translate期望这些翻译表以纯 JavaScript 对象或 JSON 对象(如果远程加载)的形式提供。

以下是这些转换表通常看起来的示例:

 // /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello", "SEARCH": "Search" } } // /src/app/toolbar/i18n/tr.json { "TOOLBAR": { "HELLO": "Merhaba", "SEARCH": "Ara" } }

要从上面国际化工具栏视图,您需要将字符串文字替换为 AngularJS 可以用来在翻译表中查找的标记:

 <!-- /src/app/toolbar/toolbar.html --> <a class="navbar-brand" href="#" translate="TOOLBAR.HELLO"></a> <!-- or --> <a class="navbar-brand" href="#">{{'TOOLBAR.HELLO' | translate}}</a>

请注意,对于内部文本,您可以使用translate指令或translate过滤器。 (您可以在此处了解有关translate指令和translate过滤器的更多信息。)

通过这些更改,当视图被渲染时, angular-translate将根据当前语言自动将TOOLBAR.HELLO对应的适当翻译插入到 DOM 中。

要标记作为属性值出现的字符串文字,您可以使用以下方法:

 <!-- /src/app/toolbar/toolbar.html --> <input type="text" class="form-control" ng-model="vm.query" translate translate-attr-placeholder="TOOLBAR.SEARCH">

现在,如果您的标记化字符串包含变量怎么办?

要处理像“Hello, {{name}}.”这样的情况,您可以使用 AngularJS 已经支持的相同插值器语法来执行变量替换:

翻译表:

 // /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello, {{name}}." } }

然后,您可以通过多种方式定义变量。 这里有几个:

 <!-- /src/app/toolbar/toolbar.html --> <a ... translate="TOOLBAR.HELLO" translate-values='{ name: vm.user.name }'></a> <!-- or --> <a ... translate="TOOLBAR.HELLO" translate-value-name='{{vm.user.name}}'></a> <!-- or --> <a ...>{{'TOOLBAR.HELLO | translate:'{ name: vm.user.name }'}}</a>

应对多元化和性别

就 i18n 和 l10n 而言,多元化是一个相当困难的话题。 不同的语言和文化对于语言在不同情况下如何处理多元化有不同的规则。

由于这些挑战,软件开发人员有时根本不会解决问题(或至少不会充分解决问题),导致软件产生如下愚蠢的句子:

 He saw 1 person(s) on floor 1. She saw 1 person(s) on floor 3. Number of people seen on floor 2: 2.

幸运的是,有一个如何处理这个问题的标准,并且该标准的 JavaScript 实现可用作 MessageFormat。

使用 MessageFormat,您可以将上述结构不佳的句子替换为以下内容:

 He saw 1 person on the 2nd floor. She saw 1 person on the 3rd floor. They saw 2 people on the 5th floor.

MessageFormat接受如下表达式:

 var message = [ '{GENDER, select, male{He} female{She} other{They}}', 'saw', '{COUNT, plural, =0{no one} one{1 person} other{# people}}', 'on the', '{FLOOR, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', 'floor.' ].join(' ');

您可以使用上述数组构建格式化程序,并使用它来生成字符串:

 var messageFormatter = new MessageFormat('en').compile(message); messageFormatter({ GENDER: 'male', COUNT: 1, FLOOR: 2 }) // 'He saw 1 person on the 2nd floor.' messageFormatter({ GENDER: 'female', COUNT: 1, FLOOR: 3 }) // 'She saw 1 person on the 3rd floor.' messageFormatter({ COUNT: 2, FLOOR: 5 }) // 'They saw 2 people on the 5th floor.'

如何将MessageFormatangular-translate结合使用,以在您的应用程序中利用其全部功能?

在您的应用程序配置中,您只需告诉angular-translate消息格式插值可用,如下所示:

 /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); });

以下是翻译表中的条目的外观:

 // /src/app/main/social/i18n/en.json { "SHARED": "{GENDER, select, male{He} female{She} other{They}} shared this." }

在视图中:

 <!-- /src/app/main/social/social.html --> <div translate="SHARED" translate-values="{ GENDER: 'male' }" translate-interpolation="messageformat"></div> <div> {{ 'SHARED' | translate:"{ GENDER: 'male' }":'messageformat' }} </div>

在这里,您必须明确指出应该使用消息格式插值器,而不是 AngularJS 中的默认插值器。 这是因为这两个插值器的语法略有不同。 您可以在此处阅读有关此内容的更多信息。

为您的应用程序提供翻译表

既然您知道 AngularJS 如何从翻译表中为您的标记查找翻译,那么您的应用程序首先是如何知道翻译表的呢? 你如何告诉你的应用应该使用哪种语言环境/语言?

这是您了解$translateProvider的地方。

您可以在应用程序的core.config.js文件中直接为要支持的每个语言环境提供翻译表,如下所示:

 // /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); $translateProvider.translations('en', { TOOLBAR: { HELLO: 'Hello, {{name}}.' } }); $translateProvider.translations('tr', { TOOLBAR: { HELLO: 'Merhaba, {{name}}.' } }); $translateProvider.preferredLanguage('en'); });

在这里,您将翻译表作为英语 (en) 和土耳其语 (tr) 的 JavaScript 对象提供,同时将当前语言声明为英语 (en)。 如果用户希望更改语言,您可以使用 $translate 服务进行:

 // /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, $translate) { $scope.changeLanguage = function (languageKey) { $translate.use(languageKey); // Persist selection in cookie/local-storage/database/etc... }; });

仍然存在默认使用哪种语言的问题。 硬编码我们应用程序的初始语言可能并不总是可以接受的。 在这种情况下,另一种方法是尝试使用 $translateProvider 自动确定语言:

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.determinePreferredLanguage(); });

determinePreferredLanguagewindow.navigator中搜索值并选​​择智能默认值,直到用户提供明确的信号。

延迟加载翻译表

上一节展示了如何在源代码中直接将翻译表作为 JavaScript 对象提供。 这对于小型应用程序来说可能是可以接受的,但这种方法不可扩展,这就是为什么翻译表通常作为 JSON 文件从远程服务器下载的原因。

以这种方式维护转换表会减少交付给客户端的初始负载大小,但会带来额外的复杂性。 现在您面临着向客户端提供 i18n 数据的设计挑战。 如果处理不当,您的应用程序的性能可能会受到不必要的影响。

为什么这么复杂? AngularJS 应用程序被组织成模块。 在一个复杂的应用程序中,可能有许多模块,每个模块都有自己独特的 i18n 数据。 因此,应避免使用幼稚的方法,例如一次加载和提供 i18n 数据。

您需要的是一种按模块组织 i18n 数据的方法。 这将使您能够在需要时加载所需的内容,并缓存以前加载的内容以避免重新加载相同的数据(至少在缓存无效之前)。

这就是partialLoader发挥作用的地方。

假设您的应用程序的翻译表结构如下:

 /src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json

您可以将$translateProvider配置为使用具有与此结构匹配的 URL 模式的partialLoader

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/src/app/{part}/i18n/{lang}.json' }); });

正如人们所期望的那样,“lang”在运行时被替换为语言代码(例如“en”或“tr”)。 “部分”呢? $translateProvider 如何知道要加载哪个“部分”?

您可以使用$translatePartialLoader在控制器内部提供此信息:

 // /src/app/main/main.controller.js app.controller('MainCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('main'); }); // /src/app/toolbar/toolbar.config.js app.controller('ToolbarCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('toolbar'); });

模式现在完成了,给定视图的 i18n 数据在其控制器首次执行时加载,这正是您想要的。

缓存:减少加载时间

缓存呢?

您可以使用$translateProvider在应用配置中启用标准缓存:

 // /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoaderCache(true); // default is false });

如果您需要破坏给定语言的缓存,可以使用$translate

 $translate.refresh(languageKey); // omit languageKey to refresh all

有了这些部分,您的应用程序就完全国际化并支持多种语言。

本地化数字、货币和日期

在本节中,您将了解如何在 AngularJS 应用程序中使用angular-dynamic-locale来支持 UI 元素的格式设置,例如数字、货币、日期等。

为此,您将需要再安装两个软件包:

 npm i -S angular-dynamic-locale angular-i18n

安装软件包后,您可以将模块添加到应用程序的依赖项中:

 // /src/app/core/core.module.js app.module('app.core', ['tmh.dynamicLocale', ...]);

语言环境规则

语言环境规则是简单的 JavaScript 文件,它提供有关日期、数字、货币等应如何由依赖于 $locale 服务的组件格式化的规范。

当前支持的语言环境列表可在此处获得。

这是angular-locale_en-us.js的片段,说明了月份和日期格式:

 ... "MONTH": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], "SHORTDAY": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], ...

与 i18n 数据不同,区域设置规则对应用程序来说是全局的,需要一次性加载给定区域设置的规则。

默认情况下, angular-dynamic-locale期望语言环境规则文件位于angular/i18n/angular-locale_{{locale}}.js中。 如果它们位于其他位置,则必须使用tmhDynamicLocaleProvider覆盖默认值:

 // /src/app/core/core.config.js app.config(function (tmhDynamicLocaleProvider) { tmhDynamicLocaleProvider.localeLocationPattern( '/node_modules/angular-i18n/angular-locale_{{locale}}.js'); });

缓存由tmhDynamicLocaleCache服务自动处理。

使缓存失效在这里不是一个问题,因为语言环境规则比字符串翻译更不可能改变。

为了在语言环境之间切换, angular-dynamic-locale提供了tmhDynamicLocale服务:

 // /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, tmhDynamicLocale) { $scope.changeLocale = function (localeKey) { tmhDynamicLocale.set(localeKey); // Persist selection in cookie/local-storage/database/etc... }; });

使用自动翻译生成翻译表

语言环境规则随angular-i18n包一起提供,因此您所要做的就是根据需要使包内容可用于您的应用程序。 但是如何为翻译表生成 JSON 文件? 没有完全可以下载并插入我们的应用程序的包。

一种选择是使用程序化翻译 API,尤其是当您的应用程序中的字符串是没有变量或复数表达式的简单文字时。

使用 Gulp 和几个额外的包,为您的应用程序请求程序化翻译变得轻而易举:

 import gulp from 'gulp'; import map from 'map-stream'; import rename from 'gulp-rename'; import traverse from 'traverse'; import transform from 'vinyl-transform'; import jsonFormat from 'gulp-json-format'; function translateTable(to) { return transform(() => { return map((data, done) => { const table = JSON.parse(data); const strings = []; traverse(table).forEach(function (value) { if (typeof value !== 'object') { strings.push(value); } }); Promise.all(strings.map((s) => getTranslation(s, to))) .then((translations) => { let index = 0; const translated = traverse(table).forEach(function (value) { if (typeof value !== 'object') { this.update(translations[index++]); } }); done(null, JSON.stringify(translated)); }) .catch(done); }); }); } function translate(to) { return gulp.src('src/app/**/i18n/en.json') .pipe(translateTable(to)) .pipe(jsonFormat(2)) .pipe(rename({ basename: to })) .pipe(gulp.dest('src/app')); } gulp.task('translate:tr', () => translate('tr')); This task assumes the following folder structure: /src/app/main/i18n/en.json /src/app/toolbar/i18n/en.json /src/app/navigation/i18n/en.json ...

该脚本首先读取所有英文翻译表,异步请求其字符串资源的翻译,然后用翻译后的字符串替换英文字符串以生成新语言的翻译表。

最后,新的翻译表写成英文翻译表的兄弟,产生:

 /src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json /src/app/navigation/i18n/en.json /src/app/navigation/i18n/tr.json ...

getTranslation的实现也很简单:

 import bluebird from 'bluebird'; import MicrosoftTranslator from 'mstranslator'; bluebird.promisifyAll(MicrosoftTranslator.prototype); const Translator = new MicrosoftTranslator({ client_id: process.env.MICROSOFT_TRANSLATOR_CLIENT_ID, client_secret: process.env.MICROSOFT_TRANSLATOR_CLIENT_SECRET }, true); function getTranslation(string, to) { const text = string; const from = 'en'; return Translator.translateAsync({ text, from, to }); }

在这里,我们使用的是 Microsoft Translate,但也可以轻松使用其他提供商,例如 Google Translate 或 Yandex Translate。

虽然程序化翻译很方便,但也有几个缺点,包括:

  • 机器人翻译适用于短字符串,但即便如此,在不同上下文中具有不同含义的单词也可能存在缺陷(例如,“pool”可能意味着游泳或分组)。
  • API 可能无法处理带有变量的字符串或依赖于消息格式的字符串。

在这些情况和其他情况下,可能需要人工翻译; 但是,这是另一篇博文的主题。

国际化前端看起来令人生畏

在本文中,您学习了如何使用这些包来国际化和本地化 AngularJS 应用程序。

angular-translateangular-dynamic-localegulp是用于将 AngularJS 应用程序国际化的强大工具,这些应用程序封装了令人痛苦的低级实现细节。

有关说明本文中讨论的想法的演示应用程序,请查看此 GitHub 存储库。