-
+
diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/perspective.js.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/perspective.js.template
index c89507825d..e8dda8eb84 100644
--- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/perspective.js.template
+++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/perspective/perspective.js.template
@@ -11,6 +11,9 @@
const perspectiveData = {
id: '${projectName}-${name}',
label: '${menuLabel}',
+ // i18n key of the (pluralized) label in the owning project's generated catalog; the shared shell
+ // translates the sidebar entry through it (loading the '${projectName}' namespace on demand).
+ tkey: '${projectName}:${tprefix}.t.${dataName}_plural',
path: '/services/web/${projectName}/gen/${genFolderName}/index.html?embedded=true#/${name}',
kind: '${type}',
// Absolute /count of this entity's generated REST controller, so the shared shell dashboard can show
diff --git a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/shell/index.html.template b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/shell/index.html.template
index 1aa2802bd2..069d5b1f77 100644
--- a/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/shell/index.html.template
+++ b/components/template/template-application-ui-harmonia-java/src/main/resources/META-INF/dirigible/template-application-ui-harmonia-java/ui/shell/index.html.template
@@ -103,7 +103,7 @@
#end
@@ -368,7 +368,7 @@
// Route segment -> i18n catalog key (the shared shell translates breadcrumbs through these).
window.__harmoniaNavKeys = {
#foreach($entity in $models)
- "${entity.name}": "$projectName:${tprefix}.t.${entity.dataName}",
+ "${entity.name}": "$projectName:${tprefix}.t.${entity.dataName}_plural",
#end
};
diff --git a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js
index a8cb51f4ed..a6e5093aec 100644
--- a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js
+++ b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js
@@ -17,6 +17,51 @@ function getTranslationId(str) {
return `${str.replaceAll(' ', '').replaceAll('_', '').replaceAll('.', '').replaceAll(':', '')}`;
}
+// Humanize a PascalCase/camelCase identifier for display ("SalesInvoice" -> "Sales Invoice").
+// Mirrors org.eclipse.dirigible.components.intent.generator.IntentNaming.humanize, including its
+// acronym overrides, so a hand-authored .edm (no entityLabel/menuLabel baked by the intent
+// generator) still yields the same labels the intent path would.
+const HUMANIZE_OVERRIDES = { 'uom': 'Unit of Measure' };
+
+function humanizeName(name) {
+ if (!name) return '';
+ const override = HUMANIZE_OVERRIDES[name.toLowerCase()];
+ if (override) return override;
+ let out = '';
+ for (let i = 0; i < name.length; i++) {
+ const c = name.charAt(i);
+ if (i > 0 && c >= 'A' && c <= 'Z' && !(name.charAt(i - 1) >= 'A' && name.charAt(i - 1) <= 'Z')) {
+ out += ' ';
+ }
+ out += i === 0 ? c.toUpperCase() : c;
+ }
+ return out;
+}
+
+// Pluralize a humanized label's last word ("Sales Invoice" -> "Sales Invoices", "Country" ->
+// "Countries"). Mirrors IntentNaming.pluralize, including its irregular overrides.
+const PLURALIZE_OVERRIDES = { 'unit of measure': 'Units of Measure', 'uom': 'Units of Measure' };
+
+function pluralizeLabel(label) {
+ if (!label) return '';
+ const override = PLURALIZE_OVERRIDES[label.toLowerCase()];
+ if (override) return override;
+ const sp = label.lastIndexOf(' ');
+ const head = sp >= 0 ? label.substring(0, sp + 1) : '';
+ const last = sp >= 0 ? label.substring(sp + 1) : label;
+ if (!last) return label;
+ const lower = last.toLowerCase();
+ let plural;
+ if (lower.length > 1 && lower.endsWith('y') && 'aeiou'.indexOf(lower.charAt(lower.length - 2)) < 0) {
+ plural = last.substring(0, last.length - 1) + 'ies';
+ } else if (lower.endsWith('s') || lower.endsWith('x') || lower.endsWith('z') || lower.endsWith('ch') || lower.endsWith('sh')) {
+ plural = last + 'es';
+ } else {
+ plural = last + 's';
+ }
+ return head + plural;
+}
+
function getTranslations(model) {
let translations = {};
for (const [key, value] of Object.entries(model)) {
@@ -686,7 +731,15 @@ export function generateFiles(model, parameters, templateSources) {
if (model.entities) {
for (let i = 0; i < model.entities.length; i++) {
if (model.entities[i].dataName && model.entities[i].name) {
- translations.t[model.entities[i].dataName] = model.entities[i].name;
+ // The entity's humanized singular ("Sales Invoice") and pluralized
+ // ("Sales Invoices") display names, per language: t.
feeds the
+ // singular UI texts (form captions, "New X", "X #id"), t._plural
+ // the plural ones (sidebar entries, list/master titles, detail panels).
+ // The model's own entityLabel/menuLabel (EdmIntentGenerator) win; models
+ // without them (hand-authored .edm) get the same derivation applied here.
+ const singular = model.entities[i].entityLabel || humanizeName(model.entities[i].name);
+ translations.t[model.entities[i].dataName] = singular;
+ translations.t[`${model.entities[i].dataName}_plural`] = model.entities[i].menuLabel || pluralizeLabel(singular);
}
if (model.entities[i].properties) {
properties(model.entities[i].properties);