You've already forked AstralRinth
forked from xxxOFFxxx/AstralRinth
feat: remove nuxt i18n for in house i18n for web (#5131)
* feat: remove nuxt i18n for in house * cleanup: remove old nuxt/i18n patch * prepr --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { createPinia } from 'pinia'
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
import App from '@/App.vue'
|
import App from '@/App.vue'
|
||||||
import i18n from '@/i18n.config'
|
import i18nPlugin from '@/plugins/i18n'
|
||||||
import router from '@/routes'
|
import router from '@/routes'
|
||||||
|
|
||||||
const vueScan = new VueScanPlugin({
|
const vueScan = new VueScanPlugin({
|
||||||
@@ -43,6 +43,6 @@ app.use(FloatingVue, {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.use(i18n)
|
app.use(i18nPlugin)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
23
apps/app-frontend/src/plugins/i18n.ts
Normal file
23
apps/app-frontend/src/plugins/i18n.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { I18N_INJECTION_KEY, type I18nContext } from '@modrinth/ui'
|
||||||
|
import type { App } from 'vue'
|
||||||
|
|
||||||
|
import i18n from '@/i18n.config'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(app: App) {
|
||||||
|
// Install vue-i18n as before
|
||||||
|
app.use(i18n)
|
||||||
|
|
||||||
|
// Wrap it in our I18nContext interface
|
||||||
|
const context: I18nContext = {
|
||||||
|
locale: i18n.global.locale,
|
||||||
|
t: (key, values) => i18n.global.t(key, values ?? {}) as string,
|
||||||
|
setLocale: (newLocale) => {
|
||||||
|
i18n.global.locale.value = newLocale
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the context at app-level
|
||||||
|
app.provide(I18N_INJECTION_KEY, context)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { createMessageCompiler } from '@modrinth/ui'
|
|
||||||
|
|
||||||
export default defineI18nConfig(() => ({
|
|
||||||
legacy: false,
|
|
||||||
locale: 'en-US',
|
|
||||||
fallbackLocale: 'en-US',
|
|
||||||
messageCompiler: createMessageCompiler(),
|
|
||||||
missingWarn: false,
|
|
||||||
fallbackWarn: false,
|
|
||||||
}))
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { type CrowdinMessages, transformCrowdinMessages } from '@modrinth/ui'
|
|
||||||
|
|
||||||
// eager:false - only loads the locale requested
|
|
||||||
const localeModules = import.meta.glob<{ default: CrowdinMessages }>(
|
|
||||||
'../src/locales/*/index.json',
|
|
||||||
{ eager: false },
|
|
||||||
)
|
|
||||||
|
|
||||||
export default defineI18nLocale(async (locale) => {
|
|
||||||
const loader = localeModules[`../src/locales/${locale}/index.json`]
|
|
||||||
if (!loader) {
|
|
||||||
console.warn(`Locale ${locale} not found`)
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
const messages = await loader()
|
|
||||||
return transformCrowdinMessages(messages.default)
|
|
||||||
})
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { GenericModrinthClient, type Labrinth } from '@modrinth/api-client'
|
import { GenericModrinthClient, type Labrinth } from '@modrinth/api-client'
|
||||||
import { LOCALES } from '@modrinth/ui/src/composables/i18n.ts'
|
|
||||||
import serverSidedVue from '@vitejs/plugin-vue'
|
import serverSidedVue from '@vitejs/plugin-vue'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import { defineNuxtConfig } from 'nuxt/config'
|
import { defineNuxtConfig } from 'nuxt/config'
|
||||||
@@ -223,7 +222,6 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
modules: [
|
modules: [
|
||||||
'@nuxtjs/i18n',
|
|
||||||
'@pinia/nuxt',
|
'@pinia/nuxt',
|
||||||
'floating-vue/nuxt',
|
'floating-vue/nuxt',
|
||||||
// Sentry causes rollup-plugin-inject errors in dev, only enable in production
|
// Sentry causes rollup-plugin-inject errors in dev, only enable in production
|
||||||
@@ -243,25 +241,6 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
i18n: {
|
|
||||||
defaultLocale: 'en-US',
|
|
||||||
lazy: true,
|
|
||||||
langDir: '.',
|
|
||||||
locales: LOCALES.map((locale) => ({
|
|
||||||
...locale,
|
|
||||||
file: 'locale-loader.ts',
|
|
||||||
})),
|
|
||||||
strategy: 'no_prefix',
|
|
||||||
detectBrowserLanguage: {
|
|
||||||
useCookie: true,
|
|
||||||
cookieKey: 'locale',
|
|
||||||
fallbackLocale: 'en-US',
|
|
||||||
},
|
|
||||||
vueI18n: './i18n.config.ts',
|
|
||||||
bundle: {
|
|
||||||
optimizeTranslationDirective: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nitro: {
|
nitro: {
|
||||||
rollupConfig: {
|
rollupConfig: {
|
||||||
// @ts-expect-error because of rolldown-vite - completely fine though
|
// @ts-expect-error because of rolldown-vite - completely fine though
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^6.2.12",
|
"@formatjs/cli": "^6.2.12",
|
||||||
"@modrinth/tooling-config": "workspace:*",
|
"@modrinth/tooling-config": "workspace:*",
|
||||||
"@nuxtjs/i18n": "^9.0.0",
|
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/iso-3166-2": "^1.0.4",
|
"@types/iso-3166-2": "^1.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
@@ -66,6 +65,7 @@
|
|||||||
"iso-3166-2": "1.0.0",
|
"iso-3166-2": "1.0.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"lru-cache": "^11.2.4",
|
||||||
"markdown-it": "14.1.0",
|
"markdown-it": "14.1.0",
|
||||||
"pathe": "^1.1.2",
|
"pathe": "^1.1.2",
|
||||||
"pinia": "^3.0.0",
|
"pinia": "^3.0.0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import {
|
import {
|
||||||
Admonition,
|
Admonition,
|
||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
|
injectI18n,
|
||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
LanguageSelector,
|
LanguageSelector,
|
||||||
languageSelectorMessages,
|
languageSelectorMessages,
|
||||||
@@ -10,8 +11,7 @@ import {
|
|||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const { $i18n } = useNuxtApp()
|
const { locale, setLocale } = injectI18n()
|
||||||
const { locale, setLocale } = $i18n
|
|
||||||
|
|
||||||
const platform = formatMessage(languageSelectorMessages.platformSite)
|
const platform = formatMessage(languageSelectorMessages.platformSite)
|
||||||
|
|
||||||
|
|||||||
112
apps/frontend/src/plugins/i18n.ts
Normal file
112
apps/frontend/src/plugins/i18n.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import {
|
||||||
|
type CrowdinMessages,
|
||||||
|
I18N_INJECTION_KEY,
|
||||||
|
type I18nContext,
|
||||||
|
LOCALES,
|
||||||
|
transformCrowdinMessages,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import IntlMessageFormat from 'intl-messageformat'
|
||||||
|
import { LRUCache } from 'lru-cache'
|
||||||
|
|
||||||
|
const DEFAULT_LOCALE = 'en-US'
|
||||||
|
|
||||||
|
const localeModules = import.meta.glob<{ default: CrowdinMessages }>('../locales/*/index.json', {
|
||||||
|
eager: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const messageCache = new LRUCache<string, Record<string, string>>({ max: 10 })
|
||||||
|
const formatterCache = new LRUCache<string, IntlMessageFormat>({ max: 1000 })
|
||||||
|
const loadingPromises = new Map<string, Promise<void>>() // Dedupe concurrent loads
|
||||||
|
|
||||||
|
async function loadLocale(code: string): Promise<void> {
|
||||||
|
if (messageCache.has(code)) return
|
||||||
|
|
||||||
|
// Dedupe concurrent requests for the same locale
|
||||||
|
const existing = loadingPromises.get(code)
|
||||||
|
if (existing) return existing
|
||||||
|
|
||||||
|
const promise = (async () => {
|
||||||
|
const loader = localeModules[`../locales/${code}/index.json`]
|
||||||
|
if (!loader) return
|
||||||
|
const raw = await loader()
|
||||||
|
messageCache.set(code, transformCrowdinMessages(raw.default))
|
||||||
|
})()
|
||||||
|
|
||||||
|
loadingPromises.set(code, promise)
|
||||||
|
try {
|
||||||
|
await promise
|
||||||
|
} finally {
|
||||||
|
loadingPromises.delete(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAcceptLanguage(header: string): string | null {
|
||||||
|
try {
|
||||||
|
for (const lang of header
|
||||||
|
.split(',')
|
||||||
|
.map((l) => l.split(';')[0]?.trim())
|
||||||
|
.filter(Boolean)) {
|
||||||
|
const exact = LOCALES.find((loc) => loc.code === lang)
|
||||||
|
if (exact) return exact.code
|
||||||
|
const prefix = LOCALES.find((loc) => loc.code.startsWith(lang.split('-')[0] + '-'))
|
||||||
|
if (prefix) return prefix.code
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Malformed header, ignore
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtPlugin({
|
||||||
|
name: 'i18n',
|
||||||
|
enforce: 'pre',
|
||||||
|
async setup(nuxtApp) {
|
||||||
|
// ONLY locale needs request-scoping (what language this request uses)
|
||||||
|
const locale = useState<string>('i18n-locale', () => DEFAULT_LOCALE)
|
||||||
|
|
||||||
|
function t(key: string, values?: Record<string, unknown>): string {
|
||||||
|
const msg = messageCache.get(locale.value)?.[key] ?? messageCache.get(DEFAULT_LOCALE)?.[key]
|
||||||
|
if (!msg) return key
|
||||||
|
if (!values || Object.keys(values).length === 0) return msg
|
||||||
|
|
||||||
|
const cacheKey = `${locale.value}:${msg}`
|
||||||
|
let formatter = formatterCache.get(cacheKey)
|
||||||
|
if (!formatter) {
|
||||||
|
formatter = new IntlMessageFormat(msg, locale.value)
|
||||||
|
formatterCache.set(cacheKey, formatter)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return formatter.format(values) as string
|
||||||
|
} catch {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLocale(newLocale: string): Promise<void> {
|
||||||
|
if (!LOCALES.some((l) => l.code === newLocale)) return
|
||||||
|
await loadLocale(newLocale)
|
||||||
|
locale.value = newLocale
|
||||||
|
useCookie('locale', { maxAge: 31536000, path: '/' }).value = newLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect initial locale (cookie > Accept-Language > default)
|
||||||
|
const cookieLocale = useCookie('locale').value
|
||||||
|
let detectedLocale = DEFAULT_LOCALE
|
||||||
|
if (cookieLocale && LOCALES.some((l) => l.code === cookieLocale)) {
|
||||||
|
detectedLocale = cookieLocale
|
||||||
|
} else if (import.meta.server) {
|
||||||
|
const acceptLang = useRequestHeaders(['accept-language'])['accept-language']
|
||||||
|
if (acceptLang) {
|
||||||
|
detectedLocale = parseAcceptLanguage(acceptLang) ?? DEFAULT_LOCALE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load locales (hits cache after first request)
|
||||||
|
await loadLocale(DEFAULT_LOCALE)
|
||||||
|
if (detectedLocale !== DEFAULT_LOCALE) await loadLocale(detectedLocale)
|
||||||
|
locale.value = detectedLocale
|
||||||
|
|
||||||
|
const context: I18nContext = { locale, t, setLocale }
|
||||||
|
nuxtApp.vueApp.provide(I18N_INJECTION_KEY, context)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -39,8 +39,7 @@
|
|||||||
"packageManager": "pnpm@9.15.0",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"readable-stream@2.3.8": "patches/readable-stream@2.3.8.patch",
|
"readable-stream@2.3.8": "patches/readable-stream@2.3.8.patch"
|
||||||
"@nuxtjs/i18n@9.5.6": "patches/@nuxtjs__i18n@9.5.6.patch"
|
|
||||||
},
|
},
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import IntlMessageFormat, { type FormatXMLElementFn, type PrimitiveType } from 'intl-messageformat'
|
import IntlMessageFormat, { type FormatXMLElementFn, type PrimitiveType } from 'intl-messageformat'
|
||||||
import { computed, useSlots, type VNode } from 'vue'
|
import { computed, useSlots, type VNode } from 'vue'
|
||||||
|
|
||||||
import { getSafeI18n, type MessageDescriptor } from '../../composables/i18n'
|
import type { MessageDescriptor } from '../../composables/i18n'
|
||||||
|
import { injectI18n } from '../../providers/i18n'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
messageId: MessageDescriptor
|
messageId: MessageDescriptor
|
||||||
@@ -10,7 +11,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
const { t, locale } = getSafeI18n()
|
const { t, locale } = injectI18n()
|
||||||
|
|
||||||
const formattedParts = computed(() => {
|
const formattedParts = computed(() => {
|
||||||
const key = props.messageId.id
|
const key = props.messageId.id
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { computed, type ComputedRef } from 'vue'
|
import { computed, type ComputedRef } from 'vue'
|
||||||
|
|
||||||
import { getSafeI18n } from './i18n'
|
import { injectI18n } from '../providers/i18n'
|
||||||
|
|
||||||
export type Formatter = (value: Date | number, options?: FormatOptions) => string
|
export type Formatter = (value: Date | number, options?: FormatOptions) => string
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ export interface FormatOptions {
|
|||||||
const formatters = new Map<string, ComputedRef<Intl.RelativeTimeFormat>>()
|
const formatters = new Map<string, ComputedRef<Intl.RelativeTimeFormat>>()
|
||||||
|
|
||||||
export function useRelativeTime(): Formatter {
|
export function useRelativeTime(): Formatter {
|
||||||
const { locale } = getSafeI18n()
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
const formatterRef = computed(
|
const formatterRef = computed(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -1,35 +1,8 @@
|
|||||||
import IntlMessageFormat from 'intl-messageformat'
|
import IntlMessageFormat from 'intl-messageformat'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import type { CompileError, Composer, MessageCompiler, MessageContext } from 'vue-i18n'
|
import type { CompileError, MessageCompiler, MessageContext } from 'vue-i18n'
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
declare const useNuxtApp: (() => { $i18n?: Pick<Composer, 't' | 'locale'> }) | undefined
|
import { injectI18n } from '../providers/i18n'
|
||||||
|
|
||||||
/**
|
|
||||||
* Get i18n instance, preferring Nuxt's $i18n to avoid vue-i18n's
|
|
||||||
* getCurrentInstance() issues on edge runtimes with concurrent SSR requests.
|
|
||||||
*/
|
|
||||||
export function getSafeI18n(): Pick<Composer, 't' | 'locale'> {
|
|
||||||
// Try Nuxt's $i18n first (avoids Error 27 on Cloudflare Workers)
|
|
||||||
if (typeof useNuxtApp === 'function') {
|
|
||||||
try {
|
|
||||||
const nuxtApp = useNuxtApp()
|
|
||||||
const $i18n = nuxtApp.$i18n
|
|
||||||
if ($i18n) {
|
|
||||||
return { t: $i18n.t, locale: $i18n.locale }
|
|
||||||
}
|
|
||||||
console.warn('[getSafeI18n] useNuxtApp() succeeded but $i18n is falsy:', $i18n)
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('[getSafeI18n] useNuxtApp() threw:', e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.debug('[getSafeI18n] useNuxtApp not available, using vue-i18n fallback')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('FALLBACK TO useI18n!!!')
|
|
||||||
// Fallback to vue-i18n's useI18n (used in Tauri app or if Nuxt context unavailable)
|
|
||||||
return useI18n()
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageDescriptor {
|
export interface MessageDescriptor {
|
||||||
id: string
|
id: string
|
||||||
@@ -55,7 +28,6 @@ export interface LocaleDefinition {
|
|||||||
code: string
|
code: string
|
||||||
name: string
|
name: string
|
||||||
dir?: 'ltr' | 'rtl'
|
dir?: 'ltr' | 'rtl'
|
||||||
// For @nuxtjs/i18n v9 compatibility
|
|
||||||
iso?: string
|
iso?: string
|
||||||
file?: string
|
file?: string
|
||||||
}
|
}
|
||||||
@@ -199,10 +171,10 @@ export interface VIntlFormatters {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable that provides formatMessage() with the same API as @vintl/vintl.
|
* Composable that provides formatMessage() with the same API as @vintl/vintl.
|
||||||
* Uses vue-i18n's useI18n() under the hood.
|
* Uses the injected I18nContext from the provider.
|
||||||
*/
|
*/
|
||||||
export function useVIntl(): VIntlFormatters & { locale: Ref<string> } {
|
export function useVIntl(): VIntlFormatters & { locale: Ref<string> } {
|
||||||
const { t, locale } = getSafeI18n()
|
const { t, locale } = injectI18n()
|
||||||
|
|
||||||
function formatMessage(descriptor: MessageDescriptor, values?: Record<string, unknown>): string {
|
function formatMessage(descriptor: MessageDescriptor, values?: Record<string, unknown>): string {
|
||||||
const key = descriptor.id
|
const key = descriptor.id
|
||||||
|
|||||||
24
packages/ui/src/providers/i18n.ts
Normal file
24
packages/ui/src/providers/i18n.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { InjectionKey, Ref } from 'vue'
|
||||||
|
import { inject, provide } from 'vue'
|
||||||
|
|
||||||
|
// This doesn't use the architecture outlined in index.ts as it needs some custom checks + use the symbol
|
||||||
|
export interface I18nContext {
|
||||||
|
locale: Ref<string>
|
||||||
|
t: (key: string, values?: Record<string, unknown>) => string
|
||||||
|
setLocale: (locale: string) => Promise<void> | void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const I18N_INJECTION_KEY: InjectionKey<I18nContext> = Symbol('i18n')
|
||||||
|
|
||||||
|
export function injectI18n(): I18nContext {
|
||||||
|
const context = inject(I18N_INJECTION_KEY)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Injection `Symbol(i18n)` not found. Ensure the i18n plugin is installed.')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
export function provideI18n(context: I18nContext): I18nContext {
|
||||||
|
provide(I18N_INJECTION_KEY, context)
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -79,6 +79,7 @@ export function createContext<ContextValue>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export * from './api-client'
|
export * from './api-client'
|
||||||
|
export * from './i18n'
|
||||||
export * from './page-context'
|
export * from './page-context'
|
||||||
export * from './project-page'
|
export * from './project-page'
|
||||||
export * from './server-context'
|
export * from './server-context'
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
diff --git a/dist/runtime/composables/index.js b/dist/runtime/composables/index.js
|
|
||||||
index ca736eae7a45ab3752c3f408c971c06bccd4f91b..ec9d34c497249576b6eb00adf3c727960778bae4 100644
|
|
||||||
--- a/dist/runtime/composables/index.js
|
|
||||||
+++ b/dist/runtime/composables/index.js
|
|
||||||
@@ -4,7 +4,24 @@ import { runtimeDetectBrowserLanguage, wrapComposable } from "../internal.js";
|
|
||||||
import { localeCodes } from "#build/i18n.options.mjs";
|
|
||||||
import { _useLocaleHead, _useSetI18nParams } from "../routing/head.js";
|
|
||||||
import { getRouteBaseName, localePath, localeRoute, switchLocalePath } from "../routing/routing.js";
|
|
||||||
-export * from "vue-i18n";
|
|
||||||
+// Re-export everything from vue-i18n EXCEPT useI18n
|
|
||||||
+// useI18n is replaced with a safe version that uses Nuxt's async-context-aware $i18n
|
|
||||||
+// to avoid Error 27 (NOT_INSTALLED) on Cloudflare Workers with concurrent SSR requests
|
|
||||||
+export { DatetimeFormat, I18nD, I18nInjectionKey, I18nN, I18nT, NumberFormat, Translation, VERSION, createI18n, vTDirective } from "vue-i18n";
|
|
||||||
+
|
|
||||||
+/**
|
|
||||||
+ * Safe useI18n replacement that uses Nuxt's $i18n instead of vue-i18n's useI18n.
|
|
||||||
+ *
|
|
||||||
+ * vue-i18n's useI18n() calls getCurrentInstance() which returns a module-level
|
|
||||||
+ * variable shared across concurrent Cloudflare Workers requests. This causes
|
|
||||||
+ * Error 27 when the instance from a different request's Vue app is returned.
|
|
||||||
+ *
|
|
||||||
+ * useNuxtApp() uses AsyncLocalStorage and is request-scoped, avoiding this issue.
|
|
||||||
+ */
|
|
||||||
+export function useI18n(options) {
|
|
||||||
+ const nuxtApp = useNuxtApp();
|
|
||||||
+ return nuxtApp.$i18n;
|
|
||||||
+}
|
|
||||||
export * from "./shared.js";
|
|
||||||
export function useSetI18nParams(seo) {
|
|
||||||
return wrapComposable(_useSetI18nParams)(seo);
|
|
||||||
diff --git a/dist/runtime/plugins/i18n.js b/dist/runtime/plugins/i18n.js
|
|
||||||
index 7a71fcfda18c0770be2c4b7a0b3c2b875bbb832e..cd008b4126400a909bcc66897a1344cb5659e8a6 100644
|
|
||||||
--- a/dist/runtime/plugins/i18n.js
|
|
||||||
+++ b/dist/runtime/plugins/i18n.js
|
|
||||||
@@ -157,7 +157,9 @@ export default defineNuxtPlugin({
|
|
||||||
}
|
|
||||||
});
|
|
||||||
nuxt.vueApp.use(i18n);
|
|
||||||
- Object.defineProperty(nuxt, "$i18n", { get: () => getI18nTarget(i18n) });
|
|
||||||
+ if (!Object.prototype.hasOwnProperty.call(nuxt, '$i18n')) {
|
|
||||||
+ Object.defineProperty(nuxt, "$i18n", { get: () => getI18nTarget(i18n), configurable: true });
|
|
||||||
+ }
|
|
||||||
return {
|
|
||||||
provide: {
|
|
||||||
/**
|
|
||||||
751
pnpm-lock.yaml
generated
751
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user