Observation-class finding, filed out of objectstack#6765 (the shared backend I18nLabel → string resolver, which is pinned to this function's rule). Not something a user hits today in the console — filed so the next reader of either implementation is not surprised by the one place they deliberately differ.
What
packages/i18n/src/pickLocalized.ts reads map entries with a bare bracket access:
const pick =
o[lang] ??
o[base] ??
(regional !== undefined ? o[regional] : undefined) ??
o.default ??
o.en ??
Object.values(o).find((v) => typeof v === 'string');
return pick == null ? '' : String(pick);
o[lang] / o[base] walk the prototype chain, so when language happens to name an Object.prototype member the lookup succeeds against the prototype and the function stringifies it:
pickLocalized({ en: 'Pricing' }, 'constructor');
// → 'function Object() { [native code] }'
pickLocalized({ en: 'Pricing' }, 'toString');
// → 'function toString() { [native code] }'
Same for valueOf, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toLocaleString. Verified against origin/main 50fa376 (blob 9e5d92a for the file, last touched by #3278).
Note the four limbs are inconsistent with the other two on a second axis as well: regional and the last-resort Object.values(...).find(...) both require typeof === 'string', while o[lang], o[base], o.default and o.en do not — so a non-string value on those limbs short-circuits the chain and renders as [object Object]. Inside the declared domain that is unobservable (InlineLocaleMapSchema in the framework repo is z.record(<BCP-47 tag>, z.string())), which is why this is filed as an observation rather than a defect.
Why it is observation-class here
In the console the language argument comes from the app's own localization state (useObjectTranslation().language / useLocalization()), i.e. a value the app picked, not one an outsider supplies. No BCP-47 language tag is an Object.prototype member, so no in-contract input reaches this. It is recorded rather than fixed silently because the other implementation of this rule now exists and its input can be untrusted.
Cross-repo context
objectstack#6765 adds resolveI18nLabel in packages/spec/src/ui/i18n-label-resolver.ts, mirrored limb for limb from this function and pinned by an executed parity table (a verbatim copy of this file is checked into that test as the reference). It reads own properties only and applies the string filter on every limb, because on a server the locale can arrive in an Accept-Language header. Those are the two deliberate departures documented there, and this card is the other half of that documentation: they are divergences from this implementation, both narrower than the shared rule, and neither is reachable by an input I18nLabelSchema accepts.
Suggested shape if it is ever picked up
Guard the four unguarded limbs the way the other two already are — an own-property check plus the existing typeof === 'string' filter — which collapses the difference between the two implementations to zero. No behaviour change for any language tag.
Generated by Claude Code
Observation-class finding, filed out of objectstack#6765 (the shared backend
I18nLabel→stringresolver, which is pinned to this function's rule). Not something a user hits today in the console — filed so the next reader of either implementation is not surprised by the one place they deliberately differ.What
packages/i18n/src/pickLocalized.tsreads map entries with a bare bracket access:o[lang]/o[base]walk the prototype chain, so whenlanguagehappens to name anObject.prototypemember the lookup succeeds against the prototype and the function stringifies it:Same for
valueOf,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString. Verified againstorigin/main50fa376(blob9e5d92afor the file, last touched by #3278).Note the four limbs are inconsistent with the other two on a second axis as well:
regionaland the last-resortObject.values(...).find(...)both requiretypeof === 'string', whileo[lang],o[base],o.defaultando.endo not — so a non-string value on those limbs short-circuits the chain and renders as[object Object]. Inside the declared domain that is unobservable (InlineLocaleMapSchemain the framework repo isz.record(<BCP-47 tag>, z.string())), which is why this is filed as an observation rather than a defect.Why it is observation-class here
In the console the
languageargument comes from the app's own localization state (useObjectTranslation().language/useLocalization()), i.e. a value the app picked, not one an outsider supplies. No BCP-47 language tag is anObject.prototypemember, so no in-contract input reaches this. It is recorded rather than fixed silently because the other implementation of this rule now exists and its input can be untrusted.Cross-repo context
objectstack#6765 adds
resolveI18nLabelinpackages/spec/src/ui/i18n-label-resolver.ts, mirrored limb for limb from this function and pinned by an executed parity table (a verbatim copy of this file is checked into that test as the reference). It reads own properties only and applies thestringfilter on every limb, because on a server the locale can arrive in anAccept-Languageheader. Those are the two deliberate departures documented there, and this card is the other half of that documentation: they are divergences from this implementation, both narrower than the shared rule, and neither is reachable by an inputI18nLabelSchemaaccepts.Suggested shape if it is ever picked up
Guard the four unguarded limbs the way the other two already are — an own-property check plus the existing
typeof === 'string'filter — which collapses the difference between the two implementations to zero. No behaviour change for any language tag.Generated by Claude Code