[PR #501] [MERGED] fix: use AsyncLocalStorage for per-request i18n locale state #623

Closed
opened 2026-05-06 13:09:09 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/501
Author: @NathanDrake2406
Created: 3/12/2026
Status: Merged
Merged: 3/12/2026
Merged by: @james-elicx

Base: mainHead: fix/i18n-locale-als


📝 Commits (1)

  • 33969ed fix: use AsyncLocalStorage for per-request i18n locale state

📊 Changes

8 files changed (+944 additions, -660 deletions)

View changed files

📝 packages/vinext/src/entries/pages-server-entry.ts (+17 -11)
📝 packages/vinext/src/index.ts (+2 -0)
📝 packages/vinext/src/server/dev-server.ts (+656 -625)
packages/vinext/src/shims/i18n-context.ts (+74 -0)
packages/vinext/src/shims/i18n-state.ts (+65 -0)
📝 packages/vinext/src/shims/link.tsx (+4 -3)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+17 -11)
📝 tests/link.test.ts (+109 -10)

📄 Description

Summary

  • Per-request i18n locale context (locale, defaultLocale, domainLocales, hostname) was stored on bare globalThis properties shared across concurrent requests
  • Under concurrent traffic (Cloudflare Workers or Node.js production), request A's locale could be overwritten by request B before A finishes rendering, causing <Link> components to produce incorrect locale-prefixed URLs
  • Introduces ALS-backed i18n state following the established router-state.ts / head-state.ts pattern:
    • i18n-context.ts: bridge module with fallback globalThis accessors and a registration API (safe for both client and server import)
    • i18n-state.ts: server-only module that registers ALS-backed accessors on import, providing per-request isolation
  • link.tsx now reads locale context via getI18nContext() instead of bare globalThis
  • Dev server uses ssrLoadModule("vinext/i18n-context") to set context on the SSR module instance (matching the existing setSSRContext pattern)
  • Production entry wraps rendering in runWithI18nState() and calls setI18nContext()

Test plan

  • Updated existing SSR locale domain test to use setI18nContext API
  • Added concurrent ALS isolation test: two simultaneous renders with different domain locale configs produce correct cross-domain hrefs independently
  • Added concurrent ALS isolation test: two simultaneous renders with different defaultLocale values produce correct locale prefixes (non-default gets prefix, default does not)
  • All 1015 tests pass across affected files (link.test.ts, features.test.ts, shims.test.ts, entry-templates.test.ts, safe-json.test.ts)
  • Typecheck and lint clean

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/501 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 3/12/2026 **Status:** ✅ Merged **Merged:** 3/12/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/i18n-locale-als` --- ### 📝 Commits (1) - [`33969ed`](https://github.com/cloudflare/vinext/commit/33969edeeeacdb295f37b21a6b7737cdd7d082c4) fix: use AsyncLocalStorage for per-request i18n locale state ### 📊 Changes **8 files changed** (+944 additions, -660 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/pages-server-entry.ts` (+17 -11) 📝 `packages/vinext/src/index.ts` (+2 -0) 📝 `packages/vinext/src/server/dev-server.ts` (+656 -625) ➕ `packages/vinext/src/shims/i18n-context.ts` (+74 -0) ➕ `packages/vinext/src/shims/i18n-state.ts` (+65 -0) 📝 `packages/vinext/src/shims/link.tsx` (+4 -3) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+17 -11) 📝 `tests/link.test.ts` (+109 -10) </details> ### 📄 Description ## Summary - Per-request i18n locale context (`locale`, `defaultLocale`, `domainLocales`, `hostname`) was stored on bare `globalThis` properties shared across concurrent requests - Under concurrent traffic (Cloudflare Workers or Node.js production), request A's locale could be overwritten by request B before A finishes rendering, causing `<Link>` components to produce incorrect locale-prefixed URLs - Introduces ALS-backed i18n state following the established `router-state.ts` / `head-state.ts` pattern: - **`i18n-context.ts`**: bridge module with fallback `globalThis` accessors and a registration API (safe for both client and server import) - **`i18n-state.ts`**: server-only module that registers ALS-backed accessors on import, providing per-request isolation - `link.tsx` now reads locale context via `getI18nContext()` instead of bare `globalThis` - Dev server uses `ssrLoadModule("vinext/i18n-context")` to set context on the SSR module instance (matching the existing `setSSRContext` pattern) - Production entry wraps rendering in `runWithI18nState()` and calls `setI18nContext()` ## Test plan - [x] Updated existing SSR locale domain test to use `setI18nContext` API - [x] Added concurrent ALS isolation test: two simultaneous renders with different domain locale configs produce correct cross-domain hrefs independently - [x] Added concurrent ALS isolation test: two simultaneous renders with different `defaultLocale` values produce correct locale prefixes (non-default gets prefix, default does not) - [x] All 1015 tests pass across affected files (`link.test.ts`, `features.test.ts`, `shims.test.ts`, `entry-templates.test.ts`, `safe-json.test.ts`) - [x] Typecheck and lint clean --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:09:09 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#623
No description provided.