Found while verifying #3676 (PR #3832). Filing rather than widening that PR — different defect, different severity.
The gap
#3778 established that there is one translation shape: the nested objects.<object>.fields.<field>.label. The flat o.-dotted dialect was retired because no producer ever wrote it. Commit 4cca74c fixed the getFieldLabels fallback in service-i18n accordingly, and left a comment saying so:
// Fallback: read field labels out of the locale's translation data.// That data is NESTED (`objects.<obj>.fields.<field>.label`) — the// flat dotted `o.<obj>.fields.<field>` keys this used to scan were a// third translation dialect that no producer ever wrote, so the// fallback always returned `{}` (#3778).constdata=i18n.getTranslations(locale)asTranslationData|undefined;constfields=data?.objects?.[objectName]?.fields??{};That fix did not reach the dispatcher's copy.packages/runtime/src/domains/i18n.ts:92-101 still does the pre-#3778 thing:
// Fallback: derive field labels from full translation bundleconsttranslations=i18nService.getTranslations(locale);constprefix=`o.${objectName}.fields.`;constlabels: Record<string,string>={};for(const[key,value]ofObject.entries(translations)){if(key.startsWith(prefix)){labels[key.substring(prefix.length)]=valueasstring;}}By the same reasoning 4cca74c applied to the other copy, this loop can never match: the bundle's top-level keys are the TranslationData groups (objects, apps, messages, …), never o.<object>.fields.<field>. So GET /i18n/labels/:object/:locale served through the dispatcher returns { labels: {} } whenever the i18n provider does not implement the optional getFieldLabels.
Reachability
The fallback is live, not dead code — getFieldLabels is optional on II18nService, and the branch guards on exactly that:
if(typeofi18nService.getFieldLabels==='function'){…}// otherwise ↓ the broken scanAny provider without the optional method takes it. Worth confirming which shipped providers actually land here (memory-i18n is the obvious candidate) — that determines whether this is user-visible today or only latent for third-party providers.
Severity note
This is a nastier class than #3676, which prompted the check. There, a declared filter was ignored and the caller got the full bundle — a correct superset, just not the optimization advertised. Here the caller gets a silently empty result that is indistinguishable from "this object has no translated labels". Nothing errors, nothing warns.
Fix
Port 4cca74c's fallback to packages/runtime/src/domains/i18n.ts — read data?.objects?.[objectName]?.fields and pull .label off each entry, matching service-i18n exactly.
Worth considering as part of the fix: these two fallbacks are the same logic maintained in two places, which is how this one got missed. Lifting it into a shared helper both surfaces call would keep the next shape change from having to be made twice.
Found while verifying #3676 (PR #3832). Filing rather than widening that PR — different defect, different severity.
The gap
#3778 established that there is one translation shape: the nested
objects.<object>.fields.<field>.label. The flato.-dotted dialect was retired because no producer ever wrote it. Commit 4cca74c fixed thegetFieldLabelsfallback inservice-i18naccordingly, and left a comment saying so:That fix did not reach the dispatcher's copy.
packages/runtime/src/domains/i18n.ts:92-101still does the pre-#3778 thing:By the same reasoning 4cca74c applied to the other copy, this loop can never match: the bundle's top-level keys are the
TranslationDatagroups (objects,apps,messages, …), nevero.<object>.fields.<field>. SoGET /i18n/labels/:object/:localeserved through the dispatcher returns{ labels: {} }whenever the i18n provider does not implement the optionalgetFieldLabels.Reachability
The fallback is live, not dead code —
getFieldLabelsis optional onII18nService, and the branch guards on exactly that:Any provider without the optional method takes it. Worth confirming which shipped providers actually land here (
memory-i18nis the obvious candidate) — that determines whether this is user-visible today or only latent for third-party providers.Severity note
This is a nastier class than #3676, which prompted the check. There, a declared filter was ignored and the caller got the full bundle — a correct superset, just not the optimization advertised. Here the caller gets a silently empty result that is indistinguishable from "this object has no translated labels". Nothing errors, nothing warns.
Fix
Port 4cca74c's fallback to
packages/runtime/src/domains/i18n.ts— readdata?.objects?.[objectName]?.fieldsand pull.labeloff each entry, matching service-i18n exactly.Worth considering as part of the fix: these two fallbacks are the same logic maintained in two places, which is how this one got missed. Lifting it into a shared helper both surfaces call would keep the next shape change from having to be made twice.