Мы уже давно имеем дело с Angular l10n и i18n. Мы составили список лучших библиотек для Angular i18n и даже описали, как локализовать приложения Angular с помощью ngx-translate или с помощью встроенного модуля i18n. Если вы знакомы с i18next и не хотите переключаться на какой-либо другой фреймворк — мы не можем вас винить — есть возможность интегрировать его в ваш проект Angular с помощью поддерживаемого сообществом плагина. ».

В этой статье мы рассмотрим процесс Angular l10n с i18next и покажем, как:

  • Создайте новый проект Angular, используя последнюю версию
  • Интегрируйте модуль i18next в приложение
  • Установить локаль по умолчанию и переключиться на другую локаль
  • Обрабатывать файлы перевода
  • Используйте интерфейс командной строки Phrase (CLI) для синхронизации наших переводов с облаком и улучшения нашего рабочего процесса.

Кстати, весь код, показанный в этом туториале, также размещен на GitHub. Давайте начнем!

Приготовьтесь к Angular l10n: создайте новый проект Angular 7

Прежде всего, перейдите на Страницу быстрого запуска Angular, установите Node.js, Angular-cli и создайте новое приложение. Назовем его my-i18n-app. Ответьте да, чтобы добавить маршрутизацию и выбрать предпочтительные методы CSS.

➜ npm install -g @angular/cli
➜ ng new my-i18n-app         
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com/documentation/file.SASS_REFERENCE.html#syntax ]
...

Добавить i18следующий

Далее нам нужно подключить библиотеку i18next с помощью angular-i18next provider.

Давайте установим эти библиотеки…

➜ npm install i18next angular-i18next --save

Измените наш app.module.ts, чтобы интегрировать и инициализировать конфигурацию i18next:

import { BrowserModule, Title } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER, LOCALE_ID } from '@angular/core';
import { I18NextModule, ITranslationService, I18NEXT_SERVICE, I18NextTitle, defaultInterpolationFormat } from 'angular-i18next';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';


export function appInit(i18next: ITranslationService) {
  return () => i18next.init({
      whitelist: ['en', 'gr'],
      fallbackLng: 'en',
      debug: true,
      returnEmptyString: false,
      ns: [
        'translation',
        'validation',
        'error',
      ],
      interpolation: {
        format: I18NextModule.interpolationFormat(defaultInterpolationFormat)
      },
    });
}

export function localeIdFactory(i18next: ITranslationService)  {
  return i18next.language;
}

export const I18N_PROVIDERS = [
{
  provide: APP_INITIALIZER,
  useFactory: appInit,
  deps: [I18NEXT_SERVICE],
  multi: true
},
{
  provide: Title,
  useClass: I18NextTitle
},
{
  provide: LOCALE_ID,
  deps: [I18NEXT_SERVICE],
  useFactory: localeIdFactory
}];

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    I18NextModule.forRoot()
  ],
  providers: [I18N_PROVIDERS],
  bootstrap: [AppComponent]
})
export class AppModule { }

Здесь мы просто импортируем библиотеку i18next и запускаем ее на обработчике жизненного цикла appInit. Таким образом, Angular не будет загружаться до тех пор, пока не сработает событие инициализации i18next. По умолчанию мы обрабатываем переводы на английский и греческий языки.

Теперь обновите app.component.html, чтобы вставить тег i18next для перевода контента.

<div style="text-align:center">
  <h1>
    {{ 'message' | i18next }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
  </li>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
  </li>
  <li>
    <h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
  </li>
</ul>

<router-outlet></router-outlet>

Если мы запустим приложение сейчас, мы заметим, что сообщение вообще не отображается. Вместо этого он печатает ключевую строку.

Причина этого в том, что мы не создали и не указали какие-либо строки перевода для библиотеки i18next для поиска. Давайте сделаем это сейчас.

Настройка и переключение локалей

Чтобы загружать переводы в наше приложение, нам нужно установить бэкенд-плагин. Кроме того, мы хотели бы иметь переключатель локали, основанный на нескольких параметрах, таких как файл cookie или параметр URL, поэтому нам нужен другой плагин, который обработает это для нас.

Установите следующие плагины:

npm install i18next-xhr-backend i18next-browser-languagedetector --save

Нам нужен i18next-xhr-backend для загрузки переводов из файла с помощью Ajax и i18next-browser-languagedetector для определения текущей пользовательской локали по некоторым параметрам, которые мы указываем. .

Теперь нам нужно включить их в нашем appInit. Обновите раздел app.module.

export function appInit(i18next: ITranslationService) {
  return () =>
      i18next
      .use(i18nextXHRBackend)
      .use(i18nextLanguageDetector)
      .init({
      whitelist: ['en', 'el'],
      fallbackLng: 'en',
      debug: true,
      returnEmptyString: false,
      ns: [
        'translation'
      ],
      interpolation: {
        format: I18NextModule.interpolationFormat(defaultInterpolationFormat)
      },
      backend: {
        loadPath: 'assets/locales/{{lng}}.{{ns}}.json',
      },
      // lang detection plugin options
      detection: {
        // order and from where user language should be detected
        order: ['querystring', 'cookie'],

        // keys or params to lookup language from
        lookupCookie: 'lang',
        lookupQuerystring: 'lng',

        // cache user language on
        caches: ['localStorage', 'cookie'],

        // optional expire and domain for set cookie
        cookieMinutes: 10080, // 7 days
      }
    });
}

Обратите внимание, что мы использовали следующий loadPath:

loadPath: ‘assets/locales/{{lng}}.{{ns}}.json’,

Это путь, который у нас будет для наших данных локали. Давайте сейчас создадим эту папку и добавим недостающие переводы.

➜ mkdir -p src/assets/locales/
➜ cd src/assets/locales
➜ touch en.translations.json
➜ touch el.translations.json

// el.translations.json
{
  "message": "Καλως ήλθατε στο PhraseApp i18next"
}

// en.translations.json
{
  "message": "Welcome to PhraseApp i18next"
}

Если мы снова запустим приложение и передадим правильные параметры, мы увидим правильное сообщение:

Было бы неплохо, если бы у нас был выпадающий список, который мы могли бы использовать для переключения локали. Давайте сделаем это сейчас:

Во-первых, давайте создадим наш компонент header, в котором будет размещаться раскрывающийся список языков…

➜ mkdir header
➜ touch header/header.component.html
➜ touch header/header.component.ts

Добавьте содержимое раскрывающегося списка:

<div>
    <select formControlName="languages" (change)="changeLanguage($event.target.value)">
        <option *ngFor="let lang of languages" [value]="lang" [selected]="language === lang">
            <a *ngIf="language !== lang" href="javascript:void(0)" class="link lang-item {{lang}}">{{ '_languages.' + lang | i18nextCap }}</a>
            <span *ngIf="language === lang" class="current lang-item {{lang}}">{{ '_languages.' + lang | i18nextCap }}</span>
        </option>
      </select>
</div>

Затем добавьте код обработчика changeLanguage:

import { ITranslationService, I18NEXT_SERVICE } from 'angular-i18next';
import { Component, ViewEncapsulation, Inject, OnInit } from '@angular/core';

@Component({
  selector: 'header-language',
  encapsulation: ViewEncapsulation.None,
  templateUrl: './header.component.html',
})
export class HeaderComponent implements OnInit {

  language = 'en';
  languages: string[] = ['en', 'el'];

  constructor(
    @Inject(I18NEXT_SERVICE) private i18NextService: ITranslationService
  ) {}

  ngOnInit() {
    this.i18NextService.events.initialized.subscribe((e) => {
      if (e) {
        this.updateState(this.i18NextService.language);
      }
    });
  }

  changeLanguage(lang: string){
    if (lang !== this.i18NextService.language) {
      this.i18NextService.changeLanguage(lang).then(x => {
        this.updateState(lang);
        document.location.reload();
      });
    }
  }

  private updateState(lang: string) {
    this.language = lang;
  }
}

Мы используем i18NextService для подписки на события initialized и обновления текущего состояния. В событии changeLanguage мы меняем текущую локаль и перезагружаем наше текущее местоположение. Прежде чем мы снова запустим приложение, нам нужно убедиться, что мы зарегистрировали этот компонент в app.module.ts.

import { HeaderComponent } from './header/header.component';
...

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent . <--- Added
  ],

Наконец, добавьте компонент в app.component.html и запустите приложение.

Использование интеграции библиотеки фраз для обработки файлов перевода

В настоящее время, поскольку мы не используем встроенный модуль i18n, предоставляемый фреймворком, нам нужно быть осторожными с управлением файлами локализации. Многое может пойти не так, и в идеале нам нужна библиотека, которая будет управлять всеми нашими переводческими проектами и синхронизироваться с любыми обновлениями от внешних переводчиков.

Для этого мы можем использовать Phrase CLI для интеграции расширенной системы управления переводами в наше приложение и решения этих проблем. Процесс прост как раз, два, три:

Сначала перейдите на страницу Phrase CLI и установите ее на основе вашей текущей ОС. Затем перейдите на страницу регистрации и зарегистрируйте бесплатную учетную запись, если у вас ее нет.

Прежде чем мы будем взаимодействовать с API, нам нужно настроить наш клиент. Запустите следующую команду в вашей локальной оболочке:

➜ ./phraseapp init
PhraseApp.com API Client Setup

Please enter your API access token (you can generate one in your profile at phraseapp.com): 
<token>

Параметр ‹token› необходим, и вы можете создать его из аккаунта › токены доступа. После создания введите ввод, и вам будет предложено выбрать проект из списка, или вы можете создать новый…

Loading projects...
 1: Test (Id: 8fa47c48c3ba80aebe255e99651de3e4)
 2: WP POT File Test (Id: 5ed97cfde5a2ac8bf4fbc6edd82eb4a9)
 3: Handmade's Tale (Id: 40fe26fda781e98df0134cf91e02aea4)
 4: Create new project
 
 > 1

Далее вам будет предложено указать форматы языковых файлов по умолчанию, которые мы собираемся использовать для этого проекта, и их расположение. Выберите номер 38 для i18next:

...
38: i18next - i18next, file extension: json
39: episerver - Episerver XML, file extension: xml
...
Select the format to use for language files you download from PhraseApp (1-39): 38

Следующий вопрос запрашивает расположение наших локалей. Введите путь к статической папке, который мы использовали ранее:

Enter the path to the language file you want to upload to PhraseApp.
For documentation, see https://help.phraseapp.com/phraseapp-for-developers/phraseapp-client/configuration#push
Source file path: [default ./locales/<locale_name>/translations.json] ./src/assets/locales/<locale_name>.translation.json             

Enter the path to which to download language files from PhraseApp.
For documentation, see https://help.phraseapp.com/phraseapp-for-developers/phraseapp-client/configuration#pull
Target file path: [default ./locales/<locale_name>/translations.json] ./src/assets/locales/<locale_name>.translation.json

We created the following configuration file for you: .phraseapp.yml

phraseapp:
  access_token: <TOKEN>
  project_id: <PROJECT_ID>
  push:
    sources:
    - file: ./src/assets/locales/<locale_name>.translation.json
      params:
        file_format: i18next
  pull:
    targets:
    - file: ./src/assets/locales/<locale_name>.translation.json
      params:
        file_format: i18next

For advanced configuration options, take a look at the documentation: https://help.phraseapp.com/phraseapp-for-developers/phraseapp-client/configuration
You can now use the push & pull commands in your workflow:

$ phraseapp push
$ phraseapp pull

Do you want to upload your locales now for the first time? (y/n) [default y] y
Uploading src/assets/locales/el.translation.json... done!
Check upload ID: 79199169613ae8ab80ec886801309d52, filename: el.translation.json for information about processing results.
Uploading src/assets/locales/en.translation.json... done!
Check upload ID: afdc9f81683ed209538f5461180779ff, filename: en.translation.json for information about processing results.
Project initialization completed!

Как только мы закончим с настройкой, мы можем получить последние локали в нашей локальной среде и проверить наши загруженные локали на панели Phrase Dashboard:

Мы также можем получить или синхронизировать удаленные переводы с нашим локальным проектом одной командой:

➜ ./phraseapp pull 
Downloaded en to src/assets/locales/en.translation.json
Downloaded de to src/assets/locales/de.translation.json
Downloaded el to src/assets/locales/el.translation.json

Теперь, если вы просмотрите содержимое файла, вы увидите загруженные переводы, например, en.translation.json будет содержать следующее:

{
  "general": {
    "back": "Back",
    "cancel": "Cancel",
    "confirm": "Are you sure?",
    "destroy": "Delete",
    "edit": "Edit",
    "new": "New",
    "test": "Test"
  },
  "hello": "Hello world",
  "layouts": {
    "application": {
      "about": "About",
      "account": "Account",
      "app_store": "App Store",
      "imprint": "Imprint",
      "logout": "Logout",
      "my_mails": "My Mails",
      "press": "Press",
      "preview": "Preview",
      "profile": "Profile",
      "sign_in": "Login",
      "sign_up": "Register"
    }
  },
  "message": "Welcome to PhraseApp i18next",
  "_languages": {
    "el": "Greek",
    "en": "English"
  }
}

Теперь процесс перевода контента для наших приложений Angular может стать более упорядоченным и гибким.

Последние мысли

Angular — зрелая платформа для разработки первоклассных одностраничных приложений. Что касается интернационализации и локализации, он имеет встроенный модуль и предлагает возможность интеграции пользовательского решения, такого как i18next. В этом руководстве объясняются шаги, необходимые для использования этого популярного плагина, и показано, как мы можем улучшить наш рабочий процесс, используя Phrase для управления нашими переводами.

Надеюсь, вам понравилась эта статья. Следите за новостями, чтобы узнать больше об этих удивительных технологиях.

Первоначально опубликовано в The Phrase Blog.