Skip to content

analytics: chart category labels drawn from a select field's options render untranslated on a SAME-object dataset dimension, while the identical values translate on a dotted cross-object one #16773

Description

@os-zhuang

Split off #16735 (finding 4) by the triage seat. It is its own card because — unlike findings 1–3 — it has no workaround, and because triage's own reading puts its mechanism in packages/services/service-analytics, a different lane from its siblings.

Original provenance: filed from an app repo (objectstack-ai/hotclm, @objectstack/* 17.3.0, @objectstack/console as shipped) after translating it end to end into en + zh-CNobjectstack-ai/hotclm#32, PR objectstack-ai/hotclm#33.

The finding

On dataset-backed charts, category and series labels drawn from a select field's option values render the inline English option label, while the identical values render the translation in the list grid on the same console.

The correlation is exact and is the useful part:

dataset dimension declared as renders
contract_metrics.direction field: 'direction' (same object) Purchase / Sales
payment_metrics.direction field: 'contract.direction' (dotted, cross-object) 采购 / 销售 ✅

Same option values, same console, same session. Also affects a status funnel (Draft/Submitted/In Review/…) and a currency_code bar (EUR — Euro).

A widget has no authoring surface for category labels, so unlike findings 1–3 this one cannot be worked around. It is the residue left visible on two of that app's three dashboards.

⭐ Triage's own reading — a mechanism the card did not have

The same-object dimension's labels are resolved by packages/services/service-analytics/src/dimension-labels.ts, which reads the option label straight out of the field metadata:

packages/services/service-analytics/src/dimension-labels.ts:32
  /** Select options — the value→label source. */
:33   options?: Array<{ value: unknown; label?: string }>;
:146      if (Array.isArray(meta.options) && meta.options.length > 0) {
:148        for (const opt of meta.options) {
:312      if (Array.isArray(meta.options) && meta.options.length > 0) {
:314        for (const opt of meta.options) {

That file consults no translation bundle at all. Measured on origin/main:

grep -n 'i18n|translat|locale|bundle'  dimension-labels.ts                    -> 0
control, same package, non-test sources                                       -> 9 files
  (analytics-service.ts, cube-registry.ts, like-pattern.ts, plugin.ts,
   preview-evaluator.ts, read-scope-sql.ts, native-sql-strategy.ts,
   objectql-strategy.ts, text-match-sql.ts)

⇒ the zero is a reading, not a silence: i18n vocabulary demonstrably reaches this package, and this one file has none of it. So the same-object arm returns opt.label verbatim — the authored English — with nothing in the path that could ever translate it.

⚠️ Not verified by triage: why the dotted cross-object arm works. The hypothesis is that a dotted dimension resolves through the object-metadata pipeline, which is translated, so it never reaches this file's option branch. That would explain the correlation exactly, but it is a hypothesis — ⛔ do not build on it without measuring. Establishing which path the dotted arm takes is the first step of this card, because it also names where the fix belongs: teaching this file the bundle, or routing the same-object arm through whatever the dotted arm already uses.

⭐ Prefer the second if it is available. A second copy of "how to translate a select option" is the multi-copy failure mode this repo keeps burning down.

Relationship to #16390 — adjacent, ⛔ neither subsumes the other

#16390 (domain:services, Bug, p2) is about the same file and the same LOOKUP_TYPES neighbourhood: there, user and tree dimensions are not label-resolved at all because LOOKUP_TYPES is a two-member subset of the spec's four-member REFERENCE_VALUE_TYPES.

Different axis:

Same file, same lane, no overlap in the change. ⇒ schedule them in the same window so two PRs do not collide on dimension-labels.ts; ⛔ do not merge the cards.

Etiquette note if both land together

#16390's acceptance requires widening LOOKUP_TYPES and the #3602 read-scope resolution together. If this card lands first and teaches the file a bundle, that bundle plumbing must be carried into the widened arms too, or user/tree labels arrive untranslated the day #16390 lands.

Acceptance

  • A same-object select dimension on a dataset-backed chart renders its option labels in the console's locale, matching what the list grid renders for the identical values.
  • The dotted cross-object arm is unchanged (it works today — it is the control).
  • Whatever mechanism is chosen, there is one copy of "translate a select option label", not two.

Provenance and boundaries

  • ⚠️ Measured in a browser by the reporting app's implementation run; the filer states plainly "I did not personally re-drive these four." They also record that a fifth finding from the same run was independently re-measured and came back inverted (record:details: the #13855 sections[].group reference form crashes the renderer, and the enumerated fields form renders raw field keys instead of declared labels #16695).
  • ⚠️ Triage independently verified only the code reading above (dimension-labels.ts reads meta.options[].label and consults no bundle, with a firing control). The browser observations, the object names and the counts are not triage's readings.
  • ⛔ Dedup could not be run from the reporting session: search_issues returned total_count: 0 for the app's own repo even on a control term certainly present in an open issue's title. ⚠️ Re-run the duplicate search at claim.
  • One app, one console build, one dialect. Re-confirm before acting.

Split siblings

https://claude.ai/code/session_01SwJQDFKe8tVit3BXQ9EfR5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions