Wróć do strony głównej
Angular

Internacjonalizacja – Czyli jak otworzyć aplikacje na świat – część 1.

W dzisiejszych czasach internacjonalizacja aplikacji to coś więcej niż jej przetłumaczenie. Chcąc wprowadzić i18n do naszej aplikacji musimy wiązać się z nie tylko z obsługą wielu języków, ale też kultur oraz preferencji lokalnych. W tym artykule opowiem Ci o wszystkim, z czym spotkasz się w trakcie pracy nad internacjonalizacją aplikacji.

Red czy Blue Pill? Czy powinniśmy zaufać twórcom angulara?

Przeglądając rynek bibliotek do internacjonalizacji z pewnością natkniemy się na bibliotekę od twórców samego angulara – @angular/localize. Ci bardziej dociekliwi zadadzą sobie pytanie, po co komuś biblioteki 3rd party, kiedy możemy wybrać sprawdzone rozwiązanie od angulara? Prawda?

Niestety, @angular/localize nie jest z nami od samego początku angulara. W tych odległych czasach community było zmuszone do stworzenia własnego rozwiązania, które pomogłyby wprowadzić i18n do naszego ulubionego frameworka. Pomijając kwestie przeszłości biblioteka od twórców angulara drastycznie się różni od innych dostępnych rozwiązań. Na rynku możemy się spotkać z 2 typami bibliotek służących do internacjonalizacji – compile time oraz runtime.

Biblioteka typu Compile Time

Na czas pisania tego artykułu jedyną biblioteką typu compile time jest nasz “red pill” – @angular/localize. Biblioteka ta charakteryzuje się tłumaczeniem naszej aplikacji w trakcie jej kompilacji. Dla każdego wspieranego przez nas języka tworzony jest osobny bundle, który jest hostowany na specyficznym urlu np:domain.com/en,domain.com/es, domain.com/pl.

Zalety:

  • Przyśpieszony proces renderowania aplikacji
  • Brak wpływu na rozmiar naszej aplikacji
  • Angular-cli, które pozwala wyekstraktować teksty z aplikacji
  • Wsparcie wielu formatów dla plików z translacjami
  • Prawdopodobnie będzie wspierana aż do “ostatniego dnia” angulara
  • Implementując translacji nie wymaga od nas znania dobrych praktyk takich jak grupowanie i nazywanie kluczy przechowywujących tłumaczenia

Wady:

  • Bardzo ciężko zintegrować tą bibliotekę ze środowiskiem do pisania aplikacji mobilnych ionic oraz desktopowych electron. Wykorzystując wyżej wymienione technologie musimy być również gotowi na bardzo małą ilość informacji oraz poradników dostępnych w internecie
  • Zmiana języka wiąże się z ponownym załadowaniem całej aplikacji. Jest to zasadniczo  największa wada całej biblioteki, szczególnie kiedy priorytetem jest zachowanie retencji użytkowników
  • Brak możliwości dynamicznej modyfikacji tłumaczeń po zbudowaniu aplikacji. Aktualizacja tłumaczenia lub poprawienie literówki wymaga ponownego skompilowana całej aplikacji
  • Relatywnie trudniejszy setup w porównaniu do bibliotek runtime

Biblioteki typu Runtime

Runtime – biblioteki tego typu tłumaczą naszą aplikację w trakcie jej pracy, a pliki z tłumaczeniami pobierają dynamicznie za pomocą requestów http.

Zalety:

  • Zmienianie języka bez potrzeby reloadu aplikacji. Ta funkcjonalność pozwala nam zatrzymać uwagę użytkownika na stronie, nawet gdy ma on słabe łącze internetowe.
  • W przypadku korzystania z electrona czy ionica możemy liczyć na szeroką gamę tutoriali wykorzystujących translacje typu runtime.
  • Możliwość wykorzystania zewnętrznego serwisu, który zarządza translacjami. Pozwala to  między innymi na odciążenie pracy developerów, czy poprawę tłumaczeń po zdeployowaniu aplikacji na serwer (więcej w dalszej części artykułu)
  • Implementacja tego typu bibliotek z pozoru jest bardzo prosta. Zapewniają one świetny developer experience

Wady:

  • Wpływ na performance aplikacji – mimo, że jest on niewielki dzięki zastosowaniu memoizacji, warto wciąż o nim pamiętać
  • Wysyłanie dodatkowych requestów fetchujących pliki z translacjami. Tę wadę można ograniczyć za pomocą wykorzystania cache’owania po pierwszym pobraniu translacji. Możemy również podzielić translacje na moduły oraz ładować je na żądanie
  • Wpływ na wielkość bundle size naszej aplikacji
  • Brak natywnego wsparcia do dynamicznej zmiany injection tokena LOCALE_ID po inicjalizacji aplikacji
  • Jeden mały błąd powodujący brak dostępu do plików z tłumaczeniami jest w stanie zatrzymać cały proces tłumaczenia naszej aplikacji. Przykładem może być to nieprawidłowy setup dostępu do plików czy wysyłanie requestu ze zbędnymi headerami np: Authorization
  • Dobra oraz skalowalna implementacja dowolnej biblioteki typu runtime wymaga od nas znacznie większej wiedzy w porównaniu do skorzystania z natywnego @angular/localize

Na czas pisania artykułu na rynku angulara możemy zauważyć 3 główne biblioteki, pozwalające nam zinternacjonalizowane naszej aplikacji. Są to:

  • Angular-i18next (~10k pobrań) – Angularowa implementacja biblioteki i18next. Nie zdobyła ona masywnej popularności tak jak w przypadku reacta czy vue. Wybierając tą bibliotekę musimy być gotowi na to, że pewnego dnia dotrzemy do jej ograniczeń.
  • @ngx-translate/core (~720k pobrań) – Jest to najbardziej popularna biblioteka typu runtime. Przez długi czas przyszłość biblioteki była nieznana, a prace nad nią stanęły. Na czas pisania artykułu biblioteka dostała pierwszy update po 1.5 roku.
  • @ngneat/transloco (~90k pobrań) – Sukcesor ngx-translate. Według mnie jest to najlepsza biblioteka typu runtime obecnie dostępna na rynku angulara. Do kilku z szerokiej gamy zalet tej biblioteki należą niesamowity DX, bardzo dobra dokumentacja czy szeroka gama dostępnych pluginów.

Czy powinniśmy wybrać redpill?

Jak pewnie sam widzisz, nie ma jednego idealnego rozwiązania do implementacji i18n w angularze. Świetnie pokazują to dane pokazujące ilość pobrań bibliotek – po zsumowaniu oba podejścia są równie często wybierane. @angular/localize ma na swoim koncie około 740k pobrań tygodniowo, gdzie 3 wyżej wspomniane biblioteki runtime po zsumowaniu liczą około 820k pobrań tygodniowo.

Wracając do oryginalnego pytania, czy powinniśmy zaufać twórcom angulara i wybrać ich “redpill”? Moim zdaniem, zdecydowanie warto jest rozważyć tą opcję. Angular localize jest bardzo solidną biblioteką, która z łatwością pozwala nam na nie tylko przetłumaczenie, ale również pełnowymiarową internacjonalizację aplikacji.

Pamiętaj jednak, że przy wybraniu biblioteki powinieneś się skupić na indywidualnych potrzebach twojej aplikacji oraz przedstawionych wymaganiach biznesowych. Warto również pamiętać, o potencjalnych problemach związanych z pełnym wprowadzeniem i18n w przypadku bibliotek typu runtime, które pojawiają się dopiero po dłuższym czasie. Przykładem tego może być, chociażby problem z dynamicznym ustawieniem injection tokena LOCALE_ID, który pozwala nam formatowanie danych przy użyciu wbudowanych pipe’ów.

Stworzenie prostej aplikacji z użyciem transloco

W tej części artykułu skupię się na implementacji biblioteki @ngneat/transloco oraz dobrych praktykach związanych z internacjonalizacją aplikacji. Bez zbędnych szczegółów przejdźmy do tego, co kochamy najbardziej – kodzenia.

Po stworzeniu nowego projektu dodajmy do niego transloco za pomocą komendy:

ng add @ngneat/transloco

W terminalu zostaniemy poproszeni o wybranie kodów językowych, dla których ma zostać wygenerowana konfiguracja oraz pliki z tłumaczeniami. Listę dostępnych kodów ISO 639-1 możecie znaleźć tutaj https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes

Spójrzmy na zmiany, które zostały wprowadzone w naszym projekcie. Po pierwsze w folderze app możemy ujrzeć nowo wygenerowany plik transloco-root.module.ts Plik ten zawiera 2 rzeczy:

  • HttpLoader – serwis zajmujący się fetchowaniem plików z translacjami do naszej aplikacji. To właśnie serwis będziemy edytować, kiedy będziemy chcieli zmienić lokalizację plików z translacjami lub w trakcie implementacji zewnętrznego serwisu przechowującego translacje

  • TranslocoRootModule – zawiera on konfiguracje naszej biblioteki. Możemy tutaj skonfigurować wspierane przez nas języki, domyślny język oraz to, czy chcemy wspierać zmianę języka w trakcie pracy aplikacji. Warto zauważyć, że moduł ten został zaimportowany automatycznie w głównym module naszej aplikacji.


    Kolejnym plikiem, który został dodany do naszego projektu jest transolo.config.js. To właśnie tutaj skonfigurujemy, ścieżkę do plików z translacjami, wspierane języki czy scoped libraries.

    Ostatnią rzeczą, która doszła do naszego projektu jest folder i18n wewnątrz folderu assets. Zawiera on nowo powstałe pliki json, które przechowują translacje. Pliki te przechowują translacje po kluczach, dokładnie tak samo jak obiekty w javascripcie.

    Dodanie Translacji

    Dodajmy kilka przykładowych translacji do naszej aplikacji.

en.json

pl.json

Jak pewnie zauważyłeś utworzyliśmy w pliku kilka różnych typów translacji. Translacje możemy podzielić na kilka typów:

  • Translacje Tekstowe – przechowywują tekst dla danego klucza np: languageName, foodStorage.
  • Translacje Interpolowalne – przechowywują tekst rozszerzony o placeholdery, które są interpolowane wartościami z parametrów w trakcie procesu translacji Np: itemTitle
  • Translacje z Pluralizacją (select) – zawierają warunkowo wyświetlony tekst, który jest zależny od wartości stringa podanego w parametrze. Np: itemType
  • Translacje z Pluralizacją (amount) – zawierają warunkowo wyświetlany tekst w zależny od liczby podanej w parametrze. Np: amountInStock

Translacje możemy grupować w obiektach. Chcąc odnieść się do zagnieżdżonej translacji należy podać ścieżkę do niej oddzieloną znakami kropki. Np: itemSelect.grapes, itemSelect.carrot

Jak używać translacji w templatkach?

Po utworzeniu translacji śmiało możemy przejść do ich wykorzystania w kodzie. Transloco daje nam kilka różnych możliwości, w jaki możemy przetłumaczyć nasze klucze w templatkach:

  • Attribute directive – w tym podejściu podajemy klucz w atrybucie transloco, oraz opcjonalne parametry w inpucie translocoParams.

  • Transloco Pipe – rozwiązanie bardzo podobne do tego z biblioteki ngx-translate. Transformujemy klucz przy użyciu pipe’a, a ewentualne parametry podajemy w jego 1 argumencie.

  • Strukturalna dyrektywa – Moje ulubione rozwiązanie, którego brakuje mi w innych bibliotekach typu run-time. Dzięki dyrektywie *transloco otrzymujemy funkcje t, za pomocą której możemy przetłumaczyć klucze w templatce. Tłumaczenie kluczy za pomocą tego podejścia jest rekomendowane przez samych twórców biblioteki. To podejście ma 3 zalety – jest zgodne z zasadą DRY, jest bardzo szybkie dzięki zastosowaniu memoizacji oraz tworzy tylko 1 subskrypcję w templatce.

Skoro już znamy wszystkie możliwości, za pomocą których możemy przetłumaczyć klucze przejdźmy do stworzenia naszej małej aplikacji.

Stworzenie Prostej Aplikacji

app.component.ts

W pliku app.component.ts wstrzykujemy instancję TranslocoService oraz deklarujemy metodę getAvailableLanguages, która będzie zwracać nam listę kodów wspieranych przez nas języków. W powyższym kodzie tworzymy również metodę onLanguageChange, która będzie zarządzać zmianą języka.

app.component.html

Aby zaimplementować zmianę języka w template tworzymy select z wartościami wspieranych języków w naszej aplikacji. Przy zmianie wartości selecta wykonujemy metodę onLanguageChange, która ustawia nowy język w naszej aplikacji.

Po wykonaniu powyższych kroków powinieneś móc bez problemu zmieniać język w aplikacji za pomocą selecta. Po zmianie języka tekst wewnątrz tagu h1 powinien ulec zmianie.

Zatrzymajmy się na chwilę, aby omówić co ciekawsze metody TranslocoService.

  • getAvailableLangs() – zwraca listę wspieranych języków w naszej aplikacji
  • getActiveLang() – zwraca obecnie wybrany język
  • setActiveLang() – ustawia nowy język
  • translate(key, params) – pozwala na synchroniczne przetłumaczenie klucza
  • selectTranslate(key, params) – asynchroniczne tłumaczenie klucza – metoda zwraca strumień, który emituje przetłumaczony klucz za każdym razem kiedy w naszej aplikacji zmieni się język.
  • Pozostałe metody TranslocoService https://ngneat.github.io/transloco/docs/translation-api/

Aby, jeszcze bardziej przybliżyć Ci TranslocoService skorzystajmy z niego raz jeszcze. Ustawmy tytuł strony w zależności od wybranego przez użytkownika języka. Przy implementacji tej funkcjonalności wykorzystamy nowy operator takeUntilDestroyed() z angulara 16.

Nazewnictwo kluczy z translacjami

Na przestrzeni lat powstało wiele metodyk oraz sposobów nazewnictwa kluczy do translacji. W tej części artykułu przybliżę Ci te metodyki, pokaże ich zalety oraz wady.

Metodyka “Bezpośrednia”

Ta metodyka polega na nadawaniu kluczom nazw bezpośrednio po oryginalnym tłumaczeniu. Dużą zaletą tej metodyki jest jej intuicyjność i prostota, co minimalizuje ryzyko niepotrzebnego duplikowania tłumaczeń w przyszłości.

E.g.

W trakcie nadawania kluczy opisom, paragrafom oraz wszelkiej maści długim tekstom powinniśmy się skupić na ich głębszym kontekście. Stworzony przez nas klucz powinien być stosunkowo krótki, ale jednocześnie powinien podsumowywać cały tekst. Dobrym pomysłem może być również nadanie suffixu description.

E.g.

“internationalizationBenefitsDescription”: “Internationalizing your application results in improved market reach, cultural inclusivity, user satisfaction and increased opportunities for your brand.”

Największym problemem tej metodyki jest sytuacja, w której napotykamy na wyraz o wielu znaczeniach. Przykładem takiego wyrazu może być angielskie “lie”, które może oznaczać zarówno “leżeć”, jak i “kłamać”. W takich przypadkach warto jest rozszerzyć klucz o szerszy kontekst.

E.g.

Używanie znaków interpunkcyjnych i diakrytycznych w kluczach może prowadzić do błędów. Dlatego, gdy napotkamy taki znak podczas translacji, najlepiej jest go pomijać, aby uniknąć problemów w przyszłości.

Metodyka z Grupowaniem

Kolejną metodyką przechowywania oraz tworzenia kluczy jest ich grupowanie po widokach. Ta metodyka sprawdza się świetnie w przypadku odosobnionych sekcji naszej aplikacji. Do zalet tego rozwiązania z pewnością należy bezinwazyjność – z racji iż klucze są zgrupowane po widokach, możemy zmieniać ich tłumaczenie bez obawy o zmianę tłumaczenia w innej warstwie aplikacji, tak jak miałoby to miejsce w przypadku metodyki bezpośredniej.

E.g.

(Chcąc odnieść się do zagnieżdżonych kluczy używamy “dot notation”, która polega na dodaniu kropki w przypadku zagnieżdżenia. Np paymentDialog.proceed)

Kolejną zaletą tego rozwiązania jest możliwość lazy-loadingu translacji wraz z modułami. Po wyodrębnieniu translacji do osobnego pliku oraz szybkiej konfiguracji jesteśmy w stanie zmniejszyć initial loading time naszej aplikacji. Więcej o tym możecie poczytać tutaj. https://ngneat.github.io/transloco/docs/lazy-load/scope-configuration

Przejdźmy teraz do często popełnianych błędów oraz popularnych problemów, z którymi spotkamy się używając grupowanych translacji.

Pierwszym z nich jest zbyt generalne nazywanie subkluczy – np: description, paragraph, button itd. Nazywając w ten sposób klucze wychodzimy z założenia, że nasz widok zawsze będzie miał jeden przycisk, paragraf czy inny element, co z pewnością może zmienić się w przyszłości. W przypadku dodania nowego elementu wraz z tłumaczeniem, bardzo ciężko jest określić, do jakiego elementu ui odnosi się klucz. Na szczęście, ten problem bardzo łatwo można obejść, poprzez zastosowanie metodyki bezpośredniej.

Np:

Kolejnym błędem występującym w tej metodyce jest zbyt duże zagnieżdżanie translacji. Warto zastosować się do limitu 3 zagnieżdżeń – po przekroczeniu tego progu, nasze klucze stają się bardzo długie, a co za tym idzie nieczytelne.

Wraz z czasem używania metodyki grupowej zauważamy, że duplikujemy niektóre tłumaczenia w innych grupach. Aby rozwiązać ten problem możemy stworzyć grupę “common” lub “generic”, w głównym pliku z translacjami. Wewnątrz tej grupy zawrzemy wszystkie powtarzające się tłumaczenia w naszej aplikacji.

Kilka Rad

  • Pamiętaj, by pod żadnym pozorem nie łączyć translacji ze sobą – wcześniej czy później doprowadzi to do większych problemów. Zamiast konkatenować tłumaczenia zawsze możesz stworzyć nowy klucz, który łączy ze sobą wyrazy, które chcesz ze sobą połączyć.
    • E.g.
    • t(”save”) + t(“changes”) -> t(“saveChanges”)
  • Używać translacji w połączeniu z innerHtml narażasz się na otwarcie luki w bezpieczeństwie, a co za tym idzie potencjalne ataki XSS.

Przejdź do części drugiej

O autorze

Dawid Kostka

Angular Developer w House of Angular. Software development dla mnie to coś więcej niż tylko klepanie kodu. Kocham eksplorować i bawić się wysokiej jakości kodem. Uwielbiam adrenalinę i pozytywne nastawienie. W wolnych chwilach jeżdzę na rolkach agresywnych.

Zapisz się do naszego newslettera. Bądź na bieżąco z najnowszymi trendami, poradami, meetupami i stań się częścią społeczności Angulara w Polsce. Rynek pracy docenia członków społeczności.

Leave a Reply

Your email address will not be published. Required fields are marked *