如何國際化你的 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.'
如何將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'); });
在這裡,您將翻譯表作為英語 (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(); });
determinePreferredLanguage
在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。
雖然程序化翻譯很方便,但也有幾個缺點,包括:
- 機器人翻譯適用於短字符串,但即便如此,在不同上下文中具有不同含義的單詞也可能存在缺陷(例如,“pool”可能意味著游泳或分組)。
- API 可能無法處理帶有變量的字符串或依賴於消息格式的字符串。
在這些情況和其他情況下,可能需要人工翻譯; 但是,這是另一篇博文的主題。
國際化前端看起來令人生畏
在本文中,您學習瞭如何使用這些包來國際化和本地化 AngularJS 應用程序。
angular-translate
、 angular-dynamic-locale
和gulp
是用於將 AngularJS 應用程序國際化的強大工具,這些應用程序封裝了令人痛苦的低級實現細節。
有關說明本文中討論的想法的演示應用程序,請查看此 GitHub 存儲庫。