Repository navigation
Description
Filed from objectstack-ai/hotcrm (the reference app) after both halves were measured in a real browser against the pinned @objectstack/console@17.4.0, not read off a docblock. Source cards: hotcrm#1822 and its PR objectstack-ai/hotcrm#1851, where the app worked around half of this and documented the other half in source.
Backlog searched before filing (this repo, semantic + control): the closest neighbours are all closed — #7804, #5428, #5141, #5076, #5084, #16773, #16167. None is open and none covers either half as stated below. #7804 is the direct ancestor and is why this is a consumer-half card rather than a feature request.
Two keys the spec types as translatable, and the Console resolves neither
1. GlobalFilterSchema.object — shipped in the spec by #7804, never implemented in the Console
@objectstack/spec@17.4.0 documents it in terms (dist/index.js:21087-21103):
"Source object for i18n label resolution (#7804): when set, this filter's field label and option labels resolve through the SAME
fields.<object>.<field>translation-bundle convention lists/forms already use, keyed by this object andfield— zero new i18n vocabulary, one resolver path."
#7804 is CLOSED. The spec half landed; the reading half was never built.
Measured: a dashboard globalFilters[] entry carrying object and no label, rendered under zh-CN, shows the raw field name — the literal string type, not 类型 — and leaves its option labels in English. The Console's filter-definition builder never copies the authored object onto the definition, and the label component reads def.label and nothing else:
// plugin-dashboard chunk
function In(e){ let { language: t } = xe(); return K(e.label, t) }⇒ the key is authorable, documented, validated, and inert. That is the declared-but-unenforced shape ADR-0049 exists to close, on a key whose own docblock names the resolver it is supposed to reach.
2. ChartAxisSchema.title accepts an inline locale map and is flattened locale-blind
ChartAxisSchema.title is I18nLabelSchema = z.union([z.string(), InlineLocaleMapSchema]), so { en: 'Revenue', 'zh-CN': '收入' } parses, builds and validates. At render the axis normaliser takes whichever value comes first:
// framework chunk
var lo = e => !!e && typeof e === 'object' && !Array.isArray(e);
function uo(e){ if (typeof e === 'string' && e) return e;
if (lo(e)) return Object.values(e).find(e => typeof e === 'string' && e) }Measured both directions, same map, order swapped:
| authored | viewer | rendered |
|---|---|---|
{ en: …, 'zh-CN': … } |
zh-CN |
English |
{ 'zh-CN': …, en: … } |
en |
Chinese |
and dropping title renders no axis title at all rather than a translated fallback. ⇒ an author has three options and all three are wrong: hardcode one language, hardcode one language behind a map that also depends on key order, or lose the axis title.
⭐ The part that makes this cheap to fix
The resolver already exists in the same bundle. Two of them, in fact — one walking default → en-US → en → zh-CN, and one walking the exact BCP-47 tag, then the language prefix, then default, then en, before any first-value fallback:
let r = Object.keys(n).find(e => e.split(`-`)[0] === a && …);
return r(i) ?? r(a) ?? (o === void 0 ? void 0 : r(o)) ?? r(`default`) ?? r(`en`)
?? Object.values(n).find(e => typeof e === `string`) ?? ``So this is not "the platform cannot resolve an inline locale map". It resolves them correctly on the filter-label path and on at least one other. The axis path bypasses a resolver two chunks away, and the filter-object path never got a consumer at all. Both are one resolver-family fix.
Why it matters beyond one app
lint:i18n-gate is green at zero and legitimately so — it counts i18n/missing-* findings, and a key nothing reads produces none. So the gate built for exactly this failure mode is blind by construction here: an app can be fully translated by every available measure and still render half-English analytics chrome. In the reference app that meant Sales Rep: 全部 beside 商机类型: 全部 on the most-viewed dashboard, long enough to be baked into a published marketing screenshot.
What the reference app did in the meantime
hotcrm PR objectstack-ai/hotcrm#1851 writes an inline locale map on every globalFilters[].label and static option label across five dashboards (the path that does resolve), leaves all 18 axis titles as plain English, and records the measurement in src/dashboards/index.ts so the next author does not repeat it. ⛔ It did not work around the axis half — there is no correct workaround, only three wrong ones.
Suggested acceptance
- The filter-definition builder copies the authored
objectthrough, and the filter label / option-label path resolvesfields.<object>.<field>when it is present — i.e. spec:GlobalFilterSchemaneeds an optionalobject— a dashboard global filter's field label (and its option labels) have no bundle key to resolve against #7804's stated contract, which is currently prose. - The axis-title normaliser resolves an inline locale map against the active locale using the resolver already in the bundle, instead of
Object.values(...).find(...). - Either would be provable by a rendering test at two locales, since a schema-level test cannot see this class at all — that is precisely how it shipped.
Refs: #7804 (the spec half, closed) · #5428 · #5141 · #5076 · #16773 · #16167 · objectstack-ai/hotcrm#1822 · objectstack-ai/hotcrm#1851
Blocked-by: #17250
Generated by Claude Code
Activity
os-litant commentedon Sep 10, 2026
Triage: the filer's
repo:objectuistands; addingpm:blockedon #17250 +priority:p2+ type Bug. ⛔ Nodomain:*— objectui's triage produces that after the move.Two dashboard i18n surfaces declared translatable and not resolved:
GlobalFilterSchema.object(#7804) is never read, andChartAxisSchema.titlebypasses a locale resolver in its own bundle.⇒ p2: a key declared translatable that is never resolved means every non-
endeployment silently shows English, with nothing to notice — the same silent-locale-drop shape as objectstack#17344 and objectui#8943 in this round. ⭐ Three instances across two repos this round makes it a class worth naming, not three coincidences.⭐ Measured in a real browser against the pinned
@objectstack/console@17.4.0, ⛔ not read off a docblock — that is the right instrument for a rendering claim, and the backlog was searched before filing.GlobalFilterSchemaorChartAxisSchemathemselves, that ispackages/specand a separate card in this repo — ⛔ do not edit spec from an objectui PR.Blocked-by: objectstack-ai/objectstack#17250appended to the body. A seat cannot transfer an issue in this container (measured), and #17250'sMaintainer-action:line re-readslabel:repo:objectuibefore acting, so a card added now is picked up.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T15:00Z · 本评论来自分诊座位Generated by Claude Code