From 7a528144fd02b9af2197815ca931f298452c0e29 Mon Sep 17 00:00:00 2001
From: Manuel Kocher <manuel.kocher@tugraz.at>
Date: Thu, 12 May 2022 13:17:40 +0200
Subject: [PATCH] Add dbp-translation component and include it in showcase

---
 packages/app-shell/src/app-shell.js           |  4 +-
 packages/common/components.js                 |  2 +
 packages/common/dbp-common-demo.js            |  7 ++
 packages/common/index.js                      |  3 +-
 packages/common/jsonld.js                     |  2 +-
 packages/common/src/i18n.js                   | 23 +++++++
 packages/common/src/i18n/de/translation.json  |  3 +-
 packages/common/src/i18n/en/translation.json  |  3 +-
 packages/common/src/translation.js            | 67 +++++++++++++++++++
 toolkit-showcase/assets/common.metadata.json  |  2 +-
 .../assets/dbp-toolkit-showcase.html.ejs      |  3 +-
 .../src/dbp-common-demo-activity.js           |  3 +
 toolkit-showcase/src/i18n/de/translation.json |  3 +
 toolkit-showcase/src/i18n/en/translation.json | 12 ++++
 14 files changed, 130 insertions(+), 7 deletions(-)
 create mode 100644 packages/common/src/translation.js
 create mode 100644 toolkit-showcase/src/i18n/de/translation.json
 create mode 100644 toolkit-showcase/src/i18n/en/translation.json

diff --git a/packages/app-shell/src/app-shell.js b/packages/app-shell/src/app-shell.js
index 95ea1045..53397c4e 100644
--- a/packages/app-shell/src/app-shell.js
+++ b/packages/app-shell/src/app-shell.js
@@ -74,6 +74,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
         this.initateOpenMenu = false;
 
         this.auth = {};
+        this.langFile = '';
     }
 
     static get scopedElements() {
@@ -271,6 +272,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
             buildTime: {type: String, attribute: 'build-time'},
             env: {type: String},
             auth: {type: Object},
+            langFile: {type: String, attribute: 'lang-file'},
         };
     }
 
@@ -701,7 +703,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) {
                 background-position:center center;
                 margin: 0 0.5% 0 1.5%;
                 font-size:94%;
-            } 
+            }
             */
 
             .menu a {
diff --git a/packages/common/components.js b/packages/common/components.js
index 6a42c6af..ac6917b0 100644
--- a/packages/common/components.js
+++ b/packages/common/components.js
@@ -7,6 +7,7 @@ import {
     MiniSpinner,
     Spinner,
     Translated,
+    Translation
 } from './index';
 
 commonUtils.defineCustomElement('dbp-mini-spinner', MiniSpinner);
@@ -16,3 +17,4 @@ commonUtils.defineCustomElement('dbp-button', Button);
 commonUtils.defineCustomElement('dbp-loading-button', LoadingButton);
 commonUtils.defineCustomElement('dbp-inline-notification', InlineNotification);
 commonUtils.defineCustomElement('dbp-translated', Translated);
+commonUtils.defineCustomElement('dbp-translation', Translation);
diff --git a/packages/common/dbp-common-demo.js b/packages/common/dbp-common-demo.js
index fad1b686..5f5b9bc0 100644
--- a/packages/common/dbp-common-demo.js
+++ b/packages/common/dbp-common-demo.js
@@ -12,6 +12,7 @@ import {
     Spinner,
     InlineNotification,
     Translated,
+    Translation,
 } from './index.js';
 
 export class DbpCommonDemo extends ScopedElementsMixin(LitElement) {
@@ -20,6 +21,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) {
         this._i18n = createInstance();
         this.lang = this._i18n.language;
         this.noAuth = false;
+        this.langFile = '';
     }
 
     static get scopedElements() {
@@ -31,6 +33,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) {
             'dbp-loading-button': LoadingButton,
             'dbp-inline-notification': InlineNotification,
             'dbp-translated': Translated,
+            'dbp-translation': Translation
         };
 
         if (customElements.get('dbp-auth')) {
@@ -44,6 +47,7 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) {
         return {
             lang: {type: String},
             noAuth: {type: Boolean, attribute: 'no-auth'},
+            langFile: {type: String, attribute: 'lang-file'},
         };
     }
 
@@ -297,6 +301,9 @@ html {
                             </div>
                         </dbp-translated>
                     </div>
+                    <div class="control" id="dbp-translation-demo">
+                        <dbp-translation key="toolkit-showcase" subscribe="lang, lang-file"></dbp-translation>
+                    </div>
                 </div>
             </section>
         `;
diff --git a/packages/common/index.js b/packages/common/index.js
index 0009a2f0..3d7467fc 100644
--- a/packages/common/index.js
+++ b/packages/common/index.js
@@ -6,6 +6,7 @@ import {Button, LoadingButton} from './src/button.js';
 import {Spinner} from './src/spinner.js';
 import {InlineNotification} from './src/inline-notification.js';
 import {Translated} from './src/translated';
+import {Translation} from './src/translation';
 import {AdapterLitElement} from './src/adapter-lit-element.js';
 
 export {EventBus, createLinkedAbortController, createTimeoutAbortSignal};
@@ -14,7 +15,7 @@ export {MiniSpinner};
 export {Button, LoadingButton};
 export {Spinner};
 export {InlineNotification};
-export {Translated};
+export {Translated, Translation};
 export * from './src/logger.js';
 export * from './src/utils.js';
 export {AdapterLitElement};
diff --git a/packages/common/jsonld.js b/packages/common/jsonld.js
index 021a62db..525e432b 100644
--- a/packages/common/jsonld.js
+++ b/packages/common/jsonld.js
@@ -297,7 +297,7 @@ export default class JSONLD {
     }
 }
 
-JSONLD._i18n = createInstance();
+JSONLD._i18n = await createInstance();
 JSONLD.instances = {};
 JSONLD.successFunctions = {};
 JSONLD.failureFunctions = {};
diff --git a/packages/common/src/i18n.js b/packages/common/src/i18n.js
index 6623ebdc..0374a825 100644
--- a/packages/common/src/i18n.js
+++ b/packages/common/src/i18n.js
@@ -6,3 +6,26 @@ import en from './i18n/en/translation.json';
 export function createInstance() {
     return _createInstance({en: en, de: de}, 'de', 'en');
 }
+
+export async function createInstanceAsync(langFile) {
+    // check if a path to language files is given
+    if(langFile) {
+      // request german lang file asynchronously
+      let result = await
+          fetch(langFile + 'de/translation.json', {
+              headers: {'Content-Type': 'application/json'},
+          });
+      const dynDe = await result.json();
+
+      // request english lang file asynchronously
+      result = await
+          fetch(langFile + 'en/translation.json', {
+              headers: {'Content-Type': 'application/json'},
+          });
+      const dynEn = await result.json();
+
+      return _createInstance({en: dynEn, de: dynDe}, 'de', 'en');
+    }
+
+    return _createInstance({en: en, de: de}, 'de', 'en');
+}
diff --git a/packages/common/src/i18n/de/translation.json b/packages/common/src/i18n/de/translation.json
index 51cf793c..b9daed00 100644
--- a/packages/common/src/i18n/de/translation.json
+++ b/packages/common/src/i18n/de/translation.json
@@ -7,5 +7,6 @@
         "api-documentation-server": "Verbindung zum apiDocumentation API Server {{apiDocUrl}} fehlgeschlagen!",
         "error-api-server": "Verbindung zum API Server {{apiUrl}} fehlgeschlagen!",
         "error-hydra-documentation-url-not-set": "Hydra apiDocumentation URL wurden für server {{apiUrl}} nicht gesetzt!"
-    }
+    },
+    "toolkit-showcase": "Dieser Text wird mithilfe von i18n Englisch wenn man die Sprache auf Englisch stellt."
 }
diff --git a/packages/common/src/i18n/en/translation.json b/packages/common/src/i18n/en/translation.json
index e1036a65..1e17839a 100644
--- a/packages/common/src/i18n/en/translation.json
+++ b/packages/common/src/i18n/en/translation.json
@@ -7,5 +7,6 @@
         "api-documentation-server": "Connection to apiDocumentation server {{apiDocUrl}} failed!",
         "error-api-server": "Connection to api server {{apiUrl}} failed!",
         "error-hydra-documentation-url-not-set": "Hydra apiDocumentation url was not set for server {{apiUrl}}!"
-    }
+    },
+    "toolkit-showcase": "This text will be translated to german using i18n when the user changes the language to german."
 }
diff --git a/packages/common/src/translation.js b/packages/common/src/translation.js
new file mode 100644
index 00000000..782dd3e2
--- /dev/null
+++ b/packages/common/src/translation.js
@@ -0,0 +1,67 @@
+import {css, html} from 'lit';
+import {classMap} from 'lit/directives/class-map.js';
+import {until} from 'lit/directives/until.js';
+import DBPLitElement from '../dbp-lit-element';
+import {createInstanceAsync} from './i18n.js';
+
+export class Translation extends DBPLitElement {
+    constructor() {
+        super();
+        this.key = '';
+        this.lang = '';
+        this.langFile = '';
+    }
+
+    static get properties() {
+        return {
+            ...super.properties,
+            key: {type: String},
+            lang: {type: String},
+            langFile: {type: String, attribute: 'lang-file'},
+        };
+    }
+
+    static get styles() {
+        // language=css
+        return css`
+            .hidden {
+                display: none;
+            }
+        `;
+    }
+
+    connectedCallback() {
+      super.connectedCallback();
+      this._i18n = createInstanceAsync(this.langFile);
+    }
+
+    update(changedProperties) {
+        changedProperties.forEach((oldValue, propName) => {
+            switch (propName) {
+                case 'lang':
+                    let lang = this.lang;
+                    this._i18n.then(function(response) {
+                      response.changeLanguage(lang);
+                    });
+                    break;
+            }
+        });
+
+        super.update(changedProperties);
+    }
+
+    render() {
+        // save global key in local variable for async use
+        let key = this.key;
+
+        // async request to i18n translation
+        const translation = this._i18n.then(function(response){
+          return response.t(key);
+        });
+
+        // load translation text when available, otherweise display "Loading.."
+        return html`
+            ${until(translation, html`<span>Loading..</span>`)}
+        `;
+    }
+}
diff --git a/toolkit-showcase/assets/common.metadata.json b/toolkit-showcase/assets/common.metadata.json
index de1f70c7..51e4892c 100644
--- a/toolkit-showcase/assets/common.metadata.json
+++ b/toolkit-showcase/assets/common.metadata.json
@@ -14,5 +14,5 @@
         "de": "Gemeinsame Web Components",
         "en": "Common web components"
     },
-    "subscribe": "lang,entry-point-url"
+    "subscribe": "lang,entry-point-url,lang-file"
 }
diff --git a/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs b/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs
index ef38009f..1cf3784a 100644
--- a/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs
+++ b/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs
@@ -11,7 +11,7 @@
 
     <!-- PWA manifest file -->
     <link rel="manifest" href="<%= getUrl(name + '.manifest.json') %>">
-    
+
     <!-- PWA iphone -->
     <link rel="apple-touch-icon" sizes="180x180" href="<%= getPrivateUrl('apple-touch-icon.png') %>">
     <link rel="icon" type="image/png" sizes="32x32" href="<%= getPrivateUrl('icon-32x32.png') %>">
@@ -113,6 +113,7 @@
 <<%= name %>
     provider-root
     lang="de"
+    lang-file="<%= getUrl('/src/i18n/') %>"
     entry-point-url="<%= entryPointURL %>"
     nextcloud-auth-url="<%= nextcloudWebAppPasswordURL %>"
     nextcloud-web-dav-url="<%= nextcloudWebDavURL %>"
diff --git a/toolkit-showcase/src/dbp-common-demo-activity.js b/toolkit-showcase/src/dbp-common-demo-activity.js
index fc633b9d..67358455 100644
--- a/toolkit-showcase/src/dbp-common-demo-activity.js
+++ b/toolkit-showcase/src/dbp-common-demo-activity.js
@@ -13,6 +13,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) {
         super();
         this.lang = 'en';
         this.entryPointUrl = '';
+        this.langFile = '';
     }
 
     static get scopedElements() {
@@ -25,6 +26,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) {
         return {
             ...super.properties,
             lang: {type: String},
+            langFile: {type: String, attribute: 'lang-file'},
             entryPointUrl: {type: String, attribute: 'entry-point-url'},
         };
     }
@@ -63,6 +65,7 @@ class DbpCommonDemoActivity extends ScopedElementsMixin(AdapterLitElement) {
             <dbp-common-demo
                 id="demo"
                 lang="${this.lang}"
+                lang-file="${this.langFile}"
                 entry-point-url="${this.entryPointUrl}"></dbp-common-demo>
         `;
     }
diff --git a/toolkit-showcase/src/i18n/de/translation.json b/toolkit-showcase/src/i18n/de/translation.json
new file mode 100644
index 00000000..d51b20bd
--- /dev/null
+++ b/toolkit-showcase/src/i18n/de/translation.json
@@ -0,0 +1,3 @@
+{
+    "toolkit-showcase": "Dieser Text wird mithilfe von i18n aus einer benutzerdefinierten Sprachdatei gelesen und ins Englische übersetzt wenn man die Sprache auf Englisch stellt."
+}
diff --git a/toolkit-showcase/src/i18n/en/translation.json b/toolkit-showcase/src/i18n/en/translation.json
new file mode 100644
index 00000000..789bde6f
--- /dev/null
+++ b/toolkit-showcase/src/i18n/en/translation.json
@@ -0,0 +1,12 @@
+{
+    "error": {
+        "connection-to-server-refused": "Connection to server refused!",
+        "summary": "An error occurred"
+    },
+    "jsonld": {
+        "api-documentation-server": "Connection to apiDocumentation server {{apiDocUrl}} failed!",
+        "error-api-server": "Connection to api server {{apiUrl}} failed!",
+        "error-hydra-documentation-url-not-set": "Hydra apiDocumentation url was not set for server {{apiUrl}}!"
+    },
+    "toolkit-showcase": "This text will be translated to german using i18n with a user defined language file when the language is changed to german."
+}
-- 
GitLab