Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4664b34
feat: enhance locale handling with AsyncLocalStorage support for serv…
JerryWu1234 Aug 15, 2025
90890c9
add changeset
JerryWu1234 Aug 15, 2025
25d1648
fix: improve locale handling in resolveHead function with AsyncLocalS…
JerryWu1234 Aug 15, 2025
0abc9d8
refactor: streamline locale handling with AsyncLocalStorage for serve…
JerryWu1234 Aug 16, 2025
27dd795
refactor: remove unnecessary comment and simplify store retrieval in …
JerryWu1234 Aug 18, 2025
1c341e4
refactor: simplify AsyncLocalStorage usage and improve locale retriev…
JerryWu1234 Aug 19, 2025
92d67da
refactor: unify AsyncLocalStorage usage across router middleware and …
JerryWu1234 Aug 25, 2025
ce8d18b
Merge branch 'build/v2' into buildv2async
JerryWu1234 Aug 25, 2025
559b9f3
refactor: streamline AsyncLocalStorage usage in locale functions and …
JerryWu1234 Aug 26, 2025
0e148b7
refactor: simplify AsyncLocalStorage usage and improve locale retriev…
JerryWu1234 Aug 26, 2025
c1c07c6
refactor: remove debug log for AsyncLocalStorage in user-response.ts
JerryWu1234 Aug 26, 2025
888e4df
refactor: enhance AsyncLocalStorage integration in request handling a…
JerryWu1234 Sep 9, 2025
1609ffd
refactor: remove server-side check in setLocale and update asyncReque…
JerryWu1234 Sep 10, 2025
ef75798
refactor: optimize AsyncLocalStorage handling in locale functions and…
JerryWu1234 Sep 10, 2025
1293991
refactor: add server-side check for AsyncLocalStorage initialization …
JerryWu1234 Sep 25, 2025
0195432
solve conflict
JerryWu1234 Sep 26, 2025
e97bc40
refactor: update import paths for core modules in async-request-store…
JerryWu1234 Sep 26, 2025
e1bd82c
refactor(router): improve plugin retrieval logic in Vite adapter
JerryWu1234 Sep 26, 2025
412a0e3
refactor(router): unify plugin retrieval logic in Vite adapter and bu…
JerryWu1234 Sep 26, 2025
bd3925a
refactor(router): add double type assertion to avoid TypeScript stack…
JerryWu1234 Sep 26, 2025
73e5e06
Merge branch 'build/v2' into buildv2async
JerryWu1234 Sep 26, 2025
f815a32
refactor(router): enhance head resolution logic to support async requ…
JerryWu1234 Sep 26, 2025
23966f2
Merge branch 'build/v2' into buildv2async
JerryWu1234 Sep 28, 2025
a50d819
refactor(router): replace asyncRequestStore with global qcAsyncReques…
JerryWu1234 Sep 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/pretty-parents-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@qwik.dev/router': patch
'@qwik.dev/core': patch
---

enhance locale handling with AsyncLocalStorage support for server-side requests
1 change: 0 additions & 1 deletion packages/qwik-router/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ type RequestEventInternal =
type AsyncStore = import('node:async_hooks').AsyncLocalStorage<RequestEventInternal>;
type SerializationStrategy = import('@qwik.dev/core/internal').SerializationStrategy;

declare var qcAsyncRequestStore: AsyncStore | undefined;
declare var _qwikActionsMap: Map<string, ActionInternal> | undefined;

/** @deprecated Will be removed in v3 */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { RequestEventInternal } from './request-event';
import type { AsyncLocalStorage } from 'node:async_hooks';

export type AsyncStore = AsyncLocalStorage<RequestEventInternal>;

// 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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
} from './request-event';
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,
Expand All @@ -32,7 +35,7 @@ import('node:async_hooks')
.then((module) => {
const AsyncLocalStorage = module.AsyncLocalStorage;
asyncStore = new AsyncLocalStorage<RequestEventInternal>();
globalThis.qcAsyncRequestStore = asyncStore;
setAsyncRequestStore(asyncStore);
})
.catch((err) => {
console.warn(
Expand Down Expand Up @@ -63,9 +66,12 @@ export function runQwikRouter<T>(
return {
response: responsePromise,
requestEv,
completion: asyncStore
? asyncStore.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!)
: runNext(requestEv, rebuildRouteInfo, resolve!),
completion: withLocale(
requestEv.locale(),
asyncStore
? () => asyncStore!.run(requestEv, runNext, requestEv, rebuildRouteInfo, resolve!)
: () => runNext(requestEv, rebuildRouteInfo, resolve!)
),
};
}

Expand Down
9 changes: 7 additions & 2 deletions packages/qwik-router/src/runtime/src/head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -37,9 +38,11 @@ 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: (fn) => withLocale(locale, fn),
withLocale: hasAsyncStore ? (fn) => fn() : (fn) => withLocale(locale, fn),
resolveValue: getData,
...routeLocation,
};
Expand All @@ -50,7 +53,9 @@ export const resolveHead = (
if (typeof contentModuleHead === 'function') {
resolveDocumentHead(
head,
withLocale(locale, () => contentModuleHead(headProps))
hasAsyncStore
? contentModuleHead(headProps)
: withLocale(locale, () => contentModuleHead(headProps))
);
} else if (typeof contentModuleHead === 'object') {
resolveDocumentHead(head, contentModuleHead);
Expand Down
3 changes: 2 additions & 1 deletion packages/qwik-router/src/runtime/src/server-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ((
Expand Down Expand Up @@ -420,7 +421,7 @@ export const serverQrl = <T extends ServerFunction>(

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[];
Expand Down
44 changes: 44 additions & 0 deletions packages/qwik/src/core/use/use-locale.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import { tryGetInvokeContext } from './use-core';
import { isServer } from '@qwik.dev/core/build';
import type { AsyncLocalStorage } from 'node:async_hooks';

let _locale: string | undefined = undefined;

let localAsyncStore: AsyncLocalStorage<{ locale?: string }> | undefined;

if (isServer) {
import('node:async_hooks')
.then((module) => {
const AsyncLocalStorage = module.AsyncLocalStorage as unknown as new () => AsyncLocalStorage<{
locale?: string;
}>;
localAsyncStore = new AsyncLocalStorage();
})
.catch(() => {
// ignore if AsyncLocalStorage is not available
});
}

/**
* Retrieve the current locale.
*
Expand All @@ -11,6 +28,14 @@ let _locale: string | undefined = undefined;
* @public
*/
export function getLocale(defaultLocale?: string): string {
// Prefer per-request locale from local AsyncLocalStorage if available (server-side)
if (localAsyncStore) {
const locale = localAsyncStore.getStore()?.locale;
if (locale) {
return locale;
}
}

if (_locale === undefined) {
const ctx = tryGetInvokeContext();
if (ctx && ctx.$locale$) {
Expand All @@ -30,6 +55,15 @@ export function getLocale(defaultLocale?: string): string {
* @public
*/
export function withLocale<T>(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
}

const previousLang = _locale;
try {
_locale = locale;
Expand All @@ -48,5 +82,15 @@ export function withLocale<T>(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
}
_locale = locale;
}
Loading