Jak umiędzynarodowić swoją aplikację AngularJS?
Opublikowany: 2022-03-11Internacjonalizacja aplikacji może sprawić, że tworzenie oprogramowania będzie bolesnym doświadczeniem, zwłaszcza jeśli nie zaczniesz tego robić od samego początku lub podejdziesz do tego, chcąc nie chcąc.
Nowoczesne aplikacje, w których front-end i back-end są wyraźnie oddzielone od siebie, mogą być jeszcze trudniejsze do radzenia sobie z internacjonalizacją. Nagle nie masz już dostępu do mnóstwa sprawdzonych narzędzi, które kiedyś pomogły w umiędzynarodowieniu tradycyjnych aplikacji internetowych generowanych po stronie serwera.
W związku z tym aplikacja AngularJS wymaga dostarczenia klientowi na żądanie danych dotyczących internacjonalizacji (i18n) i lokalizacji (l10n), aby renderować się w odpowiednich lokalizacjach. W przeciwieństwie do tradycyjnych aplikacji renderowanych po stronie serwera, nie można już polegać na serwerze w zakresie dostarczania stron, które są już zlokalizowane. Tutaj dowiesz się, jak zbudować wielojęzyczną aplikację PHP
W tym artykule dowiesz się, jak możesz umiędzynarodowić swoją aplikację AngularJS i poznasz narzędzia, których możesz użyć, aby ułatwić ten proces. Uczynienie aplikacji AngularJS wielojęzyczną może stanowić kilka interesujących wyzwań, ale niektóre podejścia mogą ułatwić obejście większości z tych wyzwań.
Prosta aplikacja AngularJS obsługująca i18n
Aby umożliwić klientowi zmianę języka i lokalizacji w locie w oparciu o preferencje użytkownika, będziesz musiał podjąć szereg kluczowych decyzji projektowych:
- Jak zaprojektować aplikację tak, aby od samego początku była niezależna od języka i lokalizacji?
- Jak ustrukturyzować dane i18n i l10n?
- Jak skutecznie dostarczasz te dane klientom?
- Jak wyabstrahować jak najwięcej szczegółów implementacji niskiego poziomu, aby uprościć przepływ pracy programisty?
Udzielenie odpowiedzi na te pytania tak wcześnie, jak to możliwe, może pomóc uniknąć przeszkód w procesie rozwoju. Każde z tych wyzwań zostanie omówione w tym artykule; niektóre za pośrednictwem solidnych bibliotek AngularJS, inne za pomocą określonych strategii i podejść.
Biblioteki internacjonalizacji dla AngularJS
Istnieje wiele bibliotek JavaScript stworzonych specjalnie do internacjonalizacji aplikacji AngularJS.
angular-translate
to moduł AngularJS, który zapewnia filtry i dyrektywy, a także możliwość asynchronicznego ładowania danych i18n. Obsługuje liczbę mnogą za pośrednictwem MessageFormat
i jest zaprojektowany tak, aby był wysoce rozszerzalny i konfigurowalny.
Jeśli używasz angular-translate
w swoim projekcie, niektóre z poniższych pakietów mogą być bardzo przydatne:
-
angular-sanitize
: może służyć do ochrony przed atakami XSS w tłumaczeniach. -
angular-translate-interpolation-messageformat
: liczba mnoga z obsługą formatowania tekstu uwzględniającego płeć. -
angular-translate-loader-partial
: służy do dostarczania przetłumaczonych ciągów do klientów.
Aby uzyskać naprawdę dynamiczne wrażenia, możesz dodać do grupy angular-dynamic-locale
. Ta biblioteka umożliwia dynamiczną zmianę ustawień regionalnych — w tym sposób formatowania dat, liczb, walut itp.
Pierwsze kroki: instalowanie odpowiednich pakietów
Zakładając, że masz już gotowy schemat AngularJS, możesz użyć NPM do zainstalowania pakietów internacjonalizacji:
npm i -S angular-translate angular-translate-interpolation-messageformat angular-translate-loader-partial angular-sanitize messageformat
Po zainstalowaniu pakietów nie zapomnij dodać modułów jako zależności aplikacji:
// /src/app/core/core.module.js app.module('app.core', ['pascalprecht.translate', ...]);
Zauważ, że nazwa modułu różni się od nazwy pakietu.
Tłumaczenie twojej pierwszej struny
Załóżmy, że Twoja aplikacja ma pasek narzędzi z tekstem i pole z tekstem zastępczym:
<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>
Powyższy widok zawiera dwa fragmenty tekstu, które możesz umiędzynarodowić: „Hello” i „Search”. W języku HTML jeden pojawia się jako tekst wewnętrzny znacznika kotwicy, a drugi jako wartość atrybutu.
Aby je umiędzynarodowić, będziesz musiał zastąpić oba literały ciągów tokenami, które AngularJS może następnie zastąpić rzeczywistymi przetłumaczonymi ciągami, w oparciu o preferencje użytkownika, podczas renderowania strony.
AngularJS może to zrobić, używając tokenów do wyszukiwania w podanych przez Ciebie tabelach tłumaczeń. Moduł angular-translate
oczekuje, że te tabele tłumaczeń będą dostarczane jako zwykłe obiekty JavaScript lub jako obiekty JSON (w przypadku zdalnego ładowania).
Oto przykład tego, jak ogólnie wyglądałyby te tabele tłumaczeń:
// /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello", "SEARCH": "Search" } } // /src/app/toolbar/i18n/tr.json { "TOOLBAR": { "HELLO": "Merhaba", "SEARCH": "Ara" } }
Aby umiędzynarodowić widok paska narzędzi z góry, musisz zastąpić literały łańcuchowe tokenami, których AngularJS może używać do wyszukiwania w tabeli tłumaczeń:
<!-- /src/app/toolbar/toolbar.html --> <a class="navbar-brand" href="#" translate="TOOLBAR.HELLO"></a> <!-- or --> <a class="navbar-brand" href="#">{{'TOOLBAR.HELLO' | translate}}</a>
Zwróć uwagę, że w przypadku tekstu wewnętrznego możesz użyć dyrektywy translate
lub filtra translate
. (Możesz dowiedzieć się więcej o dyrektywie translate
tutaj i o filtrach translate
tutaj.
Dzięki tym zmianom, gdy widok jest renderowany, angular angular-translate
automatycznie wstawi odpowiednie tłumaczenie odpowiadające TOOLBAR.HELLO
do DOM na podstawie bieżącego języka.
Aby tokenizować literały ciągów, które pojawiają się jako wartości atrybutów, można zastosować następujące podejście:
<!-- /src/app/toolbar/toolbar.html --> <input type="text" class="form-control" ng-model="vm.query" translate translate-attr-placeholder="TOOLBAR.SEARCH">
A co, jeśli twoje tokenizowane ciągi zawierają zmienne?
Aby obsłużyć przypadki takie jak „Hello, {{name}}.”, możesz wykonać zamianę zmiennych przy użyciu tej samej składni interpolatora, którą już obsługuje AngularJS:
Tabela tłumaczeń:
// /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello, {{name}}." } }
Zmienną można następnie zdefiniować na kilka sposobów. Tu jest kilka:
<!-- /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>
Radzenie sobie z pluralizacją i płcią
Pluralizacja to dość trudny temat, jeśli chodzi o i18n i l10n. Różne języki i kultury mają różne zasady dotyczące tego, jak język radzi sobie z pluralizacją w różnych sytuacjach.
Z powodu tych wyzwań twórcy oprogramowania czasami po prostu nie rozwiązują problemu (lub przynajmniej nie rozwiązują go odpowiednio), co skutkuje oprogramowaniem, które generuje głupie zdania, takie jak:
He saw 1 person(s) on floor 1. She saw 1 person(s) on floor 3. Number of people seen on floor 2: 2.
Na szczęście istnieje standard, jak sobie z tym poradzić, a implementacja standardu JavaScript jest dostępna jako MessageFormat.
Dzięki MessageFormat możesz zastąpić powyższe słabo ustrukturyzowane zdania następującymi:
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
akceptuje wyrażenia takie jak:
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(' ');
Możesz zbudować formater z powyższą tablicą i użyć go do wygenerowania ciągów:
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.'
Jak używać MessageFormat
z angular angular-translate
, aby wykorzystać jego pełną funkcjonalność w swoich aplikacjach?
W konfiguracji aplikacji po prostu mówisz angular-translate
, że interpolacja formatu wiadomości jest dostępna w następujący sposób:
/src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); });
Oto jak może wtedy wyglądać wpis w tabeli tłumaczeń:
// /src/app/main/social/i18n/en.json { "SHARED": "{GENDER, select, male{He} female{She} other{They}} shared this." }
A na widoku:
<!-- /src/app/main/social/social.html --> <div translate="SHARED" translate-values="{ GENDER: 'male' }" translate-interpolation="messageformat"></div> <div> {{ 'SHARED' | translate:"{ GENDER: 'male' }":'messageformat' }} </div>
Tutaj musisz wyraźnie wskazać, że interpolator formatu wiadomości powinien być używany zamiast domyślnego interpolatora w AngularJS. Dzieje się tak, ponieważ dwa interpolatory różnią się nieco składnią. Więcej na ten temat przeczytasz tutaj.
Udostępnianie tabel tłumaczeniowych do Twojej aplikacji
Skoro już wiesz, w jaki sposób AngularJS może wyszukiwać tłumaczenia dla Twoich tokenów z tabel tłumaczeń, skąd Twoja aplikacja w ogóle wie o tabelach tłumaczeń? W jaki sposób informujesz swoją aplikację, który język/język ma być używany?
Tutaj dowiesz się o $translateProvider
.
Możesz podać tabele tłumaczeń dla każdego języka, który chcesz obsługiwać, bezpośrednio w pliku core.config.js
aplikacji w następujący sposób:
// /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'); });
Tutaj podajesz tabele tłumaczeń jako obiekty JavaScript dla angielskiego (en) i tureckiego (tr), deklarując bieżący język jako angielski (en). Jeśli użytkownik chce zmienić język, możesz to zrobić za pomocą usługi $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... }; });
Nadal pozostaje pytanie, który język powinien być używany domyślnie. Zakodowanie początkowego języka naszej aplikacji może nie zawsze być akceptowalne. W takich przypadkach alternatywą jest próba automatycznego określenia języka za pomocą $translateProvider:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.determinePreferredLanguage(); });
determinePreferredLanguage
wyszukuje wartości w window.navigator
i wybiera inteligentne ustawienie domyślne, dopóki użytkownik nie otrzyma wyraźnego sygnału.
Leniwo ładujące się tabele tłumaczeń
W poprzedniej sekcji pokazano, w jaki sposób można udostępnić tabele tłumaczeń bezpośrednio w kodzie źródłowym jako obiekty JavaScript. Może to być akceptowalne w przypadku małych aplikacji, ale podejście nie jest skalowalne, dlatego tabele tłumaczeń są często pobierane jako pliki JSON ze zdalnego serwera.
Utrzymywanie tablic translacji w ten sposób zmniejsza początkowy rozmiar ładunku dostarczanego do klienta, ale wprowadza dodatkową złożoność. Teraz stoisz przed wyzwaniem projektowym polegającym na dostarczaniu klientowi danych i18n. Jeśli nie zostanie to potraktowane ostrożnie, wydajność aplikacji może niepotrzebnie ucierpieć.
Dlaczego jest tak złożony? Aplikacje AngularJS są zorganizowane w moduły. W złożonej aplikacji może być wiele modułów, z których każdy ma własne, odrębne dane i18n. Dlatego należy unikać naiwnego podejścia, takiego jak ładowanie i dostarczanie danych i18n jednocześnie.
Potrzebujesz sposobu na uporządkowanie danych i18n według modułów. Umożliwi to załadowanie dokładnie tego, czego potrzebujesz, kiedy tego potrzebujesz, oraz buforowanie tego, co zostało wcześniej załadowane, aby uniknąć ponownego ładowania tych samych danych (przynajmniej do czasu, gdy pamięć podręczna jest nieprawidłowa).
W tym miejscu do gry wkracza partialLoader
.
Załóżmy, że tabele tłumaczeń Twojej aplikacji mają następującą strukturę:
/src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json
Możesz skonfigurować $translateProvider
, aby używał partialLoader
ze wzorcem adresu URL, który pasuje do tej struktury:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/src/app/{part}/i18n/{lang}.json' }); });
Jak można się spodziewać, „lang” jest zastępowane kodem języka w czasie wykonywania (np. „en” lub „tr”). A co z „częścią”? Skąd $translateProvider wie, którą „część” ma załadować?
Możesz podać te informacje wewnątrz kontrolerów za pomocą $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'); });
Wzorzec jest teraz kompletny, a dane i18n dla danego widoku są ładowane, gdy jego kontroler jest uruchamiany po raz pierwszy, co jest dokładnie tym, czego chcesz.
Buforowanie: skrócenie czasu ładowania
A co z buforowaniem?
Możesz włączyć standardową pamięć podręczną w konfiguracji aplikacji za pomocą $translateProvider
:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoaderCache(true); // default is false });
Jeśli potrzebujesz zepsuć pamięć podręczną dla danego języka, możesz użyć $translate
:
$translate.refresh(languageKey); // omit languageKey to refresh all
Dzięki tym elementom Twoja aplikacja jest w pełni umiędzynarodowiona i obsługuje wiele języków.
Lokalizowanie liczb, walut i dat
W tej sekcji dowiesz się, jak używać angular-dynamic-locale
do obsługi formatowania elementów interfejsu użytkownika, takich jak liczby, waluty, daty i tym podobne, w aplikacji AngularJS.
W tym celu będziesz musiał zainstalować dwa dodatkowe pakiety:
npm i -S angular-dynamic-locale angular-i18n
Po zainstalowaniu pakietów możesz dodać moduł do zależności swojej aplikacji:
// /src/app/core/core.module.js app.module('app.core', ['tmh.dynamicLocale', ...]);
Zasady regionalne
Reguły ustawień regionalnych to proste pliki JavaScript, które zawierają specyfikacje dotyczące formatowania dat, liczb, walut itp. przez komponenty zależne od usługi $locale.
Lista aktualnie obsługiwanych języków jest dostępna tutaj.
Oto fragment z angular-locale_en-us.js
ilustrujący formatowanie miesiąca i daty:
... "MONTH": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], "SHORTDAY": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], ...
W przeciwieństwie do danych i18n, reguły ustawień regionalnych są globalne dla aplikacji, wymagając załadowania wszystkich reguł dla danej lokalizacji jednocześnie.
Domyślnie angular-dynamic-locale
oczekuje, że pliki reguł ustawień regionalnych będą zlokalizowane w angular/i18n/angular-locale_{{locale}}.js
. Jeśli znajdują się w innym miejscu, należy użyć tmhDynamicLocaleProvider
, aby zastąpić wartość domyślną:
// /src/app/core/core.config.js app.config(function (tmhDynamicLocaleProvider) { tmhDynamicLocaleProvider.localeLocationPattern( '/node_modules/angular-i18n/angular-locale_{{locale}}.js'); });
Buforowanie jest automatycznie obsługiwane przez usługę tmhDynamicLocaleCache
.
Unieważnienie pamięci podręcznej jest tutaj mniej ważne, ponieważ reguły regionalne są mniej podatne na zmiany niż translacje ciągów.
Aby przełączać się między lokalizacjami, angular angular-dynamic-locale
udostępnia usługę 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... }; });
Generowanie tabel tłumaczeń z automatycznym tłumaczeniem
Reguły ustawień regionalnych są dostarczane z pakietem angular-i18n
, więc wszystko, co musisz zrobić, to udostępnić zawartość pakietu dla swojej aplikacji w razie potrzeby. Ale jak generujesz pliki JSON dla swoich tabel tłumaczeń? Nie ma dokładnie takiego pakietu, który mógłbyś pobrać i podłączyć do naszej aplikacji.
Jedną z opcji jest użycie interfejsów API tłumaczenia programowego, zwłaszcza jeśli ciągi w aplikacji są prostymi literałami bez zmiennych lub wyrażeń w liczbie mnogiej.
Dzięki Gulp i kilku dodatkowym pakietom prośba o tłumaczenie programistyczne dla Twojej aplikacji jest bardzo prosta:
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 ...
Skrypt najpierw odczytuje wszystkie angielskie tabele tłumaczeń, asynchronicznie żąda tłumaczenia dla ich zasobów ciągów, a następnie zastępuje angielskie ciągi ciągami przetłumaczonymi, aby utworzyć tabelę tłumaczeń w nowym języku.
Wreszcie, nowa tabela tłumaczeń jest zapisywana jako rodzeństwo angielskiej tabeli tłumaczeń, co daje:
/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 ...
Implementacja getTranslation
jest również prosta:
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 }); }
Tutaj używamy Tłumacza Microsoft, ale można łatwo użyć innego dostawcy, takiego jak Tłumacz Google lub Tłumacz Yandex.
Chociaż tłumaczenia programistyczne są wygodne, istnieje kilka wad, w tym:
- Tłumaczenia robotów są dobre dla krótkich ciągów, ale nawet wtedy mogą pojawić się pułapki ze słowami, które mają różne znaczenia w różnych kontekstach (np. „basen” może oznaczać pływanie lub grupowanie).
- Interfejsy API mogą nie obsługiwać ciągów ze zmiennymi lub ciągami, które opierają się na formacie komunikatu.
W tych i innych przypadkach mogą być wymagane tłumaczenia wykonane przez człowieka; jest to jednak temat na inny wpis na blogu.
Internacjonalizacja front-endów tylko wygląda zniechęcająco
W tym artykule dowiedziałeś się, jak używać tych pakietów do internacjonalizacji i lokalizacji aplikacji AngularJS.
angular-translate
, angular-dynamic-locale
i gulp
to potężne narzędzia do internacjonalizacji aplikacji AngularJS, które hermetyzują bolesne szczegóły implementacji niskiego poziomu.
Aby uzyskać aplikację demonstracyjną, która ilustruje pomysły omówione w tym poście, sprawdź to repozytorium GitHub.