Skip to content

i18n: metadata label lookup falls through to the en bundle on a zh-CN workspace — localeChain defaults fallbackChain to ['en'] and ignores i18n.fallbackLocale, so an authored Chinese label loses to a courtesy English bundle #14882

Description

@baozhoutao

Summary

For an app whose metadata labels are authored in the workspace default locale (zh-CN, inline label: '填报单') and which ships only an en translation bundle as a courtesy for English users, the metadata API serves the English bundle labels to a zh-CN request. The authored Chinese label never wins because the label lookup's fallback chain is hard-wired to ['en'], independent of the stack's i18n.fallbackLocale.

The effect on screen: object title Entry Sheet, columns Sheet / Plan / Subject / Status / Final Score, app name KPI Assessment — sitting next to fields that have no en entry and therefore correctly show 主体类型 / 填报说明, and next to platform chrome that is fully Chinese. A zh-CN deployment cannot ship an en bundle without breaking its own default language.

Minimal reproduction

Platform 17.2.0, objectstack dev, SQLite, single tenancy, fresh DB.

objectstack.config.ts:

i18n: { defaultLocale: 'zh-CN', supportedLocales: ['zh-CN', 'en'], fallbackLocale: 'zh-CN' },
translations: [defineTranslationBundle({
  en: { objects: { kpi_entry_sheet: { label: 'Entry Sheet', pluralLabel: 'Entry Sheets',
        fields: { name: { label: 'Sheet' }, status: { label: 'Status' }, total_score: { label: 'Final Score' } } } } },
})],

kpi_entry_sheet object: label: '填报单', fields name: Field.text({ label: '填报单名称' }), status: Field.select({ label: '状态' }), total_score: Field.number({ label: '最终得分' }), subject_type: Field.select({ label: '主体类型' }) (no en entry for subject_type).

GET /api/v1/i18n/locales
→ {"locales":[{"code":"zh-CN","isDefault":true},{"code":"en","isDefault":false}]}

GET /api/v1/meta/object/kpi_entry_sheet   (Accept-Language: zh-CN, signed in as admin)
→ item.label        = "Entry Sheet"     ← expected 填报单
  item.pluralLabel  = "Entry Sheets"
  fields.name       = "Sheet"           ← expected 填报单名称
  fields.status     = "Status"          ← expected 状态
  fields.total_score= "Final Score"     ← expected 最终得分
  fields.subject_type = "主体类型"       (no en entry → authored label survives)
  fields.created_at = "创建时间"         (platform zh-CN pack → correct)

GET /api/v1/meta/object/kpi_entry_sheet   (Accept-Language: en)
→ identical for the app fields; created_at = "Created At"

GET /api/v1/meta/app/kpi_app               (Accept-Language: zh-CN)
→ label = "KPI Assessment"                ← authored label is 'KPI 考核管理'

Same request with no Accept-Language header: same English result (the workspace default is zh-CN).

Control: remove the en bundle → every label is Chinese. So the presence of a bundle for a non-requested locale changes what the requested locale sees.

Where

@objectstack/spec system — the label resolvers (lookupObjectField, lookupObjectFieldAttr, lookupAppAttr, resolveViewLabel, lookupTabLabel, …) all iterate localeChain(opts):

function localeChain(opts) {
  const locale = opts?.locale ?? "en";
  const fallbacks = opts?.fallbackChain ?? ["en"];   // ← hard-wired
  ...
  for (const code of [locale, ...fallbacks]) ...
}

and return the first bundle hit before the caller falls back to the authored label. The server-side caller does not pass a fallbackChain, so i18n.fallbackLocale: 'zh-CN' has no effect on the chain: zh-CN → en → authored. For any object the zh-CN bundle does not mention (i.e. every object whose labels are authored in Chinese), the en bundle is consulted before the authored label.

Expected

One of (in order of preference):

  1. The chain honours the stack's i18n.fallbackLocale / defaultLocale instead of a literal ['en'], so a zh-CN workspace resolves zh-CN → (fallbackLocale) → authored label, and en is only consulted when en is requested.
  2. When the requested locale is the workspace default locale, the authored label wins over any other locale's bundle (the authored label is the default-locale text — that is what defaultLocale declares).
  3. Failing both, the metadata API should at least not report locale: zh-CN while serving en strings; the i18n coverage tooling should flag "label served from a locale other than the requested/default one".

Side effects seen in the app (for triage, same root cause)

  • Related lists show what looks like a duplicate column pair 指标 / Indicator: indicator (lookup, no en entry → authored 指标) next to indicator_name (has en entry → Indicator). With the fix both read Chinese (指标 / 指标名称).
  • Record page title suffix (客户满意度 Indicator), breadcrumb (Indicator, Assessment Plan), related-list section headings (Entry Line 0, Indicator 0) all flip to English on a zh-CN page.

Environment

@objectstack/* 17.2.0 (runtime, spec, cli, console 17.2.0) · Node 22 · better-sqlite3 · single tenancy · objectstack dev. App: objectstack-ai/kpi (private) — the config above is a faithful excerpt.

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions