fix(plugin-timeline): gantt bucket labels and the Items default speak the session language (#4520) - #4532
Merged
Conversation
… the session language (#4520) objectui#4513 routed every `Intl` call in the timeline renderer through `useDisplayLocale()`, so a zh session renders `2026年8月` on the month axis. Three sibling strings in the same renderer never went through `Intl` at all and stayed English beside it: the `week` header (`Week 1`), the `quarter` header (`Q3 2026`), and the gantt row-label default (`Items`). These are a translation concern, not a locale-resolver one — a locale TAG formats a date, only a TRANSLATION spells a word. All three now resolve through the package's existing channel (`useTimelineTranslation` / `TIMELINE_DEFAULT_TRANSLATIONS`, already used for `timeline.bucket.*`) under three new keys carried by all ten locale packs: `timeline.scale.week`, `timeline.scale.quarter`, `timeline.gantt.rowLabel`. The numbers ride the channel's own `{{hole}}` parameters rather than concatenation, because word order belongs to the translation: zh puts the year first (`2026年第3季度`), which no `Q${q} ${year}` template can produce. `generateTimeScaleHeaders` is a pure exported function and cannot host a hook, so `t` is threaded in as an optional 5th parameter on the seam #4513 opened for `locale`, defaulting to the package's own defaults table. English output is byte-identical and existing 3-/4-argument call sites are unaffected. #4513's suite deliberately pinned `Week 1` / `Q3 2026` staying English to keep that card's scope honest; this card moves exactly those two expectations. The year-only `2026` header is a bare number and stays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Collaborator
Author
|
PM step-7 复核 — ACCEPT (session_017Qqyix2QcnpUC9XeYVDzx3)
Auto-merge armed (squash). Generated by Claude Code Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4520
The measured defect
objectui#4513 (PR #4523, landed as
01c918807) routed everyIntlcall inpackages/plugin-timeline/src/renderer.tsxthroughuseDisplayLocale(), so azhsession renders2026年8月on the month axis and2026年8月11日on item dates. Three sibling strings in the same renderer never went throughIntlat all, so they stayed English on that same Chinese axis:weekheaderWeek 1第 1 周quarterheaderQ3 20262026年第3季度Items条目Verbatim red-first evidence, before the fix:
The half-fixed state is the visible one: a Chinese date axis with English bucket labels beside it.
The channel — measured, not invented
The package already owns one:
useTimelineTranslation/TIMELINE_DEFAULT_TRANSLATIONSinsrc/useTimelineTranslation.ts, acreateSafeTranslationfactory thatObjectTimeline.tsxalready uses fortimeline.bucket.*. No new shape was introduced and no sibling plugin's pattern needed mirroring. Three keys were added there and to all ten locale packs, nested beside the existingtimeline.bucket.*:timeline.scale.week— enWeek {{n}}timeline.scale.quarter— enQ{{quarter}} {{year}}timeline.gantt.rowLabel— enItemsExisting spellings were reused where the packs had them (
gantt.viewMode.quarter,第 {{n}} 轮), each pack in its own quoting convention.Interpolation goes through the channel's own
{{hole}}parameter mechanism, never string concatenation, because word order belongs to the translation:zhputs the year first, which noQ${q} ${year}template can produce at all. A test drives a range across a year boundary specifically so a faked interpolation cannot pass.The seam
generateTimeScaleHeadersis a pure exported function and cannot host a hook, sotis threaded in as an optional fifth parameter — the same seam #4513 opened for the resolvedlocalestring, for the same reason. Its default is the package's own defaults table, which is exactly what the channel serves with noI18nProvidermounted, so existing three- and four-argument call sites keep producing byte-identical English (the #2942 spec-parity pins and #4513's fallback pins stay green untouched).Authorized pin move (declared)
#4513's suite deliberately pinned
Week 1/Q3 2026staying English in azhsession, to keep that card's scope honest, and filed the English as this issue. This card moves exactly those two expectations, inpackages/plugin-timeline/src/__tests__/timeline-date-locale.test.tsx, casethe locale-free header vocabularies do not go through Intl — zh:expect(week.container.textContent).toContain('Week 1')becomes'第 1 周'expect(quarter.container.textContent).toContain('Q3 2026')becomes'2026年第3季度'The
yearexpectation ('2026') is unchanged —String(getFullYear())is a bare number with no vocabulary in it. What the case asserts is unchanged too: threading a locale did not touch these, and a locale tag is still not what spells them. Nothing else in the #4513 suite was touched.Two channels, and why they may disagree
Worth stating because it reads like a bug and is not. Dates resolve through
useDisplayLocale()(tenant regional default first — how this org writes dates); vocabulary resolves through the translate channel (the UI language — what this user reads). A tenant configuredenwhose user reads Chinese chrome rendersAug 2026beside第 1 周. That is the repo's established split —timeline.bucket.*has always behaved this way — and #4513's tenant-over-UI precedence for dates is pinned unchanged here.Must-not-change, all pinned and green both sides
en output byte-identical everywhere;
dateFormat:'iso'untouched; tenant-over-UI-language precedence for dates unchanged; theyearbucket still a bare number; an author-suppliedrowLabelstill wins over the default; titles and descriptions unchanged.Per the #4514 trap, the provider-mounting cases and the provider-less ones are in separate files:
timeline-scale-vocabulary.test.tsxmounts anI18nProviderin every case, andtimeline-scale-vocabulary-defaults.test.tscontains no React at all.Verification
pnpm exec vitest run --maxWorkers=2 packages/plugin-timeline packages/i18n/src/__tests__— 52 files, 866 tests, all passing (includes the ten-pack parity tests and the moved pins).type-check(bothtscpasses) green for@object-ui/plugin-timelineand@object-ui/i18n.check:i18n-keys,check:i18n-drift,check:control-bytes,check:phantom-deps,changeset:check— all green. The i18n gate confirms every call site passes exactly the arguments itsenvalue has holes for.as anyfixture convention as its plugin-timeline hardcodes 'en-US' at four date sites, so timeline headers stay English on every non-English session #4513 sibling..d.tsdiff measured both ways (dist and tsbuildinfo cleared between builds): purely additive —generateTimeScaleHeadersgains an optional parameter, i18n gains the three keys, zero removals. Graded minor for both packages per the entry-reachable additive precedent (fix(plugin-timeline): dates follow the active locale instead of hardcoded en-US (#4513) #4523).git diff+git checkout --(nevergit stash); restore confirmed byte-exact by sha256 ofgit diffagainst the saved patch.Generated by Claude Code