diff --git a/core/src/services/routing.js b/core/src/services/routing.js index fdae494143..3a6d9dbed8 100644 --- a/core/src/services/routing.js +++ b/core/src/services/routing.js @@ -290,7 +290,7 @@ class RoutingClass { let cNode2 = currentNode; let hideSideNavInherited = nodeObject.hideSideNav; - if(hideSideNavInherited === undefined) { + if (hideSideNavInherited === undefined) { while (cNode2) { if (cNode2.tabNav && cNode2.hideSideNav === true) { hideSideNavInherited = true; @@ -500,6 +500,7 @@ class RoutingClass { } navigateToExternalLink(externalLink) { + externalLink.url = RoutingHelpers.getI18nViewUrl(externalLink.url); const updatedExternalLink = { ...NAVIGATION_DEFAULTS.externalLink, ...externalLink diff --git a/core/src/services/web-components.js b/core/src/services/web-components.js index e29ff30810..f0c208e911 100644 --- a/core/src/services/web-components.js +++ b/core/src/services/web-components.js @@ -4,6 +4,7 @@ import { registerEventListeners } from '../utilities/helpers/web-component-helpers'; import { LuigiConfig } from '../core-api'; +import { RoutingHelpers } from '../utilities/helpers'; /** Methods for dealing with web components based micro frontend handling */ class WebComponentSvcClass { @@ -72,9 +73,10 @@ class WebComponentSvcClass { * specified. * @returns a promise that gets resolved after successfull import */ registerWCFromUrl(viewUrl, wc_id) { + const i18nViewUrl = RoutingHelpers.getI18nViewUrl(viewUrl); return new Promise((resolve, reject) => { - if (this.checkWCUrl(viewUrl)) { - this.dynamicImport(viewUrl) + if (this.checkWCUrl(i18nViewUrl)) { + this.dynamicImport(i18nViewUrl) .then(module => { try { if (!window.customElements.get(wc_id)) { @@ -97,8 +99,8 @@ class WebComponentSvcClass { }) .catch(err => reject(err)); } else { - console.warn(`View URL '${viewUrl}' not allowed to be included`); - reject(`View URL '${viewUrl}' not allowed`); + console.warn(`View URL '${i18nViewUrl}' not allowed to be included`); + reject(`View URL '${i18nViewUrl}' not allowed`); } }); } @@ -170,26 +172,27 @@ class WebComponentSvcClass { * If the web component is not defined yet, it gets imported. */ renderWebComponent(viewUrl, wc_container, context, node, nodeId) { + const i18nViewUrl = RoutingHelpers.substituteViewUrl(viewUrl, { context }); const wc_id = - node.webcomponent && node.webcomponent.tagName ? node.webcomponent.tagName : this.generateWCId(viewUrl); + node.webcomponent && node.webcomponent.tagName ? node.webcomponent.tagName : this.generateWCId(i18nViewUrl); const wcItemPlaceholder = document.createElement('div'); wc_container.appendChild(wcItemPlaceholder); if (window.customElements.get(wc_id)) { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, viewUrl, nodeId); + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId); } else { /** Custom import function, if defined */ if (window.luigiWCFn) { - window.luigiWCFn(viewUrl, wc_id, wcItemPlaceholder, () => { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, viewUrl, nodeId); + window.luigiWCFn(i18nViewUrl, wc_id, wcItemPlaceholder, () => { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId); }); } else if (node.webcomponent && node.webcomponent.selfRegistered) { - this.includeSelfRegisteredWCFromUrl(node, viewUrl, () => { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, viewUrl, nodeId); + this.includeSelfRegisteredWCFromUrl(node, i18nViewUrl, () => { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId); }); } else { - this.registerWCFromUrl(viewUrl, wc_id).then(() => { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, viewUrl, nodeId); + this.registerWCFromUrl(i18nViewUrl, wc_id).then(() => { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId); }); } } @@ -230,10 +233,9 @@ class WebComponentSvcClass { */ renderWebComponentCompound(navNode, wc_container, context) { let renderer; - if (navNode.webcomponent && navNode.viewUrl) { renderer = new DefaultCompoundRenderer(); - renderer.viewUrl = navNode.viewUrl; + renderer.viewUrl = RoutingHelpers.substituteViewUrl(navNode.viewUrl, { context }); renderer.createCompoundItemContainer = layoutConfig => { var cnt = document.createElement('div'); if (layoutConfig && layoutConfig.slot) { diff --git a/core/src/utilities/helpers/routing-helpers.js b/core/src/utilities/helpers/routing-helpers.js index cb81b16cd4..4924615b2a 100644 --- a/core/src/utilities/helpers/routing-helpers.js +++ b/core/src/utilities/helpers/routing-helpers.js @@ -195,7 +195,7 @@ class RoutingHelpersClass { pathParams, LuigiConfig.getConfigValue('routing.useHashRouting') ? '#' : '' ); - return link.url || link; + return this.getI18nViewUrl(link.url) || link; } return 'javascript:void(0)'; } @@ -262,19 +262,28 @@ class RoutingHelpersClass { return this.isDynamicNode(node) ? pathParams[node.pathSegment.substring(1)] : undefined; } + /** + * Returns the viewUrl with current locale, e.g. luigi/{i18n.currentLocale}/ -> luigi/en + * if viewUrl contains {i18n.currentLocale} term, it will be replaced by current locale + * @param {*} viewUrl + */ + getI18nViewUrl(viewUrl) { + const i18n_currentLocale = '{i18n.currentLocale}'; + const locale = LuigiI18N.getCurrentLocale(); + const hasI18n = viewUrl && viewUrl.includes(i18n_currentLocale); + + return hasI18n ? viewUrl.replace(i18n_currentLocale, locale) : viewUrl; + } + substituteViewUrl(viewUrl, componentData) { const contextVarPrefix = 'context.'; const nodeParamsVarPrefix = 'nodeParams.'; - const i18n_currentLocale = '{i18n.currentLocale}'; const searchQuery = 'routing.queryParams'; viewUrl = GenericHelpers.replaceVars(viewUrl, componentData.pathParams, ':', false); viewUrl = GenericHelpers.replaceVars(viewUrl, componentData.context, contextVarPrefix); viewUrl = GenericHelpers.replaceVars(viewUrl, componentData.nodeParams, nodeParamsVarPrefix); - - if (viewUrl.includes(i18n_currentLocale)) { - viewUrl = viewUrl.replace(i18n_currentLocale, LuigiI18N.getCurrentLocale()); - } + viewUrl = this.getI18nViewUrl(viewUrl); if (viewUrl.includes(searchQuery)) { const viewUrlSearchParam = viewUrl.split('?')[1]; diff --git a/core/test/utilities/helpers/routing-helpers.spec.js b/core/test/utilities/helpers/routing-helpers.spec.js index 3f40c111ef..63ed9e03fb 100644 --- a/core/test/utilities/helpers/routing-helpers.spec.js +++ b/core/test/utilities/helpers/routing-helpers.spec.js @@ -83,14 +83,28 @@ describe('Routing-helpers', () => { setItem: sinon.stub() }; sinon.stub(config, 'configChanged'); + sinon.stub(LuigiI18N, '_notifyLocaleChange'); + LuigiI18N.setCurrentLocale('en'); }); afterEach(() => { sinon.restore(); }); - it('substitutes {i18n.currentLocale} variable to current locale', () => { - sinon.stub(LuigiI18N, '_notifyLocaleChange'); - LuigiI18N.setCurrentLocale('en'); + it('getI18nViewUrl - substitutes {i18n.currentLocale} variable to current locale', () => { + const viewUrl = '/{i18n.currentLocale}/microfrontend.html'; + const expected = '/en/microfrontend.html'; + + expect(RoutingHelpers.getI18nViewUrl(viewUrl)).to.equal(expected); + }); + + it('No substitution if {i18n.currentLocale} variable is not provided', () => { + const viewUrl = '/{i18n}/microfrontend.html'; + const expected = '/{i18n}/microfrontend.html'; + + expect(RoutingHelpers.getI18nViewUrl(viewUrl)).to.equal(expected); + }); + + it('substituteViewUrl - substitutes {i18n.currentLocale} variable to current locale', () => { const viewUrl = '/{i18n.currentLocale}/microfrontend.html'; const expected = '/en/microfrontend.html'; diff --git a/docs/navigation-parameters-reference.md b/docs/navigation-parameters-reference.md index 3588f9e6e1..5ec37b6761 100644 --- a/docs/navigation-parameters-reference.md +++ b/docs/navigation-parameters-reference.md @@ -167,7 +167,7 @@ Node parameters are all the parameters that can be added to an individual naviga - **attributes**: - **sameWindow** defines if the external URL is opened in a new or current tab. The default value for this parameter is `false`. - - **URL** is the external URL that the node leads to. + - **URL** is the external URL that the node leads to. If you are using [localization](https://docs.luigi-project.io/docs/i18n) and translating your page into different languages, you can also add a **{i18n.currentLocale}** parameter to the url part of your configuration. ### label - **type**: string @@ -510,7 +510,7 @@ Web components can communicate over an event bus. - **description**: Array of web component nodes. - **attributes**: - **id**: unique `id` of the web component. - - **viewUrl**: URL which points to the web component `.js` file. + - **viewUrl**: URL which points to the web component `.js` file. If you are using [localization](https://docs.luigi-project.io/docs/i18n) and translating your page into different languages, you can also add a **{i18n.currentLocale}** parameter to the viewUrl part of your configuration. - **context**: object, which you can pass to the web component. - **layoutConfig**: config object to define the position of an item in a grid. The properties are `row` and `column` and get the same values as in the CSS grid standard. If you want to use the mechanism of nested web components, you can define a `slot` property with the slot name instead of the config object. In that case this web component node will be plugged in the parent web component. - **eventListeners** diff --git a/test/e2e-test-application/src/luigi-config/extended/projectDetailNav.js b/test/e2e-test-application/src/luigi-config/extended/projectDetailNav.js index 734be4e7b7..2ff1bb9309 100644 --- a/test/e2e-test-application/src/luigi-config/extended/projectDetailNav.js +++ b/test/e2e-test-application/src/luigi-config/extended/projectDetailNav.js @@ -179,7 +179,7 @@ export const projectDetailNavStructure = projectId => [ context: { title: 'Hello WebComponent!' }, - viewUrl: '/assets/helloWorldWC.js', + viewUrl: '/assets/helloWorldWC.js?{i18n.currentLocale}', webcomponent: true, openNodeInModal: true }, @@ -193,7 +193,7 @@ export const projectDetailNavStructure = projectId => [ context: { title: 'Hello WebComponent!' }, - viewUrl: '/assets/helloWorldWC.js', + viewUrl: '/assets/helloWorldWC.js?{i18n.currentLocale}', webcomponent: true }, { @@ -343,6 +343,15 @@ export const projectDetailNavStructure = projectId => [ }, icon: 'globe' }, + { + label: 'Open SAP Website', + category: 'Super useful Github links', + externalLink: { + url: 'http://sap.com/{i18n.currentLocale}', + sameWindow: true + }, + icon: 'globe' + }, { pathSegment: 'collapsibles', label: 'Collapsible categories',