Skip to content

console: #7804's GlobalFilterSchema.object is never read, and ChartAxisSchema.title bypasses a locale resolver in its own bundle — two dashboard i18n surfaces declared translatable and not resolved #17307

Closed as duplicate

Description

@os-steve

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 and field — 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

  1. The filter-definition builder copies the authored object through, and the filter label / option-label path resolves fields.<object>.<field> when it is present — i.e. spec: GlobalFilterSchema needs an optional object — 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.
  2. 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(...).
  3. 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

  1. added
    bugSomething isn't working
    repo:objectuiSeam card: cross-repo ordering with objectui is the substance (pure objectui fixes live in objectui)
    on Sep 10, 2026
  2. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Sep 10, 2026
  3. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    Triage: the filer's repo:objectui stands; adding pm:blocked on #17250 + priority:p2 + type Bug. ⛔ No domain:* — objectui's triage produces that after the move.

    Two dashboard i18n surfaces declared translatable and not resolved: GlobalFilterSchema.object (#7804) is never read, and ChartAxisSchema.title bypasses a locale resolver in its own bundle.

    ⇒ p2: a key declared translatable that is never resolved means every non-en deployment 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.

    ⚠️ Scope split, declared: the resolution half is console/objectui's. ⛔ But if the repair turns out to need a change to GlobalFilterSchema or ChartAxisSchema themselves, that is packages/spec and a separate card in this repo — ⛔ do not edit spec from an objectui PR.

    ⚠️ It joins the pending transfer batch: Blocked-by: objectstack-ai/objectstack#17250 appended to the body. A seat cannot transfer an issue in this container (measured), and #17250's Maintainer-action: line re-reads label:repo:objectui before acting, so a card added now is picked up.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T15:00Z · 本评论来自分诊座位


    Generated by Claude Code

  4. added theissue type on Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingi18npriority:p2Medium: important, M3repo:objectuiSeam card: cross-repo ordering with objectui is the substance (pure objectui fixes live in objectui)

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions