Skip to content

Compose chart axis labels with the locale, not with string glue - #83

Merged
DennisAlund merged 4 commits into
mainfrom
fix/chart-axis-locale
Nov 3, 2026
Merged

DennisAlund merged 4 commits into
mainfrom
fix/chart-axis-locale

Conversation

@DennisAlund

@DennisAlund DennisAlund commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Takes over #77 by @myml, with the review findings on it addressed. The first commit is #77's commit cherry-picked unchanged, authored by the contributor.

What #77 fixed

The x-axis glued a translated month name onto a day number. zh read 7月 16 for a day and 7月 26 for July 2026, and sv and id got the month first. fmtLabel now formats through Intl.DateTimeFormat, and monthly labels carry the full year.

Review findings addressed

Finding Fix
Hourly label still glued HH:00 and keyed off range Hourly labels use Intl, matching the tooltip (09:00 AM in en, 09.00 in id). The label's shape picks the format. fmtLabel takes no range.
Daily/weekly labels omit the year across a year boundary axisLabels() adds the year on the first label and wherever the year changes, only when the axis crosses a year.
Longer monthly labels collide with Today axisLabels() estimates label widths (0.6em Latin, 1em CJK, 9px mono) and drops the neighbor that would overlap Today.
month.* keys dead Removed from en, id, sv and zh.
Locale sweep hardcodes languages Tests iterate SUPPORTED_LANGUAGES.
Weekly test cannot fail Asserts a weekly label's month and day.
Bucket parsing duplicated bucketDate() serves both the axis and the tooltip. The server-side formatBucketLabel stays separate: the client ships as a generated script string and cannot import it.
Formatter rebuilt per label BUCKET_FORMATS built once per page, like numberFormatter.
Loader inside the inner loop Client script compiled once per language.
Comment breaks writing rules Rewritten.

Second review round

Addressed in 0c810c6: invalid bucket labels no longer throw, emphasized dots follow the kept labels, the label cadence counts back from today, the axis hour uses the locale's own form, the formatters build on first use, and AXIS_FONT_SIZE feeds the width estimate. The tests now run the real renderTimeline() with each locale's translation table.

Tests

  • yarn test: 109 files, 1701 tests pass.
  • tsc --noEmit: clean.
  • yarn e2e: 38 pass.
  • Mutation check: undoing the Invalid Date guard, the today-anchored cadence, the numeric hour, the regular-label drop, the dot alignment or the lazy formatters each fails client-chart-labels.test.ts.
  • Browser check: the link detail chart in en, id, sv and zh across 24h, 7d, 30d and 1y. Dots match labels, year marks appear at the 2025/2026 turn, the console stays clean.

Supersedes #77.

myml and others added 3 commits November 2, 2026 15:41
The x-axis composed its label by gluing a translated month name onto a day
number, which hardcodes a Latin month-first shape and a space separator. A
lang=zh viewer read "7月 16" for a day bucket, where zh wants 7月16日, and
"7月 26" for a monthly bucket, which reads as the 26th of July rather than
July 2026. The same glue put the month first for sv and id, whose locales
order the day first ("16 juli", "16 Jul").

fmtLabel now builds a UTC Date from the bucket label and formats it with
Intl.DateTimeFormat, the call the tooltip formatter (fmtBucketDate) and the
server-side formatBucketLabel already make. The month branch carries a full
year: a two-digit year is not an option, because zh ICU renders it
"26年7月", which reads as the year 26.

The shape of the label now picks the branch, not the range, which drops the
range list that had to agree with the bucketing in click-repository.ts. The
month.* translation keys are left in place: the client no longer reads them,
but zh.ts is in flight in another change, and dropping keys from one locale
table at a time fails the key-parity test.

New client-chart-labels.test.ts extracts the shipped function from
adminClientScript() output and runs it per language. The chunk is evaluated
with no t() in scope, so a return to the glue throws instead of rendering a
wrong label. Mutating the year option or dropping the day option fails the
suite.
Follow-up to the review of #77, whose locale-aware day and month labels
this builds on.

- Hourly labels glued "HH:00" and keyed off range === '24h'. They now come
  from Intl like the tooltip, so en reads "09:00 AM" on both and id
  "09.00". The label's shape picks the format; fmtLabel takes no range.
- A daily or weekly axis that crosses a year boundary printed "Oct 20" for
  2025 and "Oct 5" for 2026 with nothing to tell them apart. axisLabels()
  now adds the year to the first label and wherever the year changes.
- Full-year monthly labels ("Sep 2026", "2026年9月", "sep. 2026") overlapped
  the final "Today" label 30 units away. axisLabels() estimates label
  widths for the 9px monospace axis and drops the neighbor that would
  collide with today.
- bucketDate() parses a bucket label once for the axis and the tooltip.
  The server-side formatBucketLabel stays separate: the client ships as a
  generated script string and cannot import it.
- The Intl formatters are built once per page, like numberFormatter.

Tests iterate SUPPORTED_LANGUAGES, load the client script once per
language, and replace the weekly-vs-daily comparison that could not fail
with assertions on the weekly label's month and day.
The chart axis formats month names with Intl now, so nothing reads the
twelve month.* keys. They still shipped in every admin page's translation
table, and translators kept maintaining names the UI never showed. #71 has
landed, so all four locales drop them together and the key-parity test in
i18n.test.ts stays green.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
shrtnr 0c810c6 Nov 02 2026, 01:06 PM

@DennisAlund DennisAlund left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review (xhigh). 12 findings, inline below. No high-severity bug: the year marks and the today guard behave as described for en, id, sv and zh across every range shape, including New Year crossings. The two worth fixing first: Intl.DateTimeFormat#format throws on an Invalid Date where the old path degraded to text, and labels dropped by the collision guard keep their emphasized dot.

Checked by running the shipped axisLabels under Node for every range shape and language. client-chart-labels.test.ts and i18n.test.ts pass (42 tests).

Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Comment thread src/client.ts Resolved
Addresses the review on #83.

- Intl.DateTimeFormat#format throws on an invalid date, where the old
  toLocaleDateString path printed "Invalid Date". bucketDate() now returns
  null for a label in no known shape, and the formatters show it as it
  arrived, so the rest of the analytics panel still renders.
- The label cadence counts back from the final bucket, so today always
  sits a full interval from its neighbor. The overlap guard stays for wide
  year labels, such as zh "2025年12月20日" beside "12月21日".
- Emphasized dots follow the kept labels. A label dropped for overlap used
  to leave its dot behind.
- The axis hour uses hour: 'numeric' ("1:00 PM", "9.00"). The tooltip keeps
  2-digit to match the bundle chart's server-rendered tooltip, which
  big-chart.test.ts pins.
- The date formatters are built on first use, so admin pages without a
  chart skip them. AXIS_FONT_SIZE feeds both the <text> elements and the
  width estimate.
- axisLabels reads the x positions from pts instead of a copied array.

The tests now run the real renderTimeline() with each locale's translation
table and read the SVG. That drops the copied chart geometry and checks
each locale's real today text. It also covers the regular-label drop
branch, the dot alignment and the lazy formatters.
@DennisAlund
DennisAlund merged commit 57365e5 into main Nov 3, 2026
10 checks passed
@DennisAlund
DennisAlund deleted the fix/chart-axis-locale branch November 3, 2026 01:30
DennisAlund added a commit that referenced this pull request Nov 3, 2026
…tches

Patch release for the fixes merged since 0.43.0: the chart axis formats
labels with Intl instead of string glue (#83), the admin's Chinese pages
declare lang="zh-Hans" (#84), and hono and zod pick up published
advisory fixes (#82) alongside the dependency upgrade (#81).

The spec embeds info.version, so the bump refreshes x-spec-hash in all
three SDKs. The emitted spec differs from 0.43.0 in info.version only:
spec change does not affect SDK surface; bump hash to record review.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants