Skip to content

fix(i18n): format roadmap dates with the Intl locale, not the plugin resource tag - #25021

Closed
OrcaWin wants to merge 1 commit into
mainfrom
fix/plugin-locale-intl-formatting
Closed

OrcaWin wants to merge 1 commit into
mainfrom
fix/plugin-locale-intl-formatting

Conversation

@OrcaWin

@OrcaWin OrcaWin commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​197 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​197
Prod 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2 0

ELI5

The Tasks page shows GitHub Project roadmaps with date labels. To format those dates, the code asked "what language is the app in?" and passed the answer to the browser's date formatter. When you install a community language pack (for example Portuguese), the app's internal answer is a long made-up id like plugin0070006c..., not a real language code like pt-BR. The date formatter doesn't understand that id, throws an error, and the whole Tasks page crashes. The fix asks the app's existing "what real locale should dates use?" function instead, which returns pt-BR (or English if that can't be used).

What Changed

Before: With a plugin language pack active (seen in the field on Windows with the Portuguese pack), opening a GitHub Project roadmap view on the Tasks page crashed the page into the error boundary with RangeError: Invalid language tag: plugin....

After: The roadmap renders, and its date ticks are formatted in the pack's declared locale (pt-BR). If the runtime can't format that locale, it falls back to the default locale (en).

Mechanism: ProjectRoadmap.tsx:51 used i18n.resolvedLanguage ?? i18n.language as the Intl locale. With a plugin pack, that value is the synthetic i18next resource id (pluginLanguageResourceId("plugin:stablyai.orca-portuguese/pt-BR")), which new Intl.DateTimeFormat() rejects. It now calls the existing getIntlLocale() from src/renderer/src/i18n/i18n.ts. That function maps the plugin resource id to the pack's locale and checks it with Intl.DateTimeFormat.supportedLocalesOf, falling back to DEFAULT_LOCALE. Other renderer formatters (relative time, weekday names, chat timestamps, the branch line total chip) already use it.

Guard: New AST test src/renderer/src/i18n/no-raw-language-intl-locale.test.ts. It fails when i18n.language / i18n.resolvedLanguage is passed straight to an Intl.* constructor or a toLocale* / localeCompare call, or stored in a variable named locale.

Why

  • I reused the canonical resolver rather than writing a ProjectRoadmap-specific mapping, so this path behaves like every other date formatter in the app.
  • I also swept the renderer. Every other Intl / toLocale* / Collator / localeCompare call site already uses getIntlLocale(), passes undefined, or hardcodes 'en'. The remaining reads of i18n.language are only cache or memo keys. ProjectRoadmap was the only call site still crashing.
  • Related but different: fix(plugins): reject locale ids Intl cannot parse #12533 validates the locale a pack declares. It does not cover the synthetic plugin<hex> resource id that reached Intl here.

Linked Issue

Field crash report from the Orca crash channel (report 20a1bb20-039d-4f3d-9ada-e4781be5b603, v1.4.219, Windows). No GitHub issue.

Fix evidence

Field crash (v1.4.219, win32 10.0.26200 x64):

- boundary_id: page.tasks
- error_name: RangeError
- error_stack: RangeError: Invalid language tag: plugin0070006c007500670069006e003a0073007400610062006c007900610069002e006f007200630061002d0070006f00720074007500670075006500730065002f00700074002d00420052
    at new DateTimeFormat (<anonymous>)
    at vx (file:///C:[redacted-path])
    at yx (file:///C:[redacted-path])
    at file:///C:[redacted-path]
    at Array.map (<anonymous>)
    at Fx (file:///C:[redacted-path])

The hex decodes to plugin:stablyai.orca-portuguese/pt-BR, which is the plugin resource id.

Before the fix (ProjectRoadmap.tsx checked out from origin/main, new tests kept):

     × never feeds the raw i18n language to Intl 202ms
     × formats ticks with the pack locale instead of the synthetic resource tag 17ms
 FAIL  src/renderer/src/i18n/no-raw-language-intl-locale.test.ts > Intl locale source > never feeds the raw i18n language to Intl
+   "src/renderer/src/components/github-project/ProjectRoadmap.tsx:51",
 FAIL  src/renderer/src/components/github-project/ProjectRoadmap.plugin-locale.test.tsx > ProjectRoadmap with a plugin language pack active > formats ticks with the pack locale instead of the synthetic resource tag
RangeError: Invalid language tag: plugin0070006c007500670069006e003a0073007400610062006c007900610069002e006f007200630061002d0070006f00720074007500670075006500730065002f00700074002d00420052
 ❯ src/renderer/src/components/github-project/ProjectRoadmap.tsx:333:39
 ❯ RoadmapHeaderRow src/renderer/src/components/github-project/ProjectRoadmap.tsx:332:16
 Test Files  2 failed (2)
      Tests  2 failed (2)

The render test reproduces the exact field error string.

After the fix (this branch):

 Test Files  2 passed (2)
      Tests  2 passed (2)

Visual Proof

N/A. This is a crash fix. Without a plugin pack, the UI looks the same. With one, the roadmap now renders instead of the error boundary. The render test above shows this.

Testing

  • pnpm test src/renderer/src/components/github-project/ProjectRoadmap.plugin-locale.test.tsx src/renderer/src/i18n/no-raw-language-intl-locale.test.ts: passes on this branch and fails with the fix reverted (output above).

  • oxlint on the 3 changed files: exit 0. pnpm tc:web: passes.

  • Platforms: run on macOS (vitest/jsdom). The field crash was on Windows, and the code path doesn't depend on the platform.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Trade-offs

  • getIntlLocale() now runs on every ProjectRoadmap render: one supportedLocalesOf call plus a short search of the installed packs. The cost is negligible, and other components already do the same on every render.
  • One small behavior change for built-in locales: if the runtime has no date data for the selected UI language, roadmap labels now fall back to en instead of whatever the runtime would pick. This matches every other getIntlLocale() caller. en, zh-CN and the pseudo-locale en-XA are unchanged.
  • The guard is a heuristic. It would miss the raw language held in a variable with a different name and passed through props to a formatter.
  • ProjectRoadmap still reads the locale at render time and does not subscribe to language changes, so a mid-session language switch updates the labels on the next re-render. This is unchanged from before.

Review

Adversarial review: 1 loop until clean.

Round 1: CLEAN

  • Fix is correct: ProjectRoadmap.tsx:51 now uses the canonical getIntlLocale(), which maps the plugin resource tag to pack.locale and falls back to DEFAULT_LOCALE when Intl rejects or has no data for the tag (this covers packs with an invalid or unsupported locale field).
  • Checked against the old code: with ProjectRoadmap.tsx restored from origin/main, both new tests fail. The render test throws the exact field error at ProjectRoadmap.tsx:333 in RoadmapHeaderRow, and the guard test flags ProjectRoadmap.tsx:51. Both pass on HEAD.
  • No other Intl call site in the renderer is fed the raw i18n language. NativeChatMessageTimestamp, relative-time-format, weekday-names and branch-line-total-chip already use getIntlLocale(). automation-list-view takes locale as a parameter by design. The other i18n.language reads (activity-standalone-worktree, rich-markdown-code-block-languages, settings navigation, LinearIssueMarkdownDescriptionEditor) only use it as a cache or memo key, never as an Intl argument. The remaining toLocale*() calls pass no locale.
  • Built-in locales: supportedLocalesOf keeps en, zh-CN and the pseudo-locale en-XA unchanged, so behavior matches the other getIntlLocale consumers. Language changes at runtime: locale is read on every render and is not memoized, same as before the fix, and roadmap-tick-format caches formatters per locale.
  • oxlint exits 0 on the 3 changed files and tc:web passes. The only cast is as const. Comments are brief and file names are specific.
  • Minor nit, not changed: the guard's filePath.includes('/i18n/locales/') exclusion uses POSIX separators and has no effect, because the locales folder holds only .json files and the .ts/.tsx filter already skips them.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Security: no impact. Cross-platform: platform-independent renderer code. SSH/remote: renderer-only, no effect. Mobile: not affected. Backwards compatibility: no wire or settings changes. Performance: negligible (see Trade-offs).

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…resource tag

With a plugin language pack active, i18n.language is the synthetic
plugin<hex> resource id, which Intl.DateTimeFormat rejects with a
RangeError. ProjectRoadmap passed it straight to the tick formatter,
crashing the Tasks page. Route it through getIntlLocale() and add an
AST guard against raw i18n language reaching Intl.

Co-Authored-By: Claude <noreply@anthropic.com>
@pullfrog

pullfrog Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

This run hit its time limit while still working, so it stopped before finishing.

Give runs longer with --timeout=2h (or --notimeout) in this repo's instructions — or raise the workflow's timeout input if it sets one, since that input wins. A smaller PR or a lower effort also finishes sooner.

Repo settings → · Flags →

agent run timed out after 1h

Pullfrog  | Rerun failed job ➔ | View workflow run | via Pullfrog | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0d1a9316-3c2c-4d23-88b8-a924076c8762
📥 Commits

Reviewing files that changed from the base of the PR and between 5e65871 and 933cfae.

📒 Files selected for processing (3)
  • src/renderer/src/components/github-project/ProjectRoadmap.plugin-locale.test.tsx
  • src/renderer/src/components/github-project/ProjectRoadmap.tsx
  • src/renderer/src/i18n/no-raw-language-intl-locale.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

ProjectRoadmap now gets its formatting locale from getIntlLocale(). A renderer test checks month formatting with an active pt-BR plugin pack. A new test scans renderer source files for raw i18n or i18next language values passed to locale APIs or assigned to variables named locale.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 933cf

The change addresses the reported plugin-language crash, with no identified issue requiring a fix before merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: using the Intl locale to format roadmap dates instead of the plugin resource tag.
Description check ✅ Passed The description covers the required explanation, changes, rationale, testing, visual proof, review, and compatibility notes. It provides a crash report instead of a GitHub issue link, and the manual-t…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

nwparker added a commit that referenced this pull request Oct 9, 2026
Records the retained adapted contribution from #25021 in replacement PR #26860.

Co-authored-by: m4air <m4air@Mac.localdomain>
@nwparker

Copy link
Copy Markdown
Contributor

Closing this PR as superseded by the merged PR #26860. It retains the same roadmap fix using the existing Intl locale resolver. The regression coverage was reworked into the existing roadmap suite; the broad renderer AST scanner from this PR was not included. The original m4air author identity is preserved in the co-author trailer.

@nwparker nwparker closed this Oct 10, 2026
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