Cześć, w tym krótszym wpisie chciałbym zapoznać Was z pewną metodą do wdrożenia tłumaczeń, aby nasza aplikacja była wielojęzykowa. Będziemy korzystać z biblioteki:  Angular localization Biblioteka ta służy nie tylko do robienia tłumaczeń ale także do lokalizowaniu numerów i dat aplikacji, dodawaniu kodu języka, kodu kraju, kodu waluty, strefy czasowej i wszelkich innych spraw związanych z lokalizacjami.

Zabieramy się do pracy. 

Aktualna wersja jest pod adresem: https://github.com/taaaniel/foodCalc/tree/part_5_component_modules_end .

Dodawanie serwisu do tłumaczeń najlepiej robić w początkowej fazie developingu, gdyż będziemy musieli praktycznie w każdym komponencie dodawać dekoratory do naszego serwisy tłumaczeń. Zatem im wcześniej zaczniemy tym mniej problemów będziemy mieli potem.

Po pierwsze musimy zainstalować bibliotekę w aplikacji. Będąc w katalogu głównym naszego programu,  w konsoli wpisujemy:

npm install angular-l10n --save

Teraz zaimportujemy bibliotekę do aplikacji.  W pliku shared.module.ts musimy umieścić trochę kodu. Robimy to w naszym „shared module” gdyż tłumaczenia będą używane praktycznie w każdym komponencie, tak samo jak  moduły z „Material Design” Na początek importujemy poszczególne moduły.

import { HttpClientModule } from '@angular/common/http';
import { L10nConfig, L10nLoader, LocalizationModule, StorageStrategy, ProviderType, LogLevel } from 'angular-l10n';

Teraz dodajemy konfigurację:

const l10nConfig: L10nConfig = {
  locale: {
      languages: [
          { code: 'pl', dir: 'ltr' },
          { code: 'en', dir: 'ltr' }
      ],
      language: 'pl',
      storage: StorageStrategy.Cookie
  },
  translation: {
      providers: [
          { type: ProviderType.Static, prefix: './assets/locale/' }
      ],
      caching: true,
      composedKeySeparator: '.',
  }
};

 

Zatrzymajmy się na chwilę by omówić tę konfigurację:

  • w obiekcje locale mamy tablicę obiektów, w których znajduje się kod/skrót języków jakich będziemy używać i tajemnicze „dir”. Jest to skrót od „direction” a w tym konkretnym przykładzie chodzi o kierunek czytania. „ltr” to „left to right”
  • language-  domyślny język
  • storage – W przypadku pominięcia daty ważności pliku cookie plik cookie staje się plikiem cookie sesji. Czyli ustawiamy, że nasz język będzie przetrzymywany w Ciasteczkach
  • w obiekcie „translation” propercja „providers” odpowiada za ładowanie asynchroniczne: wyłącza / włącza pamięć podręczną dla dostawców tłumaczeń i tutaj podajemy ścieżkę do naszego słownika w formacie JSON (tutaj jest prefix, zatem będziemy tworzyć pliki pl.json i en.json zgodnie z kodem językowym który jest wyżej  w tabeli languages) o czym w dalszej części
  • „catching” to ładowanie asynchroniczne: dodaje parametr zapytania „ver” do żądań http.
  • „composedKeySeparator” tutaj podajemy jakim znakiem będziemy łączyć klucze tłumaczeń. Wyjaśni się to poniżej gdy będziemy pisać słownik.

Teraz musimy dodać nasz moduł translacyjny do tablicy importów:

HttpClientModule,
LocalizationModule.forRoot(l10nConfig)

i eksportów:

HttpClientModule,
LocalizationModule

Następnie, kreujemy konstruktor w shared.module.ts i wstrzykujemy tam loader od modułu tłumaczeń.

export class SharedModule {
  constructor(public l10nLoader: L10nLoader) {
    this.l10nLoader.load();
  }
 }

Cały shared.module.ts wygląda tak:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  MatButtonModule,
  MatCardModule,
  MatMenuModule,
  MatToolbarModule,
  MatIconModule,
  MatListModule,
  MatChipsModule,
  MatTableModule,
  MatTabsModule } from '@angular/material';
import { FlexLayoutModule } from '@angular/flex-layout';
import { HttpClientModule } from '@angular/common/http';
import { L10nConfig, L10nLoader, LocalizationModule, StorageStrategy, ProviderType, LogLevel } from 'angular-l10n';

const l10nConfig: L10nConfig = {
  locale: {
      languages: [
          { code: 'pl', dir: 'ltr' },
          { code: 'en', dir: 'ltr' }
      ],
      language: 'pl',
      storage: StorageStrategy.Cookie
  },
  translation: {
      providers: [
          { type: ProviderType.Static, prefix: './assets/locale/' }
      ],
      caching: true,
      composedKeySeparator: '.',
  }
};

@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    MatButtonModule,
    MatCardModule,
    MatMenuModule,
    MatToolbarModule,
    MatIconModule,
    MatListModule,
    MatChipsModule,
    MatTableModule,
    MatTabsModule,
    FlexLayoutModule,
    HttpClientModule,
    LocalizationModule.forRoot(l10nConfig)
  ],
  exports: [
    MatButtonModule,
    MatCardModule,
    MatMenuModule,
    MatToolbarModule,
    MatIconModule,
    MatListModule,
    MatChipsModule,
    MatTableModule,
    MatTabsModule,
    FlexLayoutModule,
    HttpClientModule,
    LocalizationModule
  ]
})
export class SharedModule {
  constructor(public l10nLoader: L10nLoader) {
    this.l10nLoader.load();
  }
 }

 

Po edycji shared.module.ts do katalogu „assets” dodajemy katalog/folder „locale”.  Może być inny, ale pamiętajmy by był zgodny ze ścieżką w konfiguracji w shared.module.ts . Chodzi o tę linijkę:

{ type: ProviderType.Static, prefix: './assets/locale/' }
  ],

Teraz w folderze „locale” tworzymy dwa pliki .json:

  • pl.json – słownik kluczy i tłumaczeń na język polski
  • en.json – słownik kluczy i tłumaczeń na język angielski

Kiedy mamy już to zrobione przechodzimy do header.component.html i dodajemy w widoku trochę kodu, który utworzy menu zmiany języka. Całość kodu headera wygląda tak:

<header fxLayout='row'>
  <mat-toolbar color="primary">
    <div class="app-logo">
      <h1>FoodCalc</h1>
    </div>
    <div fxFlex></div>
    <!-- Languages switch menu -->
    <div>
      <button mat-button [matMenuTriggerFor]="languageMenu">
        <mat-icon>language</mat-icon>
        <mat-icon>arrow_drop_down</mat-icon>
      </button>
      <mat-menu  #languageMenu overlapTrigger="false">
        <button (click)="selectLanguage('pl')" mat-menu-item>
          {{ 'HEADER.LANGUAGE_POLISH'}}
        </button>
        <button (click)="selectLanguage('en')" mat-menu-item>
          {{ 'HEADER.LANGUAGE_ENGLISH' }}
      </button>
      </mat-menu>
    </div>
     <!-- END Languages switch menu -->
    <div>
      <button mat-icon-button [matMenuTriggerFor]="menu">
        <mat-icon>person</mat-icon>
        <mat-icon>arrow_drop_down</mat-icon>
      </button>
      <mat-menu #menu="matMenu">
        <button mat-menu-item>
          <mat-icon>fingerprint</mat-icon>
          <span>Login</span>
        </button>
        <button mat-menu-item disabled>
          <mat-icon>close</mat-icon>
          <span>Logout</span>
        </button>
        <button mat-menu-item>
          <mat-icon>content_paste</mat-icon>
          <span>Registration</span>
        </button>
      </mat-menu>
    </div>
    <div fxFlex='20px'></div>
  </mat-toolbar>
</header>

 

Dodana cześć kodu jest jest oznaczona komentarzem.

Zwróćmy teraz uwagę na dziwne zapisy :

{{ ‚HEADER.LANGUAGE_POLISH’ }}
{{ ‚HEADER.LANGUAGE_ENGLISH’}}
To własnie klucze tłumaczeń. Zauważmy, że są tak jakby rozbudowane i pojawia się w nich słowo HEADER.  Jest to swego rodzaju konwencja tworzenia kluczy. Jest tak jakby kontekstowa. Słówko „HEADER” sugeruje, że słowo, które tłumaczymy jest w „headerze” a dalszy człon „LANGUAGE_POLISH” to wartość klucza którą należy przetłumaczyć. Oczywiście jest to tylko konwencja i każdy może to robić jak mu się podoba. W moim przypadku jest ona bardzo dobra ponieważ pomaga w uporządkowaniu słownika w plikach .json, o czym się zaraz przekonamy.
Kolejnym krokiem jest utworzenie słownika. Zacznijmy od polskiego. Otwieramy plik pl.json w katalogu /assets/locale i wpisujemy tam taki kod:
{
  "HEADER": {
    "LANGUAGE_POLISH": "Język Polski",
    "LANGUAGE_ENGLISH": "Język Angielski"
  }
}

Widzimy tutaj obiekt, który będzie zawierał inne obiekty w, których znajdować się będą klucze i tłumaczenia. Patrząc na przykład powyżej możemy zauważyć jak będą układane i zarządzane klucze jak i ich wartości. Klucze te są przyporządkowane kontekstowo czyli, jeśli mamy komponent „HEADER” to wszystkie teksty znajdujące się w nagłówku będą miały swoje klucze pod propercją”HEADER”. Możemy stworzyć także inne propercje sugerujące inne komponenty i/lub ogólne słowa powtarzające się, czy znajdujące się tylko na przyciskach.  Ułatwi nam to znacznie utrzymanie aplikacji, wyszukiwanie tłumaczeń jak i samo pisanie programu.

Pamiętacie jeszcze wyżej opisany „composedKeySeparator”, to własnie tutaj robi on robotę. Definiuje nam separator, którym łączymy odpowiednie propercje. Dzięki temu biblioteka Angular localization wie gdzie szukać odpowiedniej wartości:  ‚HEADER.LANGUAGE_POLISH’

Plik en.json wygląda tak:
{
  "HEADER": {
    "LANGUAGE_POLISH": "Polish",
    "LANGUAGE_ENGLISH": "English"
  }
}

Wróćmy do komponentu headera. W pliku haeder.component.ts aby dodać serwis który zaimplementowaliśmy w shared.module.ts importujemy odpowiednie odnośniki

import { LocaleService, TranslationService, Language } from 'angular-l10n';

Następnie dodajemy zmienną wybranego języka

public selectedLanguage: string;

Jest to zmienna, która pokazuje jaki język jest teraz wybrany i będzie nam potrzebna także na widoku.

Dodajemy nowy dekorator w klasie  HeaderComponent

@Language() lang: string;

a w konstruktorze wstrzykujemy dwa serwisy:

constructor(
    public locale: LocaleService,
    public translation: TranslationService
  ) { }

Przechodzimy do ngOnInit() i korzystając z serwisu locale, który zaimportowaliśmy pobieramy Obecny język który jest ustawiany w ciasteczku, a jeśłi nie jest w ciasteczku to ustawiany jest domyślny czyli polski, który ustawiliśmy w propercji :

defaultLocale: { languageCode: ‚en’, countryCode: ‚EN’ } w shared.module.ts
this.selectedLanguage = this.locale.getCurrentLanguage();

Jeśli na razie nie rozumiesz jak wyciągamy metody z serwisów, nie przejmuj się będzie o tym mowa w następnych częściach kursu.

Mamy już praktycznie wszystko, jednakże widoku widzieliśmy wywołanie metody selectLanguage() z odpowiednim parametrem:

<button (click)="selectLanguage('en')" mat-menu-item>

Musimy ją także napisać. Jest to metoda która jest wywoływana na kliku w button i zmienia ona język aplikacji.

selectLanguage(language: string): void {
    this.locale.setCurrentLanguage(language);
    this.selectedLanguage = language;
  }

W tej funkcji odwołujemy się także do serwisu „locale” i dzięki metodzie setCurrentLanguage ustawiamy/zmieniamy język.

Na koniec do widoku wstawiamy zmienną selectedLanguage pokazującą aktualny język, a także musimy dodać filtr który ptrzetworzy klucz językowy na wartość.

Filtry w angularze dodajemy poprzez znaczek „|” a zatem w kodzie wygląda to tak:

<button (click)="selectLanguage('en')" mat-menu-item>
 {{ 'HEADER.LANGUAGE_POLISH' | translate: lang}}
</button>
<button (click)="selectLanguage('pl')" mat-menu-item>
 {{ 'HEADER.LANGUAGE_ENGLISH' | translate: lang}}
</button>

Całość widoku nagłówka prezentuje się następująco:

<header fxLayout='row'>
  <mat-toolbar color="primary">
    <div class="app-logo">
      <h1>FoodCalc</h1>
    </div>
    <div fxFlex></div>
    <!-- Languages switch menu -->
    <div>
      <button mat-button [matMenuTriggerFor]="languageMenu">
        <mat-icon>language</mat-icon>
         <span>{{ selectedLanguage }}</span>
        <mat-icon>arrow_drop_down</mat-icon>
      </button>
      <mat-menu  #languageMenu overlapTrigger="false">
        <button (click)="selectLanguage('pl')" mat-menu-item>
          {{ 'HEADER.LANGUAGE_POLISH' | translate: lang}}
        </button>
        <button (click)="selectLanguage('en')" mat-menu-item>
          {{ 'HEADER.LANGUAGE_ENGLISH' | translate: lang}}
      </button>
      </mat-menu>
    </div>
     <!-- END Languages switch menu -->
    <div>
      <button mat-icon-button [matMenuTriggerFor]="menu">
        <mat-icon>person</mat-icon>
        <mat-icon>arrow_drop_down</mat-icon>
      </button>
      <mat-menu #menu="matMenu">
        <button mat-menu-item>
          <mat-icon>fingerprint</mat-icon>
          <span>Login</span>
        </button>
        <button mat-menu-item disabled>
          <mat-icon>close</mat-icon>
          <span>Logout</span>
        </button>
        <button mat-menu-item>
          <mat-icon>content_paste</mat-icon>
          <span>Registration</span>
        </button>
      </mat-menu>
    </div>
    <div fxFlex='20px'></div>
  </mat-toolbar>
</header>

Teraz sami spróbujmy dodać wszędzie tłumaczenia i klucze. Pamiętajmy,  że w każdym komponencie w którym chcemy dodać tłumaczenia musimy w konstruktorze dodać serwisy i je zaimportować:

constructor(
    public locale: LocaleService,
    public translation: TranslationService
  ) { }

a także decorator:

@Language() lang: string;

Końcowa wersja jest pod adresem: https://github.com/taaaniel/foodCalc/tree/part_6_translation_service_end

W następnym wpisie napiszemy routing do apki i połączymy resztę komponentów i modułów.

Jak zwykle zapraszam do komentowania i zadawanie pytań.

Notatki:

Źródła

 

Zostaw komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *