From 5e26133eb60a521884099b3e26922760eda716b1 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Fri, 12 Jul 2024 03:55:50 +0200 Subject: [PATCH] refactor: improve function type safety (#3024) * refactor: improve function type safety * refactor: restructure and improve types * refactor: restructure into compatibility file * refactor: add comments and reorder imports * refactor: rename method and reorder imports * refactor: improve messages function types * refactor: improve server plugin types --- src/runtime/compatibility.ts | 116 +++++++++++++++++++++ src/runtime/composables/index.ts | 7 +- src/runtime/internal.ts | 28 +---- src/runtime/messages.ts | 29 +++--- src/runtime/plugins/i18n.ts | 41 +++----- src/runtime/routing/compatibles/head.ts | 7 +- src/runtime/routing/compatibles/routing.ts | 3 +- src/runtime/routing/extends/i18n.ts | 2 +- src/runtime/routing/extends/router.ts | 5 +- src/runtime/routing/utils.ts | 67 +----------- src/runtime/server/plugin.ts | 4 +- src/runtime/utils.ts | 102 ++++++------------ src/server.d.ts | 2 + tsconfig.json | 3 +- 14 files changed, 199 insertions(+), 217 deletions(-) create mode 100644 src/runtime/compatibility.ts create mode 100644 src/server.d.ts diff --git a/src/runtime/compatibility.ts b/src/runtime/compatibility.ts new file mode 100644 index 000000000..99cf2fb2b --- /dev/null +++ b/src/runtime/compatibility.ts @@ -0,0 +1,116 @@ +/** + * Utility functions to support both VueI18n and Composer instances + */ + +import { isRef, unref } from 'vue' + +import type { NuxtApp } from '#app' +import type { LocaleObject } from '#build/i18n.options.mjs' +import type { Composer, I18n, Locale, VueI18n } from 'vue-i18n' +import type { UnwrapRef } from 'vue' + +function isI18nInstance(i18n: I18n | VueI18n | Composer): i18n is I18n { + return i18n != null && 'global' in i18n && 'mode' in i18n +} + +function isComposer(target: I18n | VueI18n | Composer): target is Composer { + return target != null && !('__composer' in target) && 'locale' in target && isRef(target.locale) +} + +export function isVueI18n(target: I18n | VueI18n | Composer): target is VueI18n { + return target != null && '__composer' in target +} + +export function getI18nTarget(i18n: I18n | VueI18n | Composer) { + return isI18nInstance(i18n) ? i18n.global : i18n +} + +export function getComposer(i18n: I18n | VueI18n | Composer): Composer { + const target = getI18nTarget(i18n) + + if (isComposer(target)) return target + if (isVueI18n(target)) return target.__composer + + return target +} + +/** + * Extract the value of a property on a VueI18n or Composer instance + */ +function extractI18nProperty, K extends keyof T>( + i18n: T, + key: K +): UnwrapRef { + return unref(i18n[key]) as UnwrapRef +} + +/** + * Typesafe access to property of a VueI18n or Composer instance + */ +export function getI18nProperty>(i18n: I18n, property: K) { + return extractI18nProperty(getI18nTarget(i18n), property) +} + +/** + * Sets the value of the locale property on VueI18n or Composer instance + * + * This differs from the instance `setLocale` method in that it sets the + * locale property directly without triggering other side effects + */ +export function setLocaleProperty(i18n: I18n, locale: Locale): void { + const target = getI18nTarget(i18n) + if (isRef(target.locale)) { + target.locale.value = locale + } else { + target.locale = locale + } +} + +export function getLocale(i18n: I18n): Locale { + return getI18nProperty(i18n, 'locale') +} + +export function getLocales(i18n: I18n): string[] | LocaleObject[] { + return getI18nProperty(i18n, 'locales') +} + +export function getLocaleCodes(i18n: I18n): string[] { + return getI18nProperty(i18n, 'localeCodes') +} + +export function setLocale(i18n: I18n, locale: Locale) { + return getI18nTarget(i18n).setLocale(locale) +} + +export function setLocaleCookie(i18n: I18n, locale: Locale) { + return getI18nTarget(i18n).setLocaleCookie(locale) +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mergeLocaleMessage(i18n: I18n, locale: Locale, messages: Record) { + return getI18nTarget(i18n).mergeLocaleMessage(locale, messages) +} + +export async function onBeforeLanguageSwitch( + i18n: I18n, + oldLocale: string, + newLocale: string, + initial: boolean, + context: NuxtApp +) { + return getI18nTarget(i18n).onBeforeLanguageSwitch(oldLocale, newLocale, initial, context) +} + +export function onLanguageSwitched(i18n: I18n, oldLocale: string, newLocale: string) { + return getI18nTarget(i18n).onLanguageSwitched(oldLocale, newLocale) +} + +declare module 'vue-i18n' { + interface VueI18n { + /** + * This is not exposed in VueI18n's types, but it's used internally + * @internal + */ + __composer: Composer + } +} diff --git a/src/runtime/composables/index.ts b/src/runtime/composables/index.ts index f3c33b064..ae3d4e3ab 100644 --- a/src/runtime/composables/index.ts +++ b/src/runtime/composables/index.ts @@ -17,7 +17,8 @@ import { localeRoute, switchLocalePath } from '../routing/compatibles' -import { findBrowserLocale, getComposer, getLocale, getLocales } from '../routing/utils' +import { findBrowserLocale } from '../routing/utils' +import { getLocale, getLocales, getComposer } from '../compatibility' import type { Ref } from 'vue' import type { Locale } from 'vue-i18n' @@ -44,8 +45,8 @@ export function useSetI18nParams(seoAttributes?: SeoAttributesOptions): SetI18nP const i18n = getComposer(common.i18n) const router = common.router - const locale = getLocale(i18n) - const locales = getNormalizedLocales(getLocales(i18n)) + const locale = getLocale(common.i18n) + const locales = getNormalizedLocales(getLocales(common.i18n)) const _i18nParams = ref({}) const experimentalSSR = common.runtimeConfig.public.i18n.experimental.switchLocalePathLinkSSR diff --git a/src/runtime/internal.ts b/src/runtime/internal.ts index c2d8eaa37..ae9e522a1 100644 --- a/src/runtime/internal.ts +++ b/src/runtime/internal.ts @@ -3,19 +3,12 @@ import { isArray, isString, isObject } from '@intlify/shared' import { hasProtocol } from 'ufo' import isHTTPS from 'is-https' -import { - useRequestHeaders, - useRequestEvent, - useCookie as useNuxtCookie, - useRuntimeConfig, - useNuxtApp, - unref -} from '#imports' +import { useRequestHeaders, useRequestEvent, useCookie as useNuxtCookie, useRuntimeConfig, useNuxtApp } from '#imports' import { NUXT_I18N_MODULE_ID, DEFAULT_COOKIE_KEY, isSSG, localeCodes, normalizedLocales } from '#build/i18n.options.mjs' -import { findBrowserLocale, getLocalesRegex, getI18nTarget } from './routing/utils' +import { findBrowserLocale, getLocalesRegex } from './routing/utils' import { initCommonComposableOptions, type CommonComposableOptions } from './utils' -import type { Locale, I18n } from 'vue-i18n' +import type { Locale } from 'vue-i18n' import type { DetectBrowserLanguageOptions, LocaleObject } from '#build/i18n.options.mjs' import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router' import type { CookieRef } from 'nuxt/app' @@ -25,21 +18,6 @@ export function formatMessage(message: string) { return NUXT_I18N_MODULE_ID + ' ' + message } -export function callVueI18nInterfaces(i18n: any, name: string, ...args: any[]): Return { - const target = getI18nTarget(i18n as unknown as I18n) - // prettier-ignore - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/ban-types - const [obj, method] = [target, (target as any)[name] as Function] - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - return Reflect.apply(method, obj, [...args]) as Return -} - -export function getVueI18nPropertyValue(i18n: any, name: string): Return { - const target = getI18nTarget(i18n as unknown as I18n) - // @ts-expect-error name should be typed instead of string - return unref(target[name]) as Return -} - export function defineGetter(obj: Record, key: K, val: V) { Object.defineProperty(obj, key, { get: () => val }) } diff --git a/src/runtime/messages.ts b/src/runtime/messages.ts index 6be6aa9de..b8e36ac88 100644 --- a/src/runtime/messages.ts +++ b/src/runtime/messages.ts @@ -6,9 +6,14 @@ import type { DeepRequired } from 'ts-essentials' import type { VueI18nConfig, NuxtI18nOptions } from '../types' import type { CoreContext } from '@intlify/h3' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type LocaleLoader = { key: string; load: () => Promise; cache: boolean } +type MessageLoaderFunction = (locale: Locale) => Promise> +type MessageLoaderResult | LocaleMessages> = { default: Result } | Result +export type LocaleLoader> = { + key: string + load: () => Promise> + cache: boolean +} const cacheMessages = new Map>() export async function loadVueI18nOptions( @@ -73,21 +78,18 @@ async function loadMessage(locale: Locale, { key, load }: LocaleLoader) { let message: LocaleMessages | null = null try { __DEBUG__ && console.log('loadMessage: (locale) -', locale) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access -- FIXME - const getter = await load().then(r => r.default || r) + const getter = await load().then(r => ('default' in r ? r.default : r)) if (isFunction(getter)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call -- FIXME message = await getter(locale) __DEBUG__ && console.log('loadMessage: dynamic load', message) } else { - message = getter as LocaleMessages + message = getter if (message != null && cacheMessages) { cacheMessages.set(key, message) } __DEBUG__ && console.log('loadMessage: load', message) } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { + } catch (e: unknown) { console.error('Failed locale loading: ' + (e as Error).message) } return message @@ -126,20 +128,17 @@ export async function loadLocale( setter(locale, targetMessage) } -type LocaleLoaderMessages = CoreContext['messages'] | LocaleMessages +type LocaleLoaderMessages = + | CoreContext['messages'] + | LocaleMessages export async function loadAndSetLocaleMessages( locale: Locale, localeLoaders: Record, messages: LocaleLoaderMessages ) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const setter = (locale: Locale, message: Record) => { - // @ts-expect-error should be able to use `locale` as index - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- FIXME + const setter = (locale: Locale, message: LocaleMessages) => { const base = messages[locale] || {} deepCopy(message, base) - // @ts-expect-error should be able to use `locale` as index - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- FIXME messages[locale] = base } diff --git a/src/runtime/plugins/i18n.ts b/src/runtime/plugins/i18n.ts index a12f88ba1..ac564c792 100644 --- a/src/runtime/plugins/i18n.ts +++ b/src/runtime/plugins/i18n.ts @@ -10,27 +10,19 @@ import { normalizedLocales } from '#build/i18n.options.mjs' import { loadVueI18nOptions, loadInitialMessages, loadLocale } from '../messages' +import { loadAndSetLocale, detectLocale, detectRedirect, navigate, injectNuxtHelpers, extendBaseUrl } from '../utils' import { - loadAndSetLocale, - detectLocale, - detectRedirect, - navigate, - injectNuxtHelpers, - extendBaseUrl, - _setLocale, - mergeLocaleMessage -} from '../utils' -import { - getBrowserLocale as _getBrowserLocale, - getLocaleCookie as _getLocaleCookie, - setLocaleCookie as _setLocaleCookie, + getBrowserLocale, + getLocaleCookie, + setLocaleCookie, detectBrowserLanguage, DefaultDetectBrowserLanguageFromResult, getI18nCookie, runtimeDetectBrowserLanguage } from '../internal' -import { getLocale, inBrowser, resolveBaseUrl, setLocale } from '../routing/utils' +import { inBrowser, resolveBaseUrl } from '../routing/utils' import { extendI18n, createLocaleFromRouteGetter } from '../routing/extends' +import { setLocale, getLocale, mergeLocaleMessage, setLocaleProperty } from '../compatibility' import type { LocaleObject } from '#build/i18n.options.mjs' import type { Locale, I18nOptions } from 'vue-i18n' @@ -81,7 +73,7 @@ export default defineNuxtPlugin({ ssg: isSSG && runtimeI18n.strategy === 'no_prefix' ? 'ssg_ignore' : 'normal', callType: 'setup', firstAccess: true, - localeCookie: _getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) + localeCookie: getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) }, runtimeI18n ) @@ -118,7 +110,7 @@ export default defineNuxtPlugin({ * avoid hydration mismatch for SSG mode */ if (isSSGModeInitialSetup() && runtimeI18n.strategy === 'no_prefix' && import.meta.client) { - nuxt.hook('app:mounted', () => { + nuxt.hook('app:mounted', async () => { __DEBUG__ && console.log('hook app:mounted') const { locale: browserLocale, @@ -133,7 +125,7 @@ export default defineNuxtPlugin({ ssg: 'ssg_setup', callType: 'setup', firstAccess: true, - localeCookie: _getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) + localeCookie: getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) }, initialLocale ) @@ -146,7 +138,7 @@ export default defineNuxtPlugin({ reason, from ) - _setLocale(i18n, browserLocale) + await setLocale(i18n, browserLocale) ssgModeInitialSetup = false }) } @@ -211,16 +203,15 @@ export default defineNuxtPlugin({ ) } composer.loadLocaleMessages = async (locale: string) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const setter = (locale: Locale, message: Record) => mergeLocaleMessage(i18n, locale, message) + const setter = mergeLocaleMessage.bind(null, i18n) await loadLocale(locale, localeLoaders, setter) } composer.differentDomains = runtimeI18n.differentDomains composer.defaultLocale = runtimeI18n.defaultLocale - composer.getBrowserLocale = () => _getBrowserLocale() + composer.getBrowserLocale = () => getBrowserLocale() composer.getLocaleCookie = () => - _getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) - composer.setLocaleCookie = (locale: string) => _setLocaleCookie(localeCookie, locale, _detectBrowserLanguage) + getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) + composer.setLocaleCookie = (locale: string) => setLocaleCookie(localeCookie, locale, _detectBrowserLanguage) composer.onBeforeLanguageSwitch = (oldLocale, newLocale, initialSetup, context) => nuxt.callHook('i18n:beforeLocaleSwitch', { oldLocale, newLocale, initialSetup, context }) as Promise @@ -231,7 +222,7 @@ export default defineNuxtPlugin({ if (!i18n.__pendingLocale) { return } - setLocale(i18n, i18n.__pendingLocale) + setLocaleProperty(i18n, i18n.__pendingLocale) if (i18n.__resolvePendingLocalePromise) { // eslint-disable-next-line @typescript-eslint/await-thenable -- FIXME: `__resolvePendingLocalePromise` should be `Promise` await i18n.__resolvePendingLocalePromise() @@ -330,7 +321,7 @@ export default defineNuxtPlugin({ ssg: isSSGModeInitialSetup() && runtimeI18n.strategy === 'no_prefix' ? 'ssg_ignore' : 'normal', callType: 'routing', firstAccess: routeChangeCount === 0, - localeCookie: _getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) + localeCookie: getLocaleCookie(localeCookie, _detectBrowserLanguage, runtimeI18n.defaultLocale) }, runtimeI18n ) diff --git a/src/runtime/routing/compatibles/head.ts b/src/runtime/routing/compatibles/head.ts index 69301d952..d4d2645b8 100644 --- a/src/runtime/routing/compatibles/head.ts +++ b/src/runtime/routing/compatibles/head.ts @@ -1,9 +1,10 @@ +import { joinURL } from 'ufo' +import { isArray, isObject } from '@intlify/shared' import { unref, useNuxtApp, useRuntimeConfig } from '#imports' -import { getComposer, getLocale, getLocales, getNormalizedLocales } from '../utils' +import { getNormalizedLocales } from '../utils' import { getRouteBaseName, localeRoute, switchLocalePath } from './routing' -import { isArray, isObject } from '@intlify/shared' -import { joinURL } from 'ufo' +import { getComposer, getLocale, getLocales } from '../../compatibility' import type { I18n } from 'vue-i18n' import type { I18nHeadMetaInfo, MetaAttrs, LocaleObject, I18nHeadOptions } from '#build/i18n.options.mjs' diff --git a/src/runtime/routing/compatibles/routing.ts b/src/runtime/routing/compatibles/routing.ts index d12ccafa0..17e7f0185 100644 --- a/src/runtime/routing/compatibles/routing.ts +++ b/src/runtime/routing/compatibles/routing.ts @@ -4,8 +4,9 @@ import { hasProtocol, parsePath, parseQuery, withTrailingSlash, withoutTrailingS import { DEFAULT_DYNAMIC_PARAMS_KEY } from '#build/i18n.options.mjs' import { unref } from '#imports' +import { getLocale } from '../../compatibility' import { resolve, routeToObject } from './utils' -import { getLocale, getLocaleRouteName, getRouteName } from '../utils' +import { getLocaleRouteName, getRouteName } from '../utils' import { extendPrefixable, extendSwitchLocalePathIntercepter, type CommonComposableOptions } from '../../utils' import type { Strategies, PrefixableOptions, SwitchLocalePathIntercepter } from '#build/i18n.options.mjs' diff --git a/src/runtime/routing/extends/i18n.ts b/src/runtime/routing/extends/i18n.ts index da4f40665..22be352f2 100644 --- a/src/runtime/routing/extends/i18n.ts +++ b/src/runtime/routing/extends/i18n.ts @@ -1,5 +1,5 @@ import { effectScope } from '#imports' -import { isVueI18n, getComposer } from '../utils' +import { isVueI18n, getComposer } from '../../compatibility' import { getRouteBaseName, localeHead, diff --git a/src/runtime/routing/extends/router.ts b/src/runtime/routing/extends/router.ts index 17c6e331d..8a7adcf25 100644 --- a/src/runtime/routing/extends/router.ts +++ b/src/runtime/routing/extends/router.ts @@ -5,6 +5,9 @@ import { localeCodes } from '#build/i18n.options.mjs' import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router' import { useRuntimeConfig } from '#imports' +export type GetLocaleFromRouteFunction = ( + route: RouteLocationNormalizedLoaded | RouteLocationNormalized | string +) => string export function createLocaleFromRouteGetter() { const { routesNameSeparator, defaultLocaleRouteNameSuffix } = useRuntimeConfig().public.i18n const localesPattern = `(${localeCodes.join('|')})` @@ -17,7 +20,7 @@ export function createLocaleFromRouteGetter() { * - if route has a name, try to extract locale from it * - otherwise, fall back to using the routes'path */ - const getLocaleFromRoute = (route: RouteLocationNormalizedLoaded | RouteLocationNormalized | string): string => { + const getLocaleFromRoute: GetLocaleFromRouteFunction = route => { // extract from route name if (isObject(route)) { if (route.name) { diff --git a/src/runtime/routing/utils.ts b/src/runtime/routing/utils.ts index 890e05dc4..7ff53cda0 100644 --- a/src/runtime/routing/utils.ts +++ b/src/runtime/routing/utils.ts @@ -1,10 +1,7 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { isString, isSymbol, isFunction } from '@intlify/shared' -import { isRef, unref } from '#imports' import type { LocaleObject, Strategies, BaseUrlResolveHandler } from '#build/i18n.options.mjs' -import type { Composer, I18n, Locale, VueI18n } from 'vue-i18n' +import type { Locale } from 'vue-i18n' export const inBrowser = typeof window !== 'undefined' @@ -21,66 +18,6 @@ export function getNormalizedLocales(locales: string[] | LocaleObject[]): Locale return normalized } -function isI18nInstance(i18n: I18n | VueI18n | Composer): i18n is I18n { - return i18n != null && 'global' in i18n && 'mode' in i18n -} - -function isComposer(target: I18n | VueI18n | Composer): target is Composer { - return target != null && !('__composer' in target) && 'locale' in target && isRef(target.locale) -} - -export function isVueI18n(target: I18n | VueI18n | Composer): target is VueI18n { - return target != null && '__composer' in target -} - -export function getI18nTarget(i18n: I18n | VueI18n | Composer) { - return isI18nInstance(i18n) ? i18n.global : i18n -} - -export function getComposer(i18n: I18n | VueI18n | Composer): Composer { - const target = getI18nTarget(i18n) - - if (isComposer(target)) return target - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (isVueI18n(target)) return (target as any).__composer as Composer - - return target -} - -/** - * Get a locale - * - * @param i18n - An [I18n](https://vue-i18n.intlify.dev/api/general.html#i18n) instance or a [Composer](https://vue-i18n.intlify.dev/api/composition.html#composer) instance - * - * @returns A locale - */ -export function getLocale(i18n: I18n | Composer | VueI18n): Locale { - return unref(getI18nTarget(i18n).locale) -} - -export function getLocales(i18n: I18n | VueI18n | Composer): string[] | LocaleObject[] { - return unref(getI18nTarget(i18n).locales) -} - -export function getLocaleCodes(i18n: I18n | VueI18n | Composer): string[] { - return unref(getI18nTarget(i18n).localeCodes) -} - -/** - * Set a locale - * - * @param i18n - An [I18n](https://vue-i18n.intlify.dev/api/general.html#i18n) instance or a [Composer](https://vue-i18n.intlify.dev/api/composition.html#composer) instance - * @param locale - A target locale - */ -export function setLocale(i18n: I18n | Composer, locale: Locale): void { - const target = getI18nTarget(i18n) - if (isRef(target.locale)) { - target.locale.value = locale - } else { - target.locale = locale - } -} - export function adjustRoutePathForTrailingSlash( pagePath: string, trailingSlash: boolean, @@ -256,5 +193,3 @@ export function findBrowserLocale( export function getLocalesRegex(localeCodes: string[]) { return new RegExp(`^/(${localeCodes.join('|')})(?:/|$)`, 'i') } - -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/src/runtime/server/plugin.ts b/src/runtime/server/plugin.ts index 26a989da8..0e0ad625f 100644 --- a/src/runtime/server/plugin.ts +++ b/src/runtime/server/plugin.ts @@ -2,7 +2,6 @@ import { useRuntimeConfig } from '#imports' import { defineI18nMiddleware } from '@intlify/h3' import { localeCodes, vueI18nConfigs, localeLoaders } from '#internal/i18n/options.mjs' import { defineNitroPlugin } from 'nitropack/dist/runtime/plugin' -// @ts-ignore import { localeDetector as _localeDetector } from '#internal/i18n/locale.detector.mjs' import { loadVueI18nOptions, loadInitialMessages, makeFallbackLocaleCodes, loadAndSetLocaleMessages } from '../messages' @@ -37,11 +36,10 @@ export default defineNitroPlugin(async nitro => { event: H3Event, i18nContext: CoreContext ): Promise => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call const locale = _localeDetector(event, { defaultLocale: initialLocale, fallbackLocale: options.fallbackLocale - }) as Locale + }) if (runtimeI18n.lazy) { if (fallbackLocale) { const fallbackLocales = makeFallbackLocaleCodes(fallbackLocale, [locale]) diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index dea05ddd9..367478da7 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -2,22 +2,10 @@ import { joinURL, isEqual } from 'ufo' import { isString, isFunction, isObject } from '@intlify/shared' import { navigateTo, useNuxtApp, useRouter, useRuntimeConfig, useState } from '#imports' -import { - NUXT_I18N_MODULE_ID, - isSSG, - localeLoaders, - normalizedLocales, - type RootRedirectOptions, - type PrefixableOptions, - type SwitchLocalePathIntercepter, - type BaseUrlResolveHandler, - type LocaleObject -} from '#build/i18n.options.mjs' +import { NUXT_I18N_MODULE_ID, isSSG, localeLoaders, normalizedLocales } from '#build/i18n.options.mjs' import { wrapComposable, detectBrowserLanguage, - callVueI18nInterfaces, - getVueI18nPropertyValue, defineGetter, getLocaleDomain, getDomainFromLocale, @@ -34,66 +22,35 @@ import { DefaultPrefixable, DefaultSwitchLocalePathIntercepter } from './routing/compatibles' -import { getLocale, setLocale, getLocaleCodes, getI18nTarget } from './routing/utils' - -import type { I18n, Locale, FallbackLocale, Composer, VueI18n } from 'vue-i18n' +import { + getI18nProperty, + getI18nTarget, + getLocale, + getLocaleCodes, + mergeLocaleMessage, + onBeforeLanguageSwitch, + onLanguageSwitched, + setLocaleProperty, + setLocaleCookie +} from './compatibility' + +import type { I18n, Locale } from 'vue-i18n' import type { NuxtApp } from '#app' import type { Ref } from '#imports' import type { Router } from '#vue-router' import type { DetectLocaleContext } from './internal' import type { HeadSafe } from '@unhead/vue' -import type { createLocaleFromRouteGetter } from './routing/extends/router' +import type { GetLocaleFromRouteFunction } from './routing/extends/router' import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router' import type { RuntimeConfig } from '@nuxt/schema' import type { ModulePublicRuntimeConfig } from '../module' - -export function _setLocale(i18n: I18n, locale: Locale): ReturnType { - return callVueI18nInterfaces(i18n, 'setLocale', locale) -} - -export function setCookieLocale(i18n: I18n, locale: Locale): ReturnType { - return callVueI18nInterfaces(i18n, 'setLocaleCookie', locale) -} - -export function setLocaleMessage( - i18n: I18n, - locale: Locale, - messages: Record -): ReturnType { - return callVueI18nInterfaces(i18n, 'setLocaleMessage', locale, messages) -} - -export function mergeLocaleMessage( - i18n: I18n, - locale: Locale, - messages: Record -): ReturnType { - return callVueI18nInterfaces(i18n, 'mergeLocaleMessage', locale, messages) -} - -// eslint-disable-next-line @typescript-eslint/require-await -async function onBeforeLanguageSwitch( - i18n: I18n, - oldLocale: string, - newLocale: string, - initial: boolean, - context: NuxtApp -): Promise { - return callVueI18nInterfaces(i18n, 'onBeforeLanguageSwitch', oldLocale, newLocale, initial, context) -} - -export function onLanguageSwitched( - i18n: I18n, - oldLocale: string, - newLocale: string -): ReturnType { - return callVueI18nInterfaces(i18n, 'onLanguageSwitched', oldLocale, newLocale) -} - -// eslint-disable-next-line @typescript-eslint/require-await -export async function finalizePendingLocaleChange(i18n: I18n): Promise> { - return callVueI18nInterfaces(i18n, 'finalizePendingLocaleChange') -} +import type { + RootRedirectOptions, + PrefixableOptions, + SwitchLocalePathIntercepter, + BaseUrlResolveHandler, + LocaleObject +} from '#build/i18n.options.mjs' /** * Common options used internally by composable functions, these @@ -134,7 +91,7 @@ export async function loadAndSetLocale( if (opts === false || !opts.useCookie) return if (skipSettingLocaleOnNavigate) return - setCookieLocale(i18n, locale) + setLocaleCookie(i18n, locale) } __DEBUG__ && console.log('setLocale: new -> ', newLocale, ' old -> ', oldLocale, ' initial -> ', initial) @@ -170,10 +127,9 @@ export async function loadAndSetLocale( // load locale messages required by `newLocale` if (lazy) { - const i18nFallbackLocales = getVueI18nPropertyValue(i18n, 'fallbackLocale') + const i18nFallbackLocales = getI18nProperty(i18n, 'fallbackLocale') - const setter = (locale: Locale, message: Record): ReturnType => - mergeLocaleMessage(i18n, locale, message) + const setter = mergeLocaleMessage.bind(null, i18n) if (i18nFallbackLocales) { const fallbackLocales = makeFallbackLocaleCodes(i18nFallbackLocales, [newLocale]) await Promise.all(fallbackLocales.map(locale => loadLocale(locale, localeLoaders, setter))) @@ -187,7 +143,7 @@ export async function loadAndSetLocale( // sync cookie and set the locale syncCookie(newLocale) - setLocale(i18n, newLocale) + setLocaleProperty(i18n, newLocale) await onLanguageSwitched(i18n, oldLocale, newLocale) @@ -198,7 +154,7 @@ type LocaleLoader = () => Locale export function detectLocale( route: string | RouteLocationNormalized | RouteLocationNormalizedLoaded, - routeLocaleGetter: ReturnType, + routeLocaleGetter: GetLocaleFromRouteFunction, vueI18nOptionsLocale: Locale | undefined, initialLocaleLoader: Locale | LocaleLoader, detectLocaleContext: DetectLocaleContext, @@ -286,7 +242,7 @@ export function detectRedirect({ from?: RouteLocationNormalized | RouteLocationNormalizedLoaded } targetLocale: Locale - routeLocaleGetter: ReturnType + routeLocaleGetter: GetLocaleFromRouteFunction calledWithRouting?: boolean }): string { const nuxtApp = useNuxtApp() @@ -424,7 +380,7 @@ export async function navigate( } } -export function injectNuxtHelpers(nuxt: NuxtApp, i18n: I18n | VueI18n | Composer) { +export function injectNuxtHelpers(nuxt: NuxtApp, i18n: I18n) { /** * NOTE: * we will inject `i18n.global` to **nuxt app instance only** diff --git a/src/server.d.ts b/src/server.d.ts new file mode 100644 index 000000000..3a08813c5 --- /dev/null +++ b/src/server.d.ts @@ -0,0 +1,2 @@ +export type LocaleDetector = (event: H3Event, config: LocaleConfig) => string +export const localeDetector: LocaleDetector diff --git a/tsconfig.json b/tsconfig.json index 891cd6ce9..78e4a1d6d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -38,7 +38,8 @@ "#i18n": ["./src/runtime/composables/index.ts"], "#vue-router": ["./node_modules/vue-router"], "#build/i18n.options.mjs": ["./src/options.d.ts"], - "#internal/i18n/options.mjs": ["./src/options.d.ts"] + "#internal/i18n/options.mjs": ["./src/options.d.ts"], + "#internal/i18n/locale.detector.mjs": ["./src/server.d.ts"] } /* Specify a set of entries that re-map imports to additional lookup locations. */, // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */