From 8c725de9a33a5119ef2617f9c9a98338049595d8 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Sat, 17 Apr 2021 19:08:24 +0200 Subject: [PATCH] feat: load translations with webpack --- docs/concepts/configuration.md | 29 ++++++++------- src/app/app.server.module.ts | 41 +-------------------- src/app/core/internationalization.module.ts | 30 +++++++++------ src/typings.d.ts | 4 ++ templates/webpack/webpack.custom.ts | 18 +++++++++ 5 files changed, 56 insertions(+), 66 deletions(-) diff --git a/docs/concepts/configuration.md b/docs/concepts/configuration.md index f33f703dd6f..6d14339ef43 100644 --- a/docs/concepts/configuration.md +++ b/docs/concepts/configuration.md @@ -219,21 +219,22 @@ To dynamically set the default locale, use the URL parameter `lang` when rewriti ## Extend Locales -To add other languages except English, German or French, you have to create a new json-mapping-file with all translations, e.g., _./src/assets/i18n/nl_NL.json_). -Add the locale in the file _./src/environment/environments.ts_. -Additionally, for Angular's built-in components, e.g., currency-pipe, you have to register locale data similar to `localeDe` and `localeFr` with `registerLocaleData(localeNl)` in _./src/app/core/configuration.module.ts._ +To add other languages except English, German or French: -```typescript -... -import localeNl from '@angular/common/locales/nl'; -... -export class ConfigurationModule { - constructor(@Inject(LOCALE_ID) lang: string, translateService: TranslateService) { - registerLocaleData(localeNl); - ... - } -} -``` +1. Create a new json-mapping-file with all translations, e.g., `src/assets/i18n/nl_NL.json`. + +2. Add the locale to the environments under `src/environments`, e.g. + + ```typescript + { lang: 'nl_NL', currency: 'EUR', value: 'nl', displayName: 'Dutch', displayLong: 'Dutch (Netherlands)' } + ``` + +3. Import a [global variant of the locale data](https://angular.io/guide/i18n#import-global-variants-of-the-locale-data) in the [`InternationalizationModule`](../../src/app/core/internationalization.module.ts), e.g. + ```typescript + case 'nl_NL': + import('@angular/common/locales/global/nl'); + break; + ``` # Further References diff --git a/src/app/app.server.module.ts b/src/app/app.server.module.ts index 7227416f3b5..c2a6a3d1eac 100644 --- a/src/app/app.server.module.ts +++ b/src/app/app.server.module.ts @@ -3,10 +3,6 @@ import { ErrorHandler, NgModule } from '@angular/core'; import { TransferState } from '@angular/platform-browser'; import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'; import { META_REDUCERS } from '@ngrx/store'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { existsSync, readFileSync } from 'fs'; -import { join } from 'path'; -import { Observable, Observer } from 'rxjs'; import { configurationMeta } from 'ish-core/configurations/configuration.meta'; import { COOKIE_CONSENT_VERSION, DISPLAY_VERSION } from 'ish-core/configurations/state-keys'; @@ -18,31 +14,6 @@ import { environment } from '../environments/environment'; import { AppComponent } from './app.component'; import { AppModule } from './app.module'; -class TranslateUniversalLoader implements TranslateLoader { - getTranslation(lang: string): Observable { - return new Observable((observer: Observer) => { - let rootPath = process.cwd(); - if (rootPath && rootPath.indexOf('browser') > 0) { - rootPath = process.cwd().split('browser')[0]; - } - const file = join(rootPath, 'dist', 'browser', 'assets', 'i18n', `${lang}.json`); - if (!existsSync(file)) { - const errString = `Localization file '${file}' not found!`; - console.error(errString); - observer.error(errString); - } else { - const content = JSON.parse(readFileSync(file, 'utf8')); - observer.next(content); - observer.complete(); - } - }); - } -} - -export function translateLoaderFactory() { - return new TranslateUniversalLoader(); -} - export class UniversalErrorHandler implements ErrorHandler { handleError(error: Error): void { if (error instanceof HttpErrorResponse) { @@ -54,17 +25,7 @@ export class UniversalErrorHandler implements ErrorHandler { } @NgModule({ - imports: [ - AppModule, - ServerModule, - ServerTransferStateModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useFactory: translateLoaderFactory, - }, - }), - ], + imports: [AppModule, ServerModule, ServerTransferStateModule], providers: [ { provide: HTTP_INTERCEPTORS, useClass: UniversalMockInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: UniversalLogInterceptor, multi: true }, diff --git a/src/app/core/internationalization.module.ts b/src/app/core/internationalization.module.ts index 6894a22ac13..a1bfc5c75f8 100644 --- a/src/app/core/internationalization.module.ts +++ b/src/app/core/internationalization.module.ts @@ -1,13 +1,23 @@ -import { registerLocaleData } from '@angular/common'; -import { HttpClient } from '@angular/common/http'; -import localeDe from '@angular/common/locales/de'; -import localeFr from '@angular/common/locales/fr'; import { Inject, LOCALE_ID, NgModule } from '@angular/core'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { Observable, from } from 'rxjs'; +import { tap } from 'rxjs/operators'; -export function translateFactory(http: HttpClient) { - return new TranslateHttpLoader(http, 'assets/i18n/', '.json'); +class WebpackTranslateLoader implements TranslateLoader { + getTranslation(lang: string): Observable { + return from(import(`../../assets/i18n/${lang}.json`)).pipe( + tap(() => { + switch (lang) { + case 'de_DE': + import('@angular/common/locales/global/de'); + break; + case 'fr_FR': + import('@angular/common/locales/global/fr'); + break; + } + }) + ); + } } @NgModule({ @@ -15,17 +25,13 @@ export function translateFactory(http: HttpClient) { TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useFactory: translateFactory, - deps: [HttpClient], + useClass: WebpackTranslateLoader, }, }), ], }) export class InternationalizationModule { constructor(@Inject(LOCALE_ID) lang: string, translateService: TranslateService) { - registerLocaleData(localeDe); - registerLocaleData(localeFr); - translateService.setDefaultLang(lang.replace(/\-/, '_')); } } diff --git a/src/typings.d.ts b/src/typings.d.ts index 9741e4164a7..92a390d96c1 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -1,6 +1,10 @@ /* SystemJS module definition */ declare var module: NodeModule; +declare module '@angular/common/locales/global/de'; + +declare module '@angular/common/locales/global/fr'; + interface NodeModule { id: string; } diff --git a/templates/webpack/webpack.custom.ts b/templates/webpack/webpack.custom.ts index 9cb36437722..42e6abc5088 100644 --- a/templates/webpack/webpack.custom.ts +++ b/templates/webpack/webpack.custom.ts @@ -132,6 +132,24 @@ export default ( return 'tracking'; } + // move translation files into own bundles + const i18nMatch = /[\\/]assets[\\/]i18n[\\/](.*?)\.json/.exec(identifier); + const locale = i18nMatch && i18nMatch[1]; + + if (locale) { + return locale.replace('_', '-'); + } + + // move Angular locale data into bundle with translations + const localeDataMatch = /[\\/]@angular[\\/]common[\\/]locales[\\/]global[\\/](.*?)\.js/.exec(identifier); + let localeData = localeDataMatch && localeDataMatch[1]; + if (localeData) { + if (!localeData.includes('-')) { + localeData += '-' + localeData.toUpperCase(); + } + return localeData; + } + const match = /[\\/](extensions|projects)[\\/](.*?)[\\/](src[\\/]app[\\/])?(.*)/.exec(identifier); const feature = match && match[2];