Parcourir la source

feat(admin-ui): Add infrastructure for i18n support

Set up ngx-translate with a parser for ICU MessageFormat. Still need to actually extract messages into the translation file.
Michael Bromley il y a 7 ans
Parent
commit
c71df1a42b

+ 3 - 0
admin-ui/package.json

@@ -25,6 +25,7 @@
     "@clr/angular": "^0.11.21",
     "@clr/icons": "^0.11.21",
     "@clr/ui": "^0.11.21",
+    "@ngx-translate/core": "^10.0.2",
     "@webcomponents/custom-elements": "1.0.0",
     "apollo-angular": "^1.1.1",
     "apollo-angular-cache-ngrx": "^1.0.0-beta.0",
@@ -36,7 +37,9 @@
     "core-js": "^2.5.4",
     "graphql": "^0.13.2",
     "graphql-tag": "^2.9.2",
+    "messageformat": "^2.0.2",
     "ngx-pagination": "^3.1.1",
+    "ngx-translate-messageformat-compiler": "^4.1.3",
     "rxjs": "^6.0.0",
     "rxjs-compat": "^6.2.1",
     "zone.js": "^0.8.26"

+ 7 - 6
admin-ui/src/app/app.module.ts

@@ -1,15 +1,12 @@
-import { HttpClientModule } from '@angular/common/http';
 import { NgModule } from '@angular/core';
 import { BrowserModule } from '@angular/platform-browser';
 import { RouterModule } from '@angular/router';
-import { ClarityModule } from '@clr/angular';
-import { Apollo, ApolloModule } from 'apollo-angular';
-import { HttpLink, HttpLinkModule } from 'apollo-angular-link-http';
-import { InMemoryCache } from 'apollo-cache-inmemory';
-
+import { TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { TranslateMessageFormatDebugCompiler } from 'ngx-translate-messageformat-compiler';
 import { AppComponent } from './app.component';
 import { routes } from './app.routes';
 import { CoreModule } from './core/core.module';
+import { CustomLoader } from './core/providers/i18n/custom-loader';
 
 @NgModule({
     declarations: [
@@ -18,6 +15,10 @@ import { CoreModule } from './core/core.module';
     imports: [
         BrowserModule,
         RouterModule.forRoot(routes, { useHash: false }),
+        TranslateModule.forRoot({
+            loader: { provide: TranslateLoader, useClass: CustomLoader },
+            compiler: { provide: TranslateCompiler, useClass: TranslateMessageFormatDebugCompiler },
+        }),
         CoreModule,
     ],
     providers: [],

+ 43 - 0
admin-ui/src/app/core/providers/i18n/custom-loader.ts

@@ -0,0 +1,43 @@
+import { TranslateLoader } from '@ngx-translate/core';
+import { Observable, of as observableOf } from 'rxjs';
+
+declare function require(path: string): any;
+
+const translations = [
+    'common',
+    // 'error',
+    // 'notification',
+].reduce((hash, name) => {
+    hash[name] = require(`../../../../i18n-messages/${name}.messages.ts`).default;
+    return hash;
+}, {} as { [name: string]: any; });
+
+/**
+ * A custom language loader which splits apart a translations object in the format:
+ * {
+ *   SECTION: {
+ *     TOKEN: {
+ *       lang1: "...",
+ *       lang2: "....
+ *     }
+ *   }
+ * }
+ */
+export class CustomLoader implements TranslateLoader {
+
+    getTranslation(lang: string): Observable<any> {
+        const output: any = {};
+        for (const section in translations) {
+            if (translations.hasOwnProperty(section)) {
+                output[section] = {};
+
+                for (const token in translations[section]) {
+                    if (translations[section].hasOwnProperty(token)) {
+                        output[section][token] = translations[section][token][lang];
+                    }
+                }
+            }
+        }
+        return observableOf(output);
+    }
+}

+ 47 - 0
admin-ui/src/app/core/providers/i18n/i18n.service.ts

@@ -0,0 +1,47 @@
+import { Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+
+export type UILanguage = 'en' | 'de';
+
+@Injectable()
+export class I18nService {
+    constructor(private ngxTranslate: TranslateService) {
+        // ngxTranslate.setDefaultLang(config.FALLBACK_LANGUAGE);
+    }
+
+    /**
+     * Set the UI language
+     */
+    setLanguage(language: UILanguage): void {
+        this.ngxTranslate.use(language);
+    }
+
+    /**
+     * Translate the given key.
+     */
+    translate(key: string | string[], params?: any): string {
+        return this.ngxTranslate.instant(key, params);
+    }
+
+    /**
+     * Attempt to infer the user language from the browser's navigator object. If the result is not
+     * amongst the valid UI languages, default to the fallback language instead.
+     */
+    /*inferUserLanguage(): UILanguage {
+        const browserLanguage = navigator.language.split('-')[0];
+        if (this.config.UI_LANGUAGES.indexOf(browserLanguage) >= 0) {
+            return browserLanguage as any;
+        }
+
+        if ((navigator as any).languages) {
+            const languages: string[] = (navigator as any).languages;
+            for (const lang of languages.map(l => l.split('-')[0])) {
+                if (this.config.UI_LANGUAGES.indexOf(lang) >= 0) {
+                    return lang as any;
+                }
+            }
+        }
+
+        return this.config.FALLBACK_LANGUAGE as any;
+    }*/
+}

+ 2 - 0
admin-ui/src/app/shared/shared.module.ts

@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 import { RouterModule } from '@angular/router';
 import { ClarityModule } from '@clr/angular';
+import { TranslateModule } from '@ngx-translate/core';
 import { NgxPaginationModule } from 'ngx-pagination';
 import { DataTableColumnComponent } from './components/data-table/data-table-column.component';
 import { DataTableComponent } from './components/data-table/data-table.component';
@@ -18,6 +19,7 @@ const IMPORTS = [
     ReactiveFormsModule,
     RouterModule,
     NgxPaginationModule,
+    TranslateModule,
 ];
 
 const DECLARATIONS = [

+ 6 - 0
admin-ui/src/i18n-messages/common.messages.ts

@@ -0,0 +1,6 @@
+export default {
+  THINGS: {
+    en: `There {count, plural, =0{is} one{is} other{are} } {count, plural, =0{nothing} one{one thing} other{# things} }`,
+    de: `Es gibt {count, plural, =0{keine Dinge} one{ein Ding} other{# Dinge} }`,
+  },
+};

+ 34 - 0
admin-ui/yarn.lock

@@ -231,6 +231,12 @@
     tree-kill "^1.0.0"
     webpack-sources "^1.1.0"
 
+"@ngx-translate/core@^10.0.2":
+  version "10.0.2"
+  resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-10.0.2.tgz#5eeb78f47845b476a1e892fb2fb153dbbaf72850"
+  dependencies:
+    tslib "^1.9.0"
+
 "@nodelib/fs.stat@^1.0.1":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a"
@@ -4738,6 +4744,12 @@ make-error@^1.1.1:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.4.tgz#19978ed575f9e9545d2ff8c13e33b5d18a67d535"
 
+make-plural@^4.1.1:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.2.0.tgz#03edfc34a2aee630a57e209369ef26ee3ca69590"
+  optionalDependencies:
+    minimist "^1.2.0"
+
 map-cache@^0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
@@ -4811,6 +4823,18 @@ merge2@^1.2.1:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.2.tgz#03212e3da8d86c4d8523cebd6318193414f94e34"
 
+messageformat-parser@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-3.0.0.tgz#38ae9348eb4834ab19b3a8d682af7babb335f37a"
+
+messageformat@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-2.0.2.tgz#adb15dad45cd3a3d4900a5611ef757ae26f2508e"
+  dependencies:
+    make-plural "^4.1.1"
+    messageformat-parser "^3.0.0"
+    reserved-words "^0.1.2"
+
 methods@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -5059,6 +5083,12 @@ ngx-pagination@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/ngx-pagination/-/ngx-pagination-3.1.1.tgz#fcde5cb5fd4a1bd6aa785ff062a55f3deefcd3ac"
 
+ngx-translate-messageformat-compiler@^4.1.3:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/ngx-translate-messageformat-compiler/-/ngx-translate-messageformat-compiler-4.1.3.tgz#ac697d3f88eb8e05bdbf8fcd81af568035e8186a"
+  dependencies:
+    tslib "^1.9.0"
+
 nice-try@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
@@ -6251,6 +6281,10 @@ requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
 
+reserved-words@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
+
 resolve-cwd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"