-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: enhance locale handling with AsyncLocalStorage support for server-side requests #7826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: build/v2
Are you sure you want to change the base?
Changes from all commits
4664b34
90890c9
25d1648
0abc9d8
27dd795
1c341e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,9 +37,10 @@ export const resolveHead = ( | |
} | ||
return data; | ||
}) as any as ResolveSyncValue; | ||
const storeEv = (globalThis as any).qcAsyncRequestStore; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you rename this to Actually, it would be better to export the store from where it is made and to import it. instead of globalThis, make it |
||
const headProps: DocumentHeadProps = { | ||
head, | ||
withLocale: (fn) => withLocale(locale, fn), | ||
withLocale: storeEv ? (fn) => fn() : (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); | ||
|
JerryWu1234 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,25 @@ | ||
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<LocaleStore>; | ||
|
||
let localAsyncStore: LocaleAsyncStore | undefined; | ||
Comment on lines
+6
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. import type {AsyncLocalStorage} from 'node:async_hooks'
let asyncStore: AsyncLocalStorage<{locale?: string}> | undefined |
||
|
||
if (isServer) { | ||
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 | ||
}); | ||
} | ||
|
||
/** | ||
* Retrieve the current locale. | ||
* | ||
|
@@ -11,6 +29,16 @@ let _locale: string | undefined = undefined; | |
* @public | ||
*/ | ||
export function getLocale(defaultLocale?: string): string { | ||
// Prefer per-request locale from local AsyncLocalStorage if available (server-side) | ||
try { | ||
const locale = localAsyncStore?.getStore?.()?.locale; | ||
if (locale) { | ||
return locale; | ||
} | ||
} catch { | ||
// ignore and fallback | ||
} | ||
|
||
Comment on lines
+33
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (localAsyncStore) {
const locale = localAsyncStore.getStore()?.locale
if (locale) {
return locale
}
} don't add try/catch and conditionals when not needed, it has a runtime cost. |
||
if (_locale === undefined) { | ||
const ctx = tryGetInvokeContext(); | ||
if (ctx && ctx.$locale$) { | ||
|
@@ -30,6 +58,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?.run) { | ||
return localAsyncStore.run({ locale }, fn); | ||
} | ||
} catch { | ||
// ignore and fallback | ||
} | ||
Comment on lines
+62
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if (localAsyncStore) {
return localAsyncStore.run({ locale }, fn);
} |
||
|
||
const previousLang = _locale; | ||
try { | ||
_locale = locale; | ||
|
@@ -48,5 +85,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 { | ||
const store = localAsyncStore?.getStore?.(); | ||
if (store) { | ||
store.locale = locale; | ||
return; | ||
} | ||
} catch { | ||
// ignore and fallback | ||
} | ||
Comment on lines
+88
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see above |
||
_locale = locale; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.