From 4664b344e339107af43bb5936e88a3dab0e61c58 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Fri, 15 Aug 2025 16:24:10 +0800 Subject: [PATCH 01/10] feat: enhance locale handling with AsyncLocalStorage support for server-side requests --- .../request-handler/user-response.ts | 7 ++- packages/qwik/src/core/use/use-locale.ts | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/qwik-router/src/middleware/request-handler/user-response.ts b/packages/qwik-router/src/middleware/request-handler/user-response.ts index f37ca8a8c17..a2445a560a9 100644 --- a/packages/qwik-router/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-router/src/middleware/request-handler/user-response.ts @@ -13,6 +13,7 @@ import { } from './request-event'; import { encoder } from './resolve-request-handlers'; import type { QwikSerializer, ServerRequestEvent, StatusCodes } from './types'; +import { withLocale } from '@qwik.dev/core'; // Import separately to avoid duplicate imports in the vite dev server import { AbortMessage, @@ -64,8 +65,10 @@ export function runQwikRouter( response: responsePromise, requestEv, completion: asyncStore - ? asyncStore.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!) - : runNext(requestEv, rebuildRouteInfo, resolve!), + ? asyncStore.run(requestEv, () => + withLocale(requestEv.locale(), () => runNext(requestEv, rebuildRouteInfo, resolve!)) + ) + : withLocale(requestEv.locale(), () => runNext(requestEv, rebuildRouteInfo, resolve!)), }; } diff --git a/packages/qwik/src/core/use/use-locale.ts b/packages/qwik/src/core/use/use-locale.ts index 5e3efdddabb..90432ff61bf 100644 --- a/packages/qwik/src/core/use/use-locale.ts +++ b/packages/qwik/src/core/use/use-locale.ts @@ -11,6 +11,21 @@ let _locale: string | undefined = undefined; * @public */ export function getLocale(defaultLocale?: string): string { + // Prefer per-request locale from AsyncLocalStorage if available (server-side) + try { + const asyncStore = (globalThis as any).qcAsyncRequestStore; + const ev = asyncStore?.getStore?.(); + const evLocale = ev && typeof ev.locale === 'function' ? ev.locale : undefined; + if (evLocale) { + const l = evLocale(); + if (l) { + return l; + } + } + } catch { + // ignore and fallback + } + if (_locale === undefined) { const ctx = tryGetInvokeContext(); if (ctx && ctx.$locale$) { @@ -30,6 +45,24 @@ export function getLocale(defaultLocale?: string): string { * @public */ export function withLocale(locale: string, fn: () => T): T { + // If running on the server with AsyncLocalStorage, set locale on the current request + try { + const asyncStore = (globalThis as any).qcAsyncRequestStore; + const ev = asyncStore?.getStore?.(); + const evLocale = ev && typeof ev.locale === 'function' ? ev.locale : undefined; + if (evLocale) { + const previous = evLocale(); + try { + evLocale(locale); + return fn(); + } finally { + evLocale(previous); + } + } + } catch { + // ignore and fallback + } + const previousLang = _locale; try { _locale = locale; @@ -48,5 +81,17 @@ export function withLocale(locale: string, fn: () => T): T { * @public */ export function setLocale(locale: string): void { + // On the server, prefer setting the locale on the per-request store + try { + const asyncStore = (globalThis as any).qcAsyncRequestStore; + const ev = asyncStore?.getStore?.(); + const evLocale = ev && typeof ev.locale === 'function' ? ev.locale : undefined; + if (evLocale) { + evLocale(locale); + return; + } + } catch { + // ignore and fallback + } _locale = locale; } From 90890c916349b35cf6988fec6089a2eff939da11 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Fri, 15 Aug 2025 16:43:52 +0800 Subject: [PATCH 02/10] add changeset --- .changeset/pretty-parents-draw.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/pretty-parents-draw.md diff --git a/.changeset/pretty-parents-draw.md b/.changeset/pretty-parents-draw.md new file mode 100644 index 00000000000..5dc3372ef3f --- /dev/null +++ b/.changeset/pretty-parents-draw.md @@ -0,0 +1,6 @@ +--- +'@qwik.dev/router': patch +'@qwik.dev/core': patch +--- + +enhance locale handling with AsyncLocalStorage support for server-side requests From 25d16481246dfcfdf4c421d290a8b58ed87deda6 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Fri, 15 Aug 2025 17:00:50 +0800 Subject: [PATCH 03/10] fix: improve locale handling in resolveHead function with AsyncLocalStorage support --- packages/qwik-router/src/runtime/src/head.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/qwik-router/src/runtime/src/head.ts b/packages/qwik-router/src/runtime/src/head.ts index 6e2136cd43f..39d31bb6ce3 100644 --- a/packages/qwik-router/src/runtime/src/head.ts +++ b/packages/qwik-router/src/runtime/src/head.ts @@ -37,9 +37,10 @@ export const resolveHead = ( } return data; }) as any as ResolveSyncValue; + const storeEv = (globalThis as any).qcAsyncRequestStore?.getStore?.(); const headProps: DocumentHeadProps = { head, - withLocale: (fn) => withLocale(locale, fn), + withLocale: (fn) => (storeEv ? fn() : withLocale(locale, fn)), resolveValue: getData, ...routeLocation, }; @@ -50,7 +51,9 @@ export const resolveHead = ( if (typeof contentModuleHead === 'function') { resolveDocumentHead( head, - withLocale(locale, () => contentModuleHead(headProps)) + storeEv + ? contentModuleHead(headProps) + : withLocale(locale, () => contentModuleHead(headProps)) ); } else if (typeof contentModuleHead === 'object') { resolveDocumentHead(head, contentModuleHead); From 0abc9d8e41a8a69d3642ea8eb12d2fc37c5a208a Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Sat, 16 Aug 2025 09:23:21 +0800 Subject: [PATCH 04/10] refactor: streamline locale handling with AsyncLocalStorage for server-side context --- .../request-handler/user-response.ts | 10 +-- packages/qwik/src/core/use/use-locale.ts | 62 +++++++++++-------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/packages/qwik-router/src/middleware/request-handler/user-response.ts b/packages/qwik-router/src/middleware/request-handler/user-response.ts index a2445a560a9..70c7b954980 100644 --- a/packages/qwik-router/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-router/src/middleware/request-handler/user-response.ts @@ -64,11 +64,11 @@ export function runQwikRouter( return { response: responsePromise, requestEv, - completion: asyncStore - ? asyncStore.run(requestEv, () => - withLocale(requestEv.locale(), () => runNext(requestEv, rebuildRouteInfo, resolve!)) - ) - : withLocale(requestEv.locale(), () => runNext(requestEv, rebuildRouteInfo, resolve!)), + completion: withLocale(requestEv.locale(), () => + asyncStore + ? asyncStore.run(requestEv, () => runNext(requestEv, rebuildRouteInfo, resolve!)) + : runNext(requestEv, rebuildRouteInfo, resolve!) + ), }; } diff --git a/packages/qwik/src/core/use/use-locale.ts b/packages/qwik/src/core/use/use-locale.ts index 90432ff61bf..ca711abfd06 100644 --- a/packages/qwik/src/core/use/use-locale.ts +++ b/packages/qwik/src/core/use/use-locale.ts @@ -1,7 +1,30 @@ import { tryGetInvokeContext } from './use-core'; +import { isServer } from '@qwik.dev/core/build'; let _locale: string | undefined = undefined; +type LocaleStore = { locale: string | undefined }; + +type LocaleAsyncStore = import('node:async_hooks').AsyncLocalStorage; + +let localAsyncStore: LocaleAsyncStore | undefined; + +if (isServer) { + try { + // Lazy import to avoid bundling for non-Node targets + import('node:async_hooks') + .then((module) => { + const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => LocaleAsyncStore; + localAsyncStore = new AsyncLocalStorage(); + }) + .catch(() => { + // ignore if AsyncLocalStorage is not available + }); + } catch { + // ignore and fallback + } +} + /** * Retrieve the current locale. * @@ -11,16 +34,12 @@ let _locale: string | undefined = undefined; * @public */ export function getLocale(defaultLocale?: string): string { - // Prefer per-request locale from AsyncLocalStorage if available (server-side) + // Prefer per-request locale from local AsyncLocalStorage if available (server-side) try { - const asyncStore = (globalThis as any).qcAsyncRequestStore; - const ev = asyncStore?.getStore?.(); - const evLocale = ev && typeof ev.locale === 'function' ? ev.locale : undefined; - if (evLocale) { - const l = evLocale(); - if (l) { - return l; - } + const store = localAsyncStore?.getStore?.(); + const l = store?.locale; + if (l) { + return l; } } catch { // ignore and fallback @@ -45,19 +64,10 @@ export function getLocale(defaultLocale?: string): string { * @public */ export function withLocale(locale: string, fn: () => T): T { - // If running on the server with AsyncLocalStorage, set locale on the current request + // If running on the server with AsyncLocalStorage, set locale for this async context try { - const asyncStore = (globalThis as any).qcAsyncRequestStore; - const ev = asyncStore?.getStore?.(); - const evLocale = ev && typeof ev.locale === 'function' ? ev.locale : undefined; - if (evLocale) { - const previous = evLocale(); - try { - evLocale(locale); - return fn(); - } finally { - evLocale(previous); - } + if (localAsyncStore?.run) { + return localAsyncStore.run({ locale }, fn); } } catch { // ignore and fallback @@ -81,13 +91,11 @@ export function withLocale(locale: string, fn: () => T): T { * @public */ export function setLocale(locale: string): void { - // On the server, prefer setting the locale on the per-request store + // On the server, prefer setting the locale on the local per-request store try { - const asyncStore = (globalThis as any).qcAsyncRequestStore; - const ev = asyncStore?.getStore?.(); - const evLocale = ev && typeof ev.locale === 'function' ? ev.locale : undefined; - if (evLocale) { - evLocale(locale); + const store = localAsyncStore?.getStore?.(); + if (store) { + store.locale = locale; return; } } catch { From 27dd79508e7322ab1dea943b79db373795e2c369 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Mon, 18 Aug 2025 09:40:45 +0800 Subject: [PATCH 05/10] refactor: remove unnecessary comment and simplify store retrieval in head.ts --- packages/qwik-router/src/runtime/src/head.ts | 2 +- packages/qwik/src/core/use/use-locale.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/qwik-router/src/runtime/src/head.ts b/packages/qwik-router/src/runtime/src/head.ts index 39d31bb6ce3..a347c43e396 100644 --- a/packages/qwik-router/src/runtime/src/head.ts +++ b/packages/qwik-router/src/runtime/src/head.ts @@ -37,7 +37,7 @@ export const resolveHead = ( } return data; }) as any as ResolveSyncValue; - const storeEv = (globalThis as any).qcAsyncRequestStore?.getStore?.(); + const storeEv = (globalThis as any).qcAsyncRequestStore; const headProps: DocumentHeadProps = { head, withLocale: (fn) => (storeEv ? fn() : withLocale(locale, fn)), diff --git a/packages/qwik/src/core/use/use-locale.ts b/packages/qwik/src/core/use/use-locale.ts index ca711abfd06..4aaf785795e 100644 --- a/packages/qwik/src/core/use/use-locale.ts +++ b/packages/qwik/src/core/use/use-locale.ts @@ -11,7 +11,6 @@ let localAsyncStore: LocaleAsyncStore | undefined; if (isServer) { try { - // Lazy import to avoid bundling for non-Node targets import('node:async_hooks') .then((module) => { const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => LocaleAsyncStore; From 1c341e44bd4053581733234eaffd345f21d1bd09 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Tue, 19 Aug 2025 17:02:13 +0800 Subject: [PATCH 06/10] refactor: simplify AsyncLocalStorage usage and improve locale retrieval in use-locale.ts --- .../request-handler/user-response.ts | 7 ++--- packages/qwik-router/src/runtime/src/head.ts | 2 +- packages/qwik/src/core/use/use-locale.ts | 27 ++++++++----------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/qwik-router/src/middleware/request-handler/user-response.ts b/packages/qwik-router/src/middleware/request-handler/user-response.ts index 70c7b954980..c3bb6e22012 100644 --- a/packages/qwik-router/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-router/src/middleware/request-handler/user-response.ts @@ -64,10 +64,11 @@ export function runQwikRouter( return { response: responsePromise, requestEv, - completion: withLocale(requestEv.locale(), () => + completion: withLocale( + requestEv.locale(), asyncStore - ? asyncStore.run(requestEv, () => runNext(requestEv, rebuildRouteInfo, resolve!)) - : runNext(requestEv, rebuildRouteInfo, resolve!) + ? () => asyncStore!.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!) + : () => runNext(requestEv, rebuildRouteInfo, resolve!) ), }; } diff --git a/packages/qwik-router/src/runtime/src/head.ts b/packages/qwik-router/src/runtime/src/head.ts index a347c43e396..a712bc9110d 100644 --- a/packages/qwik-router/src/runtime/src/head.ts +++ b/packages/qwik-router/src/runtime/src/head.ts @@ -40,7 +40,7 @@ export const resolveHead = ( const storeEv = (globalThis as any).qcAsyncRequestStore; const headProps: DocumentHeadProps = { head, - withLocale: (fn) => (storeEv ? fn() : withLocale(locale, fn)), + withLocale: storeEv ? (fn) => fn() : (fn) => withLocale(locale, fn), resolveValue: getData, ...routeLocation, }; diff --git a/packages/qwik/src/core/use/use-locale.ts b/packages/qwik/src/core/use/use-locale.ts index 4aaf785795e..589529ad789 100644 --- a/packages/qwik/src/core/use/use-locale.ts +++ b/packages/qwik/src/core/use/use-locale.ts @@ -10,18 +10,14 @@ type LocaleAsyncStore = import('node:async_hooks').AsyncLocalStorage { - const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => LocaleAsyncStore; - localAsyncStore = new AsyncLocalStorage(); - }) - .catch(() => { - // ignore if AsyncLocalStorage is not available - }); - } catch { - // ignore and fallback - } + import('node:async_hooks') + .then((module) => { + const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => LocaleAsyncStore; + localAsyncStore = new AsyncLocalStorage(); + }) + .catch(() => { + // ignore if AsyncLocalStorage is not available + }); } /** @@ -35,10 +31,9 @@ if (isServer) { export function getLocale(defaultLocale?: string): string { // Prefer per-request locale from local AsyncLocalStorage if available (server-side) try { - const store = localAsyncStore?.getStore?.(); - const l = store?.locale; - if (l) { - return l; + const locale = localAsyncStore?.getStore?.()?.locale; + if (locale) { + return locale; } } catch { // ignore and fallback From 92d67daa6d61d3e5b3b8e8e49663e1c7988eba72 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Mon, 25 Aug 2025 13:42:18 +0800 Subject: [PATCH 07/10] refactor: unify AsyncLocalStorage usage across router middleware and improve request handling --- packages/qwik-router/global.d.ts | 1 - .../request-handler/async-request-store.ts | 11 ++++++++ .../request-handler/request-event.ts | 3 ++- .../request-handler/user-response.ts | 4 ++- packages/qwik-router/src/runtime/src/head.ts | 8 +++--- .../src/runtime/src/server-functions.ts | 3 ++- packages/qwik/src/core/use/use-locale.ts | 25 ++++++++----------- 7 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 packages/qwik-router/src/middleware/request-handler/async-request-store.ts diff --git a/packages/qwik-router/global.d.ts b/packages/qwik-router/global.d.ts index 2f382a9ddcf..12b823070f8 100644 --- a/packages/qwik-router/global.d.ts +++ b/packages/qwik-router/global.d.ts @@ -6,7 +6,6 @@ type RequestEventInternal = type AsyncStore = import('node:async_hooks').AsyncLocalStorage; type SerializationStrategy = import('@qwik.dev/core/internal').SerializationStrategy; -declare var qcAsyncRequestStore: AsyncStore | undefined; declare var _qwikActionsMap: Map | undefined; /** @deprecated Will be removed in v3 */ diff --git a/packages/qwik-router/src/middleware/request-handler/async-request-store.ts b/packages/qwik-router/src/middleware/request-handler/async-request-store.ts new file mode 100644 index 00000000000..fd34f6ae826 --- /dev/null +++ b/packages/qwik-router/src/middleware/request-handler/async-request-store.ts @@ -0,0 +1,11 @@ +import type { RequestEventInternal } from './request-event'; +import type { AsyncLocalStorage } from 'node:async_hooks'; + +export type AsyncStore = AsyncLocalStorage; + +// Qwik Core will also be using the async store if this is present +export let asyncRequestStore: AsyncStore | undefined; + +export const setAsyncRequestStore = (store: AsyncStore | undefined) => { + asyncRequestStore = store; +}; diff --git a/packages/qwik-router/src/middleware/request-handler/request-event.ts b/packages/qwik-router/src/middleware/request-handler/request-event.ts index 9cdadfe9055..42096286bd7 100644 --- a/packages/qwik-router/src/middleware/request-handler/request-event.ts +++ b/packages/qwik-router/src/middleware/request-handler/request-event.ts @@ -20,6 +20,7 @@ import { RewriteMessage, } from '@qwik.dev/router/middleware/request-handler'; import { encoder, getRouteLoaderPromise } from './resolve-request-handlers'; +import { asyncRequestStore } from './async-request-store'; import type { CacheControl, CacheControlTarget, @@ -83,7 +84,7 @@ export function createRequestEvent( while (routeModuleIndex < requestHandlers.length) { const moduleRequestHandler = requestHandlers[routeModuleIndex]; - const asyncStore = globalThis.qcAsyncRequestStore; + const asyncStore = asyncRequestStore; const result = asyncStore?.run ? asyncStore.run(requestEv, moduleRequestHandler, requestEv) : moduleRequestHandler(requestEv); diff --git a/packages/qwik-router/src/middleware/request-handler/user-response.ts b/packages/qwik-router/src/middleware/request-handler/user-response.ts index c3bb6e22012..f94450d61af 100644 --- a/packages/qwik-router/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-router/src/middleware/request-handler/user-response.ts @@ -14,6 +14,8 @@ import { import { encoder } from './resolve-request-handlers'; import type { QwikSerializer, ServerRequestEvent, StatusCodes } from './types'; import { withLocale } from '@qwik.dev/core'; +import { setAsyncRequestStore } from './async-request-store'; +import type { AsyncStore } from './async-request-store'; // Import separately to avoid duplicate imports in the vite dev server import { AbortMessage, @@ -33,7 +35,7 @@ import('node:async_hooks') .then((module) => { const AsyncLocalStorage = module.AsyncLocalStorage; asyncStore = new AsyncLocalStorage(); - globalThis.qcAsyncRequestStore = asyncStore; + setAsyncRequestStore(asyncStore); }) .catch((err) => { console.warn( diff --git a/packages/qwik-router/src/runtime/src/head.ts b/packages/qwik-router/src/runtime/src/head.ts index a712bc9110d..ab4d7d696aa 100644 --- a/packages/qwik-router/src/runtime/src/head.ts +++ b/packages/qwik-router/src/runtime/src/head.ts @@ -13,6 +13,7 @@ import type { ActionInternal, } from './types'; import { isPromise } from './utils'; +import { asyncRequestStore } from '../../middleware/request-handler/async-request-store'; export const resolveHead = ( endpoint: EndpointResponse | ClientPageData, @@ -37,10 +38,11 @@ export const resolveHead = ( } return data; }) as any as ResolveSyncValue; - const storeEv = (globalThis as any).qcAsyncRequestStore; + // Qwik Core will also be using the async store if this is present + const hasAsyncStore = asyncRequestStore; const headProps: DocumentHeadProps = { head, - withLocale: storeEv ? (fn) => fn() : (fn) => withLocale(locale, fn), + withLocale: hasAsyncStore ? (fn) => fn() : (fn) => withLocale(locale, fn), resolveValue: getData, ...routeLocation, }; @@ -51,7 +53,7 @@ export const resolveHead = ( if (typeof contentModuleHead === 'function') { resolveDocumentHead( head, - storeEv + hasAsyncStore ? contentModuleHead(headProps) : withLocale(locale, () => contentModuleHead(headProps)) ); diff --git a/packages/qwik-router/src/runtime/src/server-functions.ts b/packages/qwik-router/src/runtime/src/server-functions.ts index fafddd70ab2..3ba57ada274 100644 --- a/packages/qwik-router/src/runtime/src/server-functions.ts +++ b/packages/qwik-router/src/runtime/src/server-functions.ts @@ -65,6 +65,7 @@ import { useAction, useLocation, useQwikRouterEnv } from './use-functions'; import type { FormSubmitCompletedDetail } from './form-component'; import { deepFreeze } from './utils'; +import { asyncRequestStore } from '../../middleware/request-handler/async-request-store'; /** @internal */ export const routeActionQrl = (( @@ -420,7 +421,7 @@ export const serverQrl = ( if (isServer) { // Running during SSR, we can call the function directly - let requestEvent = globalThis.qcAsyncRequestStore?.getStore() as RequestEvent | undefined; + let requestEvent = asyncRequestStore?.getStore() as RequestEvent | undefined; if (!requestEvent) { const contexts = [useQwikRouterEnv()?.ev, this, _getContextEvent()] as RequestEvent[]; diff --git a/packages/qwik/src/core/use/use-locale.ts b/packages/qwik/src/core/use/use-locale.ts index 589529ad789..81a3cd6f106 100644 --- a/packages/qwik/src/core/use/use-locale.ts +++ b/packages/qwik/src/core/use/use-locale.ts @@ -1,18 +1,17 @@ import { tryGetInvokeContext } from './use-core'; import { isServer } from '@qwik.dev/core/build'; +import type { AsyncLocalStorage } from 'node:async_hooks'; let _locale: string | undefined = undefined; -type LocaleStore = { locale: string | undefined }; - -type LocaleAsyncStore = import('node:async_hooks').AsyncLocalStorage; - -let localAsyncStore: LocaleAsyncStore | undefined; +let localAsyncStore: AsyncLocalStorage<{ locale?: string }> | undefined; if (isServer) { import('node:async_hooks') .then((module) => { - const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => LocaleAsyncStore; + const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => AsyncLocalStorage<{ + locale?: string; + }>; localAsyncStore = new AsyncLocalStorage(); }) .catch(() => { @@ -30,13 +29,11 @@ if (isServer) { */ export function getLocale(defaultLocale?: string): string { // Prefer per-request locale from local AsyncLocalStorage if available (server-side) - try { - const locale = localAsyncStore?.getStore?.()?.locale; + if (localAsyncStore) { + const locale = localAsyncStore.getStore()?.locale; if (locale) { return locale; } - } catch { - // ignore and fallback } if (_locale === undefined) { @@ -60,7 +57,7 @@ export function getLocale(defaultLocale?: string): string { export function withLocale(locale: string, fn: () => T): T { // If running on the server with AsyncLocalStorage, set locale for this async context try { - if (localAsyncStore?.run) { + if (localAsyncStore) { return localAsyncStore.run({ locale }, fn); } } catch { @@ -87,9 +84,9 @@ export function withLocale(locale: string, fn: () => T): T { export function setLocale(locale: string): void { // On the server, prefer setting the locale on the local per-request store try { - const store = localAsyncStore?.getStore?.(); - if (store) { - store.locale = locale; + if (localAsyncStore && localAsyncStore.getStore) { + const store = localAsyncStore.getStore(); + store!.locale = locale; return; } } catch { From 559b9f381f4bb7196ced25407677dcea940cf942 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Tue, 26 Aug 2025 10:24:03 +0800 Subject: [PATCH 08/10] refactor: streamline AsyncLocalStorage usage in locale functions and improve code clarity --- packages/qwik-router/src/runtime/src/head.ts | 3 +-- packages/qwik/src/core/use/use-locale.ts | 22 ++++++-------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/qwik-router/src/runtime/src/head.ts b/packages/qwik-router/src/runtime/src/head.ts index ab4d7d696aa..dd8ec4c5661 100644 --- a/packages/qwik-router/src/runtime/src/head.ts +++ b/packages/qwik-router/src/runtime/src/head.ts @@ -13,7 +13,7 @@ import type { ActionInternal, } from './types'; import { isPromise } from './utils'; -import { asyncRequestStore } from '../../middleware/request-handler/async-request-store'; +import { asyncRequestStore as hasAsyncStore } from '../../middleware/request-handler/async-request-store'; export const resolveHead = ( endpoint: EndpointResponse | ClientPageData, @@ -39,7 +39,6 @@ export const resolveHead = ( return data; }) as any as ResolveSyncValue; // Qwik Core will also be using the async store if this is present - const hasAsyncStore = asyncRequestStore; const headProps: DocumentHeadProps = { head, withLocale: hasAsyncStore ? (fn) => fn() : (fn) => withLocale(locale, fn), diff --git a/packages/qwik/src/core/use/use-locale.ts b/packages/qwik/src/core/use/use-locale.ts index 81a3cd6f106..83d545f8f3f 100644 --- a/packages/qwik/src/core/use/use-locale.ts +++ b/packages/qwik/src/core/use/use-locale.ts @@ -55,13 +55,8 @@ export function getLocale(defaultLocale?: string): string { * @public */ export function withLocale(locale: string, fn: () => T): T { - // If running on the server with AsyncLocalStorage, set locale for this async context - try { - if (localAsyncStore) { - return localAsyncStore.run({ locale }, fn); - } - } catch { - // ignore and fallback + if (localAsyncStore) { + return localAsyncStore.run({ locale }, fn); } const previousLang = _locale; @@ -82,15 +77,10 @@ export function withLocale(locale: string, fn: () => T): T { * @public */ export function setLocale(locale: string): void { - // On the server, prefer setting the locale on the local per-request store - try { - if (localAsyncStore && localAsyncStore.getStore) { - const store = localAsyncStore.getStore(); - store!.locale = locale; - return; - } - } catch { - // ignore and fallback + if (localAsyncStore && localAsyncStore.getStore) { + const store = localAsyncStore.getStore(); + store!.locale = locale; + return; } _locale = locale; } From 0e148b711fc8dc3e45b8c284478e502084e7e158 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Tue, 26 Aug 2025 14:21:01 +0800 Subject: [PATCH 09/10] refactor: simplify AsyncLocalStorage usage and improve locale retrieval in use-locale.ts --- packages/qwik-router/global.d.ts | 2 ++ .../middleware/request-handler/async-request-store.ts | 4 ++-- .../src/middleware/request-handler/user-response.ts | 11 +++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/qwik-router/global.d.ts b/packages/qwik-router/global.d.ts index 12b823070f8..ac5e1996356 100644 --- a/packages/qwik-router/global.d.ts +++ b/packages/qwik-router/global.d.ts @@ -6,6 +6,8 @@ type RequestEventInternal = type AsyncStore = import('node:async_hooks').AsyncLocalStorage; type SerializationStrategy = import('@qwik.dev/core/internal').SerializationStrategy; +declare var qcAsyncRequestStore: AsyncStore | undefined; + declare var _qwikActionsMap: Map | undefined; /** @deprecated Will be removed in v3 */ diff --git a/packages/qwik-router/src/middleware/request-handler/async-request-store.ts b/packages/qwik-router/src/middleware/request-handler/async-request-store.ts index fd34f6ae826..72b87db8203 100644 --- a/packages/qwik-router/src/middleware/request-handler/async-request-store.ts +++ b/packages/qwik-router/src/middleware/request-handler/async-request-store.ts @@ -4,8 +4,8 @@ import type { AsyncLocalStorage } from 'node:async_hooks'; export type AsyncStore = AsyncLocalStorage; // Qwik Core will also be using the async store if this is present -export let asyncRequestStore: AsyncStore | undefined; +export const asyncRequestStore: AsyncStore | undefined = globalThis.qcAsyncRequestStore; export const setAsyncRequestStore = (store: AsyncStore | undefined) => { - asyncRequestStore = store; + globalThis.qcAsyncRequestStore = store; }; diff --git a/packages/qwik-router/src/middleware/request-handler/user-response.ts b/packages/qwik-router/src/middleware/request-handler/user-response.ts index f94450d61af..d7c5c311207 100644 --- a/packages/qwik-router/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-router/src/middleware/request-handler/user-response.ts @@ -14,8 +14,7 @@ import { import { encoder } from './resolve-request-handlers'; import type { QwikSerializer, ServerRequestEvent, StatusCodes } from './types'; import { withLocale } from '@qwik.dev/core'; -import { setAsyncRequestStore } from './async-request-store'; -import type { AsyncStore } from './async-request-store'; +import { setAsyncRequestStore, asyncRequestStore } from './async-request-store'; // Import separately to avoid duplicate imports in the vite dev server import { AbortMessage, @@ -30,11 +29,11 @@ export interface QwikRouterRun { completion: Promise; } -let asyncStore: AsyncStore | undefined; import('node:async_hooks') .then((module) => { const AsyncLocalStorage = module.AsyncLocalStorage; - asyncStore = new AsyncLocalStorage(); + const asyncStore = new AsyncLocalStorage(); + console.log('AsyncLocalStorage', asyncStore); setAsyncRequestStore(asyncStore); }) .catch((err) => { @@ -68,8 +67,8 @@ export function runQwikRouter( requestEv, completion: withLocale( requestEv.locale(), - asyncStore - ? () => asyncStore!.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!) + asyncRequestStore + ? () => asyncRequestStore!.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!) : () => runNext(requestEv, rebuildRouteInfo, resolve!) ), }; From c1c07c65fde33ddc4c4bf4eb72381dd2afeb0938 Mon Sep 17 00:00:00 2001 From: Jerry_Wu <409187100@qq.com> Date: Tue, 26 Aug 2025 14:24:56 +0800 Subject: [PATCH 10/10] refactor: remove debug log for AsyncLocalStorage in user-response.ts --- .../qwik-router/src/middleware/request-handler/user-response.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/qwik-router/src/middleware/request-handler/user-response.ts b/packages/qwik-router/src/middleware/request-handler/user-response.ts index d7c5c311207..39100b1e469 100644 --- a/packages/qwik-router/src/middleware/request-handler/user-response.ts +++ b/packages/qwik-router/src/middleware/request-handler/user-response.ts @@ -33,7 +33,6 @@ import('node:async_hooks') .then((module) => { const AsyncLocalStorage = module.AsyncLocalStorage; const asyncStore = new AsyncLocalStorage(); - console.log('AsyncLocalStorage', asyncStore); setAsyncRequestStore(asyncStore); }) .catch((err) => {