Pull to refresh

Интернационализация (i18n) в Angular 2

Reading time 5 min
Views 33K
Для создания мультиязычных интерфейсов Ангулар предлагает использовать механизм разметки HTML шаблонов специальным маркером i18n который после компиляции удаляется из финального кода. Для этого достаточно указать этот маркер как атрибут тега окружающего текст.

<h1 i18n>Hello baby!</h1>

Для маркера можно указывать дополнительные параметры которые отображаются в специализированных редакторах использующихся для перевода и дополняют переводимый текст служебной информацией призванной помочь переводчику. Это параметры передаются в формате «Значение|Описание» или только «Описание».

<h1 i18n="An introduction header for this sample">Hello baby!</h1>
<h1 i18n="User welcome|An introduction header for this sample">Hello baby!</h1>

Возможно указать маркер и для текста который не окружен тегом. Для этого используется либо специальный пустой тег который не рендерится в финальный код.

<ng-container i18n>I don't output any element</ng-container>

или специальный комментарий

<!--i18n: optional meaning|optional description -->
I don't output any element either<!--/i18n-->

Возможно так же делать перевод для аттрибутов тегов. Таких как alt, title,…

<img [src]="logo" i18n-title title="Angular logo" />

Plural


Окончание слов для множественного числа основано на правилах http://unisource.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules и http://cldr.unisource.org/index/cldr-spec/plural-rules в формате:

<span i18n>{wolves, plural, =0 {no wolves} =1 {one wolf} =2 {two wolves} other {a wolf pack}}</span>

wolves — название переменной содержащей число
plural — название типа преобразования
=0 — возможное значение переменной, {no wolves} — отображаемый текст для данного значения
=1 — возможное значение переменной, {one wolf} — отображаемый текст для данного значения
=2 — возможное значение переменной, {two wolves} — отображаемый текст для данного значения
other — если значение переменной не задано, {a wolf pack} — отображаемый текст для данного значения

Возможные следующие варианты:

  • =0
  • =1
  • =2
  • few (...3-10 или 5-10, зависит от правил локализации)
  • other (дефолтное значение для всех неуказанных вариантов)

Select


Дает возможность выбирать значение на базе литеральной переменной:

<span i18n>The hero is {gender, select, m {male} f {female}}</span>

gender — название переменной содержащей значение
select — название типа преобразования
m — возможное значение переменной, {male} — отображаемый текст для данного значения
f — возможное значение переменной, {female} — отображаемый текст для данного значения

ng-xi18n


Для извлечения строк из аппликации используется специальная утилита ng-xi18n входящая в состав angular-cli:

./node_modules/.bin/ng-xi18n -p src/tsconfig.json

Данная утилита ищет маркер i18n в шаблонах и копирует соответствующий текст формируя из него XML файл XLIFF формата (версии 1.2).

Так же поддерживается формат XML Message Bundle (XMB). Для этого нужно задать соответствующий ключ:

./node_modules/.bin/ng-xi18n --i18nFormat=xmb -p src/tsconfig.json

Далее XML файл может быть отправлен переводчику который сможет редактировать его одним из специализированных редакторов. Полученный в итоге файл должен быть сохранен в папке i18n и содержать в своем названии язык локализации. Например в случае испанского языка, из исходного messages.xlf получится messages.es.xlf. Для каждого языка интерфейса должен быть создан отдельный файл.

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

// Указать нужные зависимости
import {TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID} from '@angular/core';

// Задать ссылку на XML файл содержащий перевод
let current_language = 'ru';
import {RU_TRANS} from './i18n/messages.' + current_language;

Тут есть одно замечание. Если мы используем angular-cli, то в настоящее время мы не можем самостоятельно (официально) менять конфигурацию webpack и соответственно не можем добавить правило raw для файлов с расширением xlf позволяющее импортировать их напрямую. Поэтому пока приходится вставлять их в файл обертку (messages.ru.ts):

export const RU_TRANS = ` //Обратные кавычки в файле позволяют многострочный контент.
// сюда нужно скопировать содержимое XML файла messages.ru.xlf
`;

Конфигурируем в main.ts

platformBrowserDynamic().bootstrapModule(AppModule, {
  providers: [
    {provide: TRANSLATIONS, useValue: RU_TRANS},
    {provide: TRANSLATIONS_FORMAT, useValue: 'xlf'},
    {provide: LOCALE_ID, useValue: current_language}
  ]
});


К сожалению описанная выше интернализация имеет ряд подводных камней и ограничений из-за которых она может быть использована только в проектах с самым простым интерфейсом:

  • при смене содержимого тега даже не относящегося к переводимому тексту при последующей экстракции может поменяться ID, что в свою очередь приводит к ошибкам компиляции приложения.
  • экстракция происходит только из HTML файлов и невозможна для динамического контента (например каких либо списков или дропбоксов)



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

Работа по решению данных вопросов происходит и возможно в версиях Ангулар 4 или 5 интернационализация будет доведена до ума.

Поэтому пока единственным рабочим решением, по прежнему, остается ng2-translate.

ng2-translate


ngx-translate.com

Является непрямым наследником хорошо известного модуля angular-translate.
Так же использует translate фильтр. Кроме того тексты с переводом хранятся в файлах JSON в том же формате как и в angular-translate.

Все это позволяет очень просто перенести шаблоны из ng1 в ng2. По сути все что требуется это сделать следующее:

Добавить зависимость в модуль аппликации в файл src/app/app.module.ts

import { HttpModule, Http } from '@angular/http';
import { TranslateModule, TranslateLoader, TranslateStaticLoader } from 'ng2-translate/ng2-translate';

@NgModule({
...
  imports: [
    ...
    HttpModule,
    TranslateModule.forRoot({
      provide: TranslateLoader,
      // можно указать свой путь к папке i18n где находятся файлы с переводом
      useFactory: (http: Http) => new TranslateStaticLoader(http, '/assets/i18n', '.json'),
      deps: [Http]
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})

Вышеприведенный пример загрузки зависимости актуален для 5-ой версии ng2-translate.

Модуль сам подключает JSON файлы находящиеся в папке i18n. Поэтому импортировать вручную их не требуется.

Так же необходимо в корневом компоненте указать используемый язык:

import {TranslateService} from 'ng2-translate';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';

  constructor(translate: TranslateService) {
    // this language will be used as a fallback when a translation isn't found in the current language
    translate.setDefaultLang('ru');

    // the lang to use, if the lang isn't available, it will use the current loader to get them
    translate.use('ru');
  }
}

Собственно это все что необходимо. Модуль готов к работе.

Формат JSON файла очень простой (ключ: перевод):

{
  "HELLO": "Привет"
}

далее в HTML можно соответственно использовать translate в следующих формах:

<div>{{'HELLO' | translate}}</div>
<div [translate]="'HELLO'" [translateParams]="{value: 'world'}"></div>
<div translate [translateParams]="{value: 'world'}">HELLO</div>

Так же можно использовать TranslateService в компонентах.

Update (4.3.2017):

Буквально накануне вышла 6-я версия, в которой ng2-translate был переименован в ngx-translate для соответствия официальной конвенции.

Кроме этого принципиальных изменений не произошло. Единственное TranslateStaticLoader был вынесен в самостоятельный модуль и переименован в TranslateHttpLoader. Соответственно синтаксис загрузки модуля в app.module.ts стал следующим:

import { HttpModule, Http } from '@angular/http';
import {TranslateModule, TranslateLoader} from "@ngx-translate/core"; //изменилось
import {TranslateHttpLoader} from "@ngx-translate/http-loader"; //изменилось

@NgModule({
...
  imports: [
    ...
    HttpModule,
    TranslateModule.forRoot({
      provide: TranslateLoader,
      // можно указать свой путь к папке i18n где находятся файлы с переводом
      useFactory: (http: Http) => new TranslateHttpLoader(http, "i18n/", ".json"), //изменилось
      deps: [Http]
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
Tags:
Hubs:
+24
Comments 10
Comments Comments 10

Articles