Для создания мультиязычных интерфейсов Ангулар предлагает использовать механизм разметки 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]
})
Поделиться с друзьями
-->

Комментарии (7)


  1. lega
    04.03.2017 02:56
    -1

    В Angular Light можно изящней сделать, вместо

    <div>{{'HELLO' | translate}}</div>
    
    например так:
    <div>{{^ HELLO}}</div>
    
    (текст без кавычек и «директива» одним символом), пример jsfiddle, при этом нагрузка на dirty-checking не идет в отличие от ангуляра (как минимум первого).


  1. kemsky
    04.03.2017 14:21

    Ситуация с встроенной локализацией остается печальной с момента релиза (полгода уже?), я бы пока никому не советовал с ней связываться.
    Кроме того, идея компилировать приложение для каждой локализации лично мне кажется слишком ограниченной. До сих пор нет возможности переводить из кода, можно только в шаблоне. Поддержка в angular-cli также хромает.


  1. Radli007
    04.03.2017 14:35

    1-ый способ мне не понравился, т.к. там xml (размер файла, структура файла предполагает лишнюю информацию), пробывать не стал

    ng2-translate способ не понравился тем, что там мешает кеширование браузера. При обновлении словаря, файлы из папки i18n в формате json кешируются и у клиента моут обновиться только спустя какое то время. ( возможно можно настроить, что бы в продакшене так же приписывать к названию файлов guid)

    использую другой translate чуть модифицированный (что бы можно было использовать в shared module)
    https://github.com/AtnagulovID/Angular-2-CLI-Learn-project/tree/master/src/app/_translate (откуда взял не помню, но кода меньше чем в ng2-translate это во первых и в файлы переводов тоже идут как часть приложения в продакшене на angular cli, так что нет проблем с кешированием браузеров)
    atnagulovid.myjino.ru — тут можно посмотреть как работает (приложение само ниочем, создается во время изучени я angular 2 )


  1. rostman_rk
    04.03.2017 17:53

    А как быть если словарь храниться в базе данных? Использовать еще 1 сервис для перевода из базы в файл?
    Сам некогда искал способ, но ничего в то время не нашел. В итоге пришлось грузить вюхи из сервера (написаном на .Net), что несет за собой дополнительные запросы, но кэш работает на ура + при смене языка надо перегружать страницу, а сам язык ходил вместе с куки (так сервер знал что вернуть). И еще 1 минус такого подхода, требуется настройка для бандла, а использовал я SystemJs + Gulp


  1. navix
    05.03.2017 10:25

    Для xlf-формата есть много софта, который помогает мержить и обновлять файлы. Я использовал Virtaal и это было удобно, программа достаточно успешно вылавливала измененные фразы и показывала предыдущий вариант перевода.

    Но у нативного i18n есть большие проблеми с plural, фраза заменяется на пустой тег, т.е нужно задавать дополнительный контекст или выдумывать что-то еще.

    Если нужно получить фразу в коде, то можно использовать вот такой хак: забирать фразу из шаблона во время выполнения кода.

    Сторонние либы неплохо справляются с задачами, ну и если нужен какой-то специфический функционал, то всегда можно реализовать его самому. При этом у меня есть уверенность, что нативная система будет доработана и ее использование не будет вызывать столько проблем.


  1. elepner
    05.03.2017 12:46

    Пробовал завести Angular2 i18n, все сыпалось с ужасными ошибками. Видимо, он еще не очень дружит с Angular Universal. Пришлось использовать старый, добрый, знакомый с первого Angular'a ng-translate. Кстати, не знаю у кого как, но вечные замечания от мереджеров/UX типа: «тут надо запятую поставить», «тут с большой буквы», «не хватает перевода, пожалуйста добавь» у меня вызывают нервный тик.

    Поэтому я заэкстендил [translate] директиву, чтобы она добавляла маленькую кнопку к тексу, чтобы можно было перевести все и записать изменения прямо на сервер. Единственный недостаток, что при редеплое все изменения могут быть потеряны, но я всем объяснил, чтоб сохраняли *.json c переводами и отсылали его по почте.

    Кстати, кому-нибудь интересен данный подход?

    PS: естественно этот код даже не подключается в продакшне.


  1. diomas
    06.03.2017 17:16

    0, 1, 2, few, other и всё?


    Куда-то затерялась еще одна категория: many. В арабском, например 5-ю категориями не обойтись.