diff --git a/packages/app-shell/src/app-shell.js b/packages/app-shell/src/app-shell.js index 65892521d9ad18fcdd8b3a7a8be0b5dae4aa7d63..15eb1cc0c0570511fcaa261f4a45d83a82403bf7 100644 --- a/packages/app-shell/src/app-shell.js +++ b/packages/app-shell/src/app-shell.js @@ -75,6 +75,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { this.auth = {}; this.langFiles = ''; + this.overrideFiles = ''; } static get scopedElements() { @@ -273,6 +274,7 @@ export class AppShell extends ScopedElementsMixin(DBPLitElement) { env: {type: String}, auth: {type: Object}, langFiles: {type: String, attribute: 'lang-files'}, + overrideFiles: {type: String, attribute: 'override-files'}, }; } diff --git a/packages/common/dbp-common-demo.js b/packages/common/dbp-common-demo.js index ef8c262275be70730755c3a2b1a78627d1a8358b..7893841f2cc52fcd43d518694fd00339ac0a5460 100644 --- a/packages/common/dbp-common-demo.js +++ b/packages/common/dbp-common-demo.js @@ -15,13 +15,14 @@ import { Translation, } from './index.js'; + + export class DbpCommonDemo extends ScopedElementsMixin(LitElement) { constructor() { super(); this._i18n = createInstance(); this.lang = this._i18n.language; this.noAuth = false; - this.langFiles = ''; } static get scopedElements() { @@ -47,7 +48,6 @@ export class DbpCommonDemo extends ScopedElementsMixin(LitElement) { return { lang: {type: String}, noAuth: {type: Boolean, attribute: 'no-auth'}, - langFiles: {type: String, attribute: 'lang-files'}, }; } @@ -302,8 +302,10 @@ html { </dbp-translated> </div> <div class="control" id="dbp-translation-demo"> - <dbp-translation key="toolkit-showcase" subscribe="lang, lang-files"></dbp-translation> - <dbp-translation key="toolkit-showcase-link" var='{"link1": "https://www.i18next.com/translation-function/interpolation"}' subscribe="lang, lang-files" unsafe></dbp-translation> + <p><dbp-translation key="toolkit-showcase" subscribe="lang, lang-files"></dbp-translation></p> + <p><dbp-translation key="toolkit-showcase" subscribe="lang, lang-files, override-files"></dbp-translation></p> + <p><dbp-translation key="toolkit-showcase-link" var='{"link1": "https://www.i18next.com/translation-function/interpolation"}' subscribe="lang, lang-files" unsafe></dbp-translation></p> + <p><dbp-translation key="toolkit-showcase-link" var='{"link1": "https://dbp-demo.tugraz.at/"}' subscribe="lang, lang-files, override-files" unsafe></dbp-translation></p> </div> </div> </section> diff --git a/packages/common/i18next.js b/packages/common/i18next.js index 2b9244b3608c23c74e0123a7b4076c3363346981..54550215ea628cb9b5b2ed433b7d3b0627b05284 100644 --- a/packages/common/i18next.js +++ b/packages/common/i18next.js @@ -124,3 +124,48 @@ export function setOverrides(i18n, element, overrides) { } i18n.setDefaultNamespace(hasOverrides ? overrideNamespace : namespace); } + +async function fetchOverridesByLanguage(overrides, lng) { + let result = await + fetch(overrides + lng +'/translation.json', { + headers: {'Content-Type': 'application/json'}, + }); + let json = await result.json(); + return json; +} + +/** + * Sets translation overrides for the given i18next instance. Any previously + * applied overrides will be removed first. So calling this with an empty overrides + * object is equal to removing all overrides. + * + * @param {i18next.i18n} i18n - The i18next instance + * @param {HTMLElement} element - The element at which the overrides are targeted + * @param {String} overridesFile - Path to the translation file containing the overrides + */ +export async function setOverridesByFile(i18n, element, overridesFile) { + // We add a special namespace which gets used with priority and falls back + // to the original one. This way we an change the overrides at runtime + // and can even remove them. + + // The scoped mixin saves the real tag name under data-tag-name + let tagName = ((element.dataset && element.dataset.tagName) || element.tagName).toLowerCase(); + let namespace = i18n.options.fallbackNS; + let overrideNamespace = getOverrideNamespace(namespace); + let hasOverrides = false; + for (let lng of i18n.languages) { + // get translation.json for each lang + let response = await fetchOverridesByLanguage(overridesFile, lng); + // remove old language + i18n.removeResourceBundle(lng, overrideNamespace); + // if no new translation is available, skip + if (response === undefined || response[tagName] === undefined) return; + // get new translation + let resources = response[tagName]; + hasOverrides = true; + // set new translation + i18n.addResourceBundle(lng, overrideNamespace, resources); + i18n.setDefaultNamespace(hasOverrides ? overrideNamespace : namespace); + } + return i18n; +} diff --git a/packages/common/src/i18n.js b/packages/common/src/i18n.js index d9e9630a3f2ed276165e46ed7f7732d3bb86e7a1..ae6a0c68533a977069ae91e0bea852e438f3dba9 100644 --- a/packages/common/src/i18n.js +++ b/packages/common/src/i18n.js @@ -1,4 +1,4 @@ -import {createInstance as _createInstance} from '../i18next.js'; +import {createInstance as _createInstance, setOverridesByFile} from '../i18next.js'; import de from './i18n/de/translation.json'; import en from './i18n/en/translation.json'; @@ -12,6 +12,7 @@ export async function createInstanceAsync(langFile, namespace) { namespace = 'translation' // check if a path to language files is given if(langFile) { + // request german lang file asynchronously let result = await fetch(langFile + 'de/' + namespace +'.json', { @@ -31,3 +32,5 @@ export async function createInstanceAsync(langFile, namespace) { return _createInstance({en: en, de: de}, 'de', 'en', namespace); } + +export {setOverridesByFile}; diff --git a/packages/common/src/i18n/de/translation.json b/packages/common/src/i18n/de/translation.json index b9daed00bc4e840bf1e885e4cff6f5a5e9c4d229..51cf793c6cb4108b18d8cab47b63959663ec8590 100644 --- a/packages/common/src/i18n/de/translation.json +++ b/packages/common/src/i18n/de/translation.json @@ -7,6 +7,5 @@ "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 1e17839ab74d17ebb29bd8bf73c721a13533280a..e1036a6580fd180af131a4c2744c8c4fef2745fb 100644 --- a/packages/common/src/i18n/en/translation.json +++ b/packages/common/src/i18n/en/translation.json @@ -7,6 +7,5 @@ "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 index 874bcb0aed096a13281ba1f3c56deaa5f7c881c1..fd965c2e747155b3edd329bb9bfa9ddaaf2fd52b 100644 --- a/packages/common/src/translation.js +++ b/packages/common/src/translation.js @@ -2,7 +2,20 @@ import {css, html} from 'lit'; import {until} from 'lit/directives/until.js'; import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import DBPLitElement from '../dbp-lit-element'; -import {createInstanceAsync} from './i18n.js'; +import {createInstanceAsync, setOverridesByFile} from './i18n.js'; + +let OVERRIDE = { + 'de': { + 'dbp-translation': { + 'toolkit-showcase': 'testText' + } + }, + 'en': { + 'dbp-translation': { + 'toolkit-showcase': 'testText2' + } + } +} export class Translation extends DBPLitElement { constructor() { @@ -12,6 +25,7 @@ export class Translation extends DBPLitElement { this.langFiles = ''; this.interpolation = ''; this.namespace = ''; + this.overrideFiles = ''; } static get properties() { @@ -23,6 +37,7 @@ export class Translation extends DBPLitElement { interpolation: {type: Object, attribute: 'var'}, unsafe: {type: Boolean, attribute: 'unsafe'}, namespace: {type: String, attribute: 'ns'}, + overrideFiles: {type: String, attribute: 'override-files'}, }; } @@ -37,11 +52,24 @@ export class Translation extends DBPLitElement { connectedCallback() { super.connectedCallback(); - if (this.namespace == '') + if (this.namespace == '') { this._i18n = createInstanceAsync(this.langFiles); + } else { this._i18n = createInstanceAsync(this.langFiles, this.namespace); } + + let local = this; + let overrideFiles = this.overrideFiles; + + if (this.overrideFiles) { + this._i18n.then(function(response) { + setOverridesByFile(response, local, overrideFiles).then(function(response) { + local._i18n = response; + local.requestUpdate(); + }); + }) + } } update(changedProperties) { @@ -49,7 +77,7 @@ export class Translation extends DBPLitElement { changedProperties.forEach((oldValue, propName) => { switch (propName) { case 'lang': - this._i18n.then(function(response) { + Promise.resolve(this._i18n).then(function(response) { response.changeLanguage(lang); }); break; @@ -66,7 +94,7 @@ export class Translation extends DBPLitElement { let unsafe = this.unsafe; // async request to i18n translation - const translation = this._i18n.then(function(response){ + const translation = Promise.resolve(this._i18n).then(function(response){ if (interpolation && unsafe) return unsafeHTML(response.t(key, interpolation)); else if (interpolation) diff --git a/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs b/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs index feaee44ca49cf1b38505c4840f74a0e3eabbf2aa..9d45cbe472fbff92215897635961c4ad821ab031 100644 --- a/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs +++ b/toolkit-showcase/assets/dbp-toolkit-showcase.html.ejs @@ -114,6 +114,7 @@ provider-root lang="de" lang-files="<%= getPrivateUrl('i18n/') %>" + override-files="<%= getPrivateUrl('i18n/overrides/') %>" entry-point-url="<%= entryPointURL %>" nextcloud-auth-url="<%= nextcloudWebAppPasswordURL %>" nextcloud-web-dav-url="<%= nextcloudWebDavURL %>" diff --git a/toolkit-showcase/assets/i18n/overrides/de/translation.json b/toolkit-showcase/assets/i18n/overrides/de/translation.json new file mode 100644 index 0000000000000000000000000000000000000000..b3b136fbef6577c800f678f3b15957a3dd029a6e --- /dev/null +++ b/toolkit-showcase/assets/i18n/overrides/de/translation.json @@ -0,0 +1,6 @@ +{ + "dbp-translation": { + "toolkit-showcase": "Überschriebener i18n toolkit-showcase Text", + "toolkit-showcase-link": "Überschriebener i18n toolkit-showcase-link Text mit <a href=\"{{- link1}}\">TestLink</a>" + } +} diff --git a/toolkit-showcase/assets/i18n/overrides/en/translation.json b/toolkit-showcase/assets/i18n/overrides/en/translation.json new file mode 100644 index 0000000000000000000000000000000000000000..3892525bd8e50e661a30f65057a10d43526b8015 --- /dev/null +++ b/toolkit-showcase/assets/i18n/overrides/en/translation.json @@ -0,0 +1,6 @@ +{ + "dbp-translation": { + "toolkit-showcase": "Overriden i18n toolkit-showcase text", + "toolkit-showcase-link": "Overriden i18n toolkit-showcase-link text with a <a href=\"{{- link1}}\">test link</a>" + } +}