Skip to content

Dispatcher's getFieldLabels fallback still scans the retired flat o. prefix — always returns {} #3833

Description

@os-zhuang

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 scan

Any 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions