Практическая пошаговая инструкция как прикрутить динамическую локализацию (возможность выбора языков) в веб приложении на Angular 4+ используя @ngx-translate/core.

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

Почему именно ngx-translate

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

Что получим на выходе

  • Ангуляр приложение, в папке assets/locale расположены *.json файлы с ключами и их локализованными значениями, по одному файлу для каждого поддерживаемого языка в веб приложении.
  • Динамическая смена языка
  • Хранение ключей в объектной структуре, то есть иметь что-то такое:
// en.json
{
    Common: {
        Yes: "Yes"
    }
}

Это позволит группировать ключи / значения и как-то организовать json файл с локализацией. Для больших проектов весьма полезно.

  • Минимальную автоматизацию и проверку на дурака. Что если забыли добавить ключ в json?
  • Применение локализации как в html шаблонах, так и в ts коде

Настраиваем

Создаем или берем готовый ангуляр проект. Версия ангуляра 4+, но лучше конечно максимально свежую.

Устанавливаем нужные библиотеки:

npm install @ngx-translate/core --save
npm install @ngx-translate/http-loader --save
@biesbjerg/ngx-translate-extract --saveDev

Библиотека @ngx-translate/http-loader нужна для того, чтобы мы могли считывать данные о локализации из *.json файлов. Без нее жить можно, но тогда придется полностью самостоятельно писать хранение и загрузку данных локализации. Данный подход подойдет тем, у кого данные локализации (или попросту все переведенные тексты) уже где-то хранятся.

Библиотека @biesbjerg/ngx-translate-extract не обязательна, но довольно полезна. Она позволяет собирать по коду приложения забытые локализационные ключи и обновлять файлы локализаций. С ее помощью очень удобно обрабатывать такой сценарий: добавляем новый язык и просто запускаем команду из этой библиотеки. В результате пустой файл для нового языка наполнится всей структурой ключей и каких-то значений по умолчанию. Пример будет ниже.

  1. Импортируем библиотеку в главном модуле:
@NgModule({
  imports: [
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient],
      },
      useDefaultLang: false,
    })
  ],
})
export class AppModule { }

В настройках библиотеки присутствует HttpLoaderFactory, ее можно описать прямо в этом же файле, она скромных размеров.

export function HttpLoaderFactory(http: HttpClient): TranslateLoader {
  return new TranslateHttpLoader(http, './assets/locale/', '.json');
}

Именно тут мы указываем путь к файлам локализации. Если они хранятся как-то отдельно, или вообще различно в разных енвайронментах - поменяйте данный код на использование environment.ts например.

  1. В принципе этого достаточно для основных настроек, но мы сделаем еще один дополнительный шаг - обработку ошибок, когда не найден ключ.

Для этого нужно в настройку (сразу после указания loader) добавить еще одно поле:

missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationService },

И конечно же необходимо создать в отдельном файле саму реализацию этого обработчика:

export class MissingTranslationService implements MissingTranslationHandler {
  handle(params: MissingTranslationHandlerParams) {
    return `WARN: '${params.key}' is missing in '${params.translateService.currentLang}' locale`;
  }
}
  1. Добавляем хранение доступных языков. В простом случае удобно положить их в environment.ts
locales: ['en', 'ru'],
defaultLocale: 'en',
  1. Чтобы сервис начал работать, его необходимо инициализировать в файле AppComponent,  при загрузке приложения:
@Component({...})
export class AppComponent implements OnInit {
  constructor(private translateService: TranslateService) {}
  ngOnInit(): void {
    this.translateService.use(environment.defaultLocale);
  }
}
  1. Добавляем файлы en.json и ru.json (в соответствии с тем, что указано в списке доступных языков). Добавьте какое-то базовое поле, чтобы это был валидный json.

Когда эти шаги сделаны, сервис начнет работать.

Используем сервис

HTML разметка

Тут все просто. Библиотека предлагает pipe translate, мы просто применяем его в разметке для определенных ключей.

<label>{{ 'LANGUAGES.TITLE' | translate }}</label>

Заметьте, я использую тут вложенные свойства, ту структуру json, о которой говорил в начале.

Так же поддерживаются параметры, посмотрите про них пожалуйста в документации @ngx-translate/core. С помощью параметров можно реализовывать что-то вроде интерполирования строк.

В коде компонентов

this.translateService.get(['KEY1', 'KEY2']))
      .subscribe(translations => {
        console.log(translations['KEY1'])
        console.log(translations['KEY2'])
      });

Это надежный способ, если вы нормально относитесь к Observable и RxJs.
Если же нет - есть такой способ:

this.translateService.instant('Key')

Данный способ работает хорошо, но на этапе инициализации приложения (например ngOnInit AppComponent) данные для него могут еще быть не загружены. Будьте аккуратны.

Поддержка файлов .json

В конце хочу показать, как использовать ngx-translate-extract утилиту. Просто выполняете в консоли команду ngx-translate-extract -i ./src -o src/assets/locale/*.json --sort --format namespaced-json. Данная команда запустит анализ файлов приложения, разметки и кода тайпскрипт. Все найденные в разметке и прочем коде ключи попадут в .json для всех языков, имеющиеся ключи и их значения останутся нетронутыми, разве что порядок может измениться.

Для удобства я дописываю это как скрипт в package.json

"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "update-locale": "ngx-translate-extract -i ./src -o src/assets/locale/*.json --sort --format namespaced-json"
  },

Все, что я описал, можно найти собранным вместе в открытом репозитории: valentinkononov/ngx-translate-angular

Надеюсь материал будет полезен! Пишите код с удовольствием, выбирайте удобные библиотеки и делайте полезные проекты!

Опубликовано на хабре.