本文概述
- 一个支持i18n的简单AngularJS应用
- AngularJS的国际化库
- 入门:安装相关软件包
- 翻译你的第一个字符串
- 处理多元化和性别
- 为你的应用程序提供翻译表
- 延迟加载转换表
- 缓存:减少加载时间
- 本地化数字, 货币和日期
- 使用自动翻译生成翻译表
- 前端国际化只是令人生畏
对你的应用进行国际化可能会使软件开发感到痛苦, 特别是如果你从一开始就没有开始做, 或者对它采取不友好的方法。
前端和后端彼此明显分开的现代应用程序在涉及国际化时可能更加棘手。突然, 你将无法再使用大量经过时间考验的工具, 这些工具曾经帮助国际化了传统的服务器端页面生成的Web应用程序。
因此, AngularJS应用程序需要按需交付国际化(i18n)和本地化(l10n)数据, 以将其交付给客户端以在适当的语言环境中呈现自己。与传统的服务器端呈现的应用程序不同, 你不再可以依靠服务器来交付已经本地化的页面。你可以在此处了解有关构建多语言PHP应用程序的信息。
在本文中, 你将学习如何使AngularJS应用程序国际化, 并学习可用于简化流程的工具。使你的AngularJS应用程序多语言化可能会带来一些有趣的挑战, 但是某些方法可以使应对这些挑战变得更加容易。
一个支持i18n的简单AngularJS应用
为了允许客户端根据用户的偏好即时更改语言和语言环境, 你将需要做出一些关键的设计决策:
- 你如何从一开始就将应用设计为与语言和地区无关?
- 你如何构造i18n和l10n数据?
- 你如何有效地将这些数据传递给客户?
- 你如何抽象出尽可能多的底层实现细节以简化开发人员工作流程?
尽早回答这些问题可以帮助避免开发过程中的障碍。本文将解决所有这些挑战;一些通过健壮的AngularJS库, 其他通过某些策略和方法。
AngularJS的国际化库
有许多专门用于国际化AngularJS应用程序的JavaScript库。
angular-translate是一个AngularJS模块, 它提供过滤器和指令, 以及异步加载i18n数据的能力。它通过MessageFormat支持复数, 并且被设计为高度可扩展和可配置的。
如果你在项目中使用angular-translate, 可能会发现以下某些软件包非常有用:
- angular-sanitize:可用于防御翻译中的XSS攻击。
- angular-translate-interpolation-messageformat:支持性别敏感文本格式的复数形式。
- angular-translate-loader-partial:用于将转换后的字符串传递给客户端。
为了获得真正的动态体验, 你可以将角度动态区域设置添加到束中。该库允许你动态更改语言环境-包括日期, 数字, 货币等全部格式化的方式。
入门:安装相关软件包
假设你已经准备好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而言, 一个显示为锚标记的内部文本, 而另一个显示为属性的值。
为了使它们国际化, 你必须将两个字符串文字替换为Token, 然后在呈现页面时, AngularJS可以根据用户的喜好将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指令和在此处了解转换过滤器的信息。)
通过这些更改, 在渲染视图时, 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.'
如何将MessageFormat与angular-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');
});
在这里, 你将翻译表作为JavaScript对象提供给英语(en)和土耳其语(tr), 同时将当前语言声明为英语(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();
});
definePreferredLanguage在window.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。
尽管程序化翻译很方便, 但是也有一些缺点, 包括:
- 机器人翻译适用于短字符串, 但即使那样, 在不同上下文中具有不同含义的单词也会有一些陷阱(例如, “池”可能意味着游泳或分组)。
- API可能无法处理带有变量的字符串或依赖于消息格式的字符串。
在这些情况下, 可能需要人工翻译;但是, 这是另一篇博客文章的主题。
前端国际化只是令人生畏
在本文中, 你学习了如何使用这些包对AngularJS应用程序进行国际化和本地化。
angular-translate, angular-dynamic-locale和gulp是使AngularJS应用程序国际化的强大工具, 该应用程序封装了痛苦的低级实现细节。
对于演示应用程序中讨论的想法的演示应用程序, 请查看此GitHub存储库。
评论前必须登录!
注册