From 15b1125af6f9cc3c3d865acdc6d2bdc6963c1f71 Mon Sep 17 00:00:00 2001 From: delchev Date: Fri, 3 Jul 2026 12:31:53 +0300 Subject: [PATCH 1/8] fix(harmonia): print language dialog must always show for multiple templates The multilingual feature made openPrint auto-print the configured Region & Language when a template for it exists. The locale store ALWAYS resolves to a value (en by default, even untouched), so with 2+ templates the language dialog became unreachable - uploading a bg template gave no way to pick it. Several templates now ALWAYS open the dialog; the configured language is only pre-sorted first as the suggested default. Single/zero template behavior unchanged. Co-Authored-By: Claude Fable 5 --- .../DirigibleJavaScriptEndpointUriFactory.java | 7 +++++++ .../perspective/document/document-page.js.template | 14 +++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/components/engine/engine-camel/src/generated/java/org/eclipse/dirigible/components/engine/camel/components/DirigibleJavaScriptEndpointUriFactory.java b/components/engine/engine-camel/src/generated/java/org/eclipse/dirigible/components/engine/camel/components/DirigibleJavaScriptEndpointUriFactory.java index 49c186a444e..9e688b89804 100644 --- a/components/engine/engine-camel/src/generated/java/org/eclipse/dirigible/components/engine/camel/components/DirigibleJavaScriptEndpointUriFactory.java +++ b/components/engine/engine-camel/src/generated/java/org/eclipse/dirigible/components/engine/camel/components/DirigibleJavaScriptEndpointUriFactory.java @@ -21,6 +21,7 @@ public class DirigibleJavaScriptEndpointUriFactory extends org.apache.camel.supp private static final Set PROPERTY_NAMES; private static final Set SECRET_PROPERTY_NAMES; + private static final Set ENDPOINT_IDENTITY_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { Set props = new HashSet<>(2); @@ -28,6 +29,7 @@ public class DirigibleJavaScriptEndpointUriFactory extends org.apache.camel.supp props.add("lazyStartProducer"); PROPERTY_NAMES = Collections.unmodifiableSet(props); SECRET_PROPERTY_NAMES = Collections.emptySet(); + ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet(); MULTI_VALUE_PREFIXES = Collections.emptyMap(); } @@ -58,6 +60,11 @@ public Set secretPropertyNames() { return SECRET_PROPERTY_NAMES; } + @Override + public Set endpointIdentityPropertyNames() { + return ENDPOINT_IDENTITY_PROPERTY_NAMES; + } + @Override public Map multiValuePrefixes() { return MULTI_VALUE_PREFIXES; diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template index c9e384818ec..05c09b87a17 100644 --- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/document/document-page.js.template @@ -176,13 +176,13 @@ document.addEventListener('alpine:init', () => { console.error('Print languages lookup failed', e); } this.printBusy = false; - // The Region & Language setting wins when a template exists for it - print directly in the - // user's configured language instead of asking. - const configured = (Alpine.store('locale') || {}).value; - if (configured && languages.some(l => l.code === configured)) { - await this.printDoc(configured); - } else if (languages.length > 1) { - this.printLanguages = languages; + if (languages.length > 1) { + // Several templates: ALWAYS ask (auto-printing the configured language here suppressed the + // dialog entirely, since the locale store always resolves to a value - a shipped regression). + // The Region & Language setting only pre-sorts its language first as the suggested default. + const configured = (Alpine.store('locale') || {}).value; + this.printLanguages = languages.slice().sort((a, b) => + (a.code === configured ? -1 : 0) - (b.code === configured ? -1 : 0)); this.printLangOpen = true; } else { // zero languages still attempts the default: the server answers with a clear 404 From 783d0f884c9ba813335a8dab79bb8c8fa2b18885 Mon Sep 17 00:00:00 2001 From: delchev Date: Fri, 3 Jul 2026 12:42:55 +0300 Subject: [PATCH 2/8] fix(harmonia): report.js integer fallback pattern broke Velocity parsing The pattern-less numeric fallback used the DecimalFormat literal '##0' inside the Velocity-rendered report.js.template. Velocity treats two hashes as a line comment, so it swallowed the rest of the line including the closing quote - the generated report.js carried an unterminated string, failed to parse, reportPage never registered, and every report page rendered blank with Alpine expression errors. '0' formats identically in displayNumber (0 decimals, no grouping) and carries no Velocity-significant characters. Co-Authored-By: Claude Fable 5 --- .../ui/perspective/report-file/report.js.template | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/report-file/report.js.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/report-file/report.js.template index 5a5a201ff8f..c22bf1dcafc 100644 --- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/report-file/report.js.template +++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/report-file/report.js.template @@ -150,8 +150,10 @@ document.addEventListener('alpine:init', () => { const meta = this.columnMeta(key); if (meta && meta.pattern) return this.displayNumber(v, meta.pattern); // Pattern-less numeric columns (counts, year/month buckets) still render as clean integers - - // the DB returns them as decimals (1.0, 202607.0). - if (meta && meta.kind === 'number') return this.displayNumber(v, '##0'); + // the DB returns them as decimals (1.0, 202607.0). NB: the pattern must stay '0' - never a + // hash-style DecimalFormat literal, because two hashes start a Velocity line comment and + // swallow the rest of the line (unterminated string in the generated file). + if (meta && meta.kind === 'number') return this.displayNumber(v, '0'); return v; }, From 4d68fee2a806d97d84e3bb5350225ab1ee301e50 Mon Sep 17 00:00:00 2001 From: delchev Date: Fri, 3 Jul 2026 12:42:55 +0300 Subject: [PATCH 3/8] feat(application-shell): Region & Language picker in the shared shell Settings The picker previously existed only in each generated app's own Settings page (and only when that app's intent declares 2+ languages), so the shared application shell at /services/web/application/ had no visible place to switch the data language. The shell's Settings master pane now offers the same Region & Language select, backed by the shared locale store (localStorage codbex.harmonia.language, sent as Accept-Language by every app's fetch client). The offered codes are the union of the languages the embedded apps declare in their generated js/config.js (read with a targeted match - the config is a JS file); hidden while only one language is known. Co-Authored-By: Claude Fable 5 --- .../dirigible/application/js/appShell.js | 46 +++++++++++++++++++ .../application/views/_settings.html | 14 ++++++ 2 files changed, 60 insertions(+) diff --git a/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/js/appShell.js b/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/js/appShell.js index 28e7e3acb1f..01795d0442c 100644 --- a/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/js/appShell.js +++ b/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/js/appShell.js @@ -33,6 +33,41 @@ document.addEventListener('alpine:init', () => { settingsMode: false, settingsSelected: '', settingsUrl: '', + // Region & Language: the app-wide language flag (the shared locale store), offered here because + // the shell's Settings is where users look for it. The available codes are the UNION of the + // languages every embedded app declares in its generated js/config.js. + language: 'en', + languages: ['en'], + + // Union of the data languages the embedded apps declare (each generated app's js/config.js + // carries `languages: [...]` from its intent). The config is a JS file, so the array is read + // with a targeted match rather than executed. + async loadLanguages(perspectives) { + const bases = new Set(); + const collect = (item) => { + const path = item && item.path; + const match = typeof path === 'string' && path.match(/^(\/services\/web\/[^#?]*\/)index\.html/); + if (match) bases.add(match[1]); + }; + (perspectives || []).forEach(g => Array.isArray(g.items) ? g.items.forEach(collect) : collect(g)); + const union = new Set(['en']); + await Promise.all([...bases].map(async (base) => { + try { + const res = await fetch(base + 'js/config.js', { credentials: 'same-origin' }); + if (!res.ok) return; + const text = await res.text(); + const match = text.match(/languages:\s*(\[[^\]]*\])/); + if (match) JSON.parse(match[1].replace(/'/g, '"')).forEach(code => union.add(code)); + } catch (e) { /* an app without a readable config simply contributes nothing */ } + })); + this.languages = [...union]; + }, + + // The offered codes with display names for the Settings picker (delegates to the locale store). + languageOptions() { + const locale = Alpine.store('locale'); + return this.languages.map(code => ({ value: code, text: locale ? locale.displayName(code) : code })); + }, async init() { try { @@ -84,12 +119,23 @@ document.addEventListener('alpine:init', () => { this.settingsItems = settings; // Fire-and-forget: load each entity's live record count for the dashboard KPI tiles. this.loadCounts(); + // Fire-and-forget: union the data languages the embedded apps offer (drives the + // Region & Language picker in Settings; hidden while only one language is known). + this.loadLanguages(all); } } catch (e) { console.error('Failed to load application perspectives', e); } this.loading = false; + // Mirror the shared locale store so the Settings picker has a plain bindable property; + // persisting goes through the store (the fetch client sends it as Accept-Language). + const locale = Alpine.store('locale'); + if (locale) { + this.language = locale.value; + this.$watch('language', (v) => locale.set(v)); + } + // Resolve shell state from the current route. Hosted domain apps are addressable as // /app/[/]; everything else is a built-in page rendered into #app. // The inner route is the iframe app's own hash route (e.g. /SalesInvoice/42/edit), so the top diff --git a/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html b/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html index 8bfbb2bcf6f..78bd8205053 100644 --- a/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html +++ b/components/resources/resources-application/src/main/resources/META-INF/dirigible/application/views/_settings.html @@ -9,6 +9,20 @@
+ +
+ Region & Language +
+ +
+ +
+
+
Configuration & nomenclature across all applications.
+ +
Configuration & nomenclature across all applications.