Skip to content

Commit

Permalink
fix: common composable option passing (#2737)
Browse files Browse the repository at this point in the history
* fix: common composable option passing

* chore: fix composable JSDoc description

* refactor: remove unused code

* fix: `resolveRoute` should pass common options to `resolve`

* test: add regression tests for `#2736`

* chore: add JSDoc description to `CommonComposableOptions`

* fix: provide composable options by wrapping

* refactor: head composable consistency
  • Loading branch information
BobbieGoede committed Jan 29, 2024
1 parent c9bdcd7 commit a94d606
Show file tree
Hide file tree
Showing 14 changed files with 268 additions and 273 deletions.
31 changes: 31 additions & 0 deletions specs/fixtures/issues/2726/app.vue
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { useLocaleHead, useLocalePath, useLocaleRoute, useRouteBaseName, useSwitchLocalePath } from '#i18n'
import { computed, useHead } from '#imports'
const localePath = useLocalePath()
const localeRoute = useLocaleRoute()
const switchLocalePath = useSwitchLocalePath()
const routeBaseName = useRouteBaseName()
const localeHead = useLocaleHead({ addDirAttribute: true, addSeoAttributes: true, identifierAttribute: 'id' })
const metaTestEntries = computed(() => [
{ id: 'locale-path', content: localePath('/nested/test-route') },
{ id: 'locale-route', content: localeRoute('/nested/test-route')?.fullPath ?? '' },
{ id: 'switch-locale-path', content: switchLocalePath('fr') },
{ id: 'route-base-name', content: routeBaseName(localeRoute('/nested/test-route', 'fr')) ?? '' }
])
useHead({
htmlAttrs: {
...localeHead.value.htmlAttrs
},
link: () => [...(localeHead.value.link ?? [])],
meta: () => [...(localeHead.value.meta ?? []), ...metaTestEntries.value]
})
</script>

<template>
<div>
<NuxtPage />
</div>
</template>
19 changes: 19 additions & 0 deletions specs/fixtures/issues/2726/nuxt.config.ts
@@ -0,0 +1,19 @@
// https://nuxt.com/docs/guide/directory-structure/nuxt.config
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
debug: false,
i18n: {
defaultLocale: 'en',
strategy: 'prefix_and_default',
locales: [
{
code: 'en',
iso: 'en-US'
},
{
code: 'fr',
iso: 'fr-FR'
}
]
}
})
14 changes: 14 additions & 0 deletions specs/fixtures/issues/2726/package.json
@@ -0,0 +1,14 @@
{
"name": "nuxt3-test-basic-usage-layers",
"private": true,
"type": "module",
"scripts": {
"dev": "nuxi dev",
"build": "nuxt build",
"start": "node .output/server/index.mjs"
},
"devDependencies": {
"@nuxtjs/i18n": "latest",
"nuxt": "latest"
}
}
3 changes: 3 additions & 0 deletions specs/fixtures/issues/2726/pages/index.vue
@@ -0,0 +1,3 @@
<template>
<h1>Home</h1>
</template>
3 changes: 3 additions & 0 deletions specs/fixtures/issues/2726/pages/nested/test-route.vue
@@ -0,0 +1,3 @@
<template>
<div>Test route</div>
</template>
20 changes: 20 additions & 0 deletions specs/issues/2726.spec.ts
@@ -0,0 +1,20 @@
import { test, expect, describe } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup, $fetch } from '../utils'
import { getDom } from '../helper'

describe('#2726 - Composable requires access to the Nuxt instance', async () => {
await setup({
rootDir: fileURLToPath(new URL(`../fixtures/issues/2726`, import.meta.url))
})

test('Composables correctly initialize common options, no internal server error', async () => {
const html = await $fetch('/')
const dom = getDom(html)

expect(dom.querySelector('head #locale-path').content).toEqual('/nested/test-route')
expect(dom.querySelector('head #locale-route').content).toEqual('/nested/test-route')
expect(dom.querySelector('head #switch-locale-path').content).toEqual('/fr')
expect(dom.querySelector('head #route-base-name').content).toEqual('nested-test-route')
})
})
84 changes: 57 additions & 27 deletions src/runtime/composables/index.ts
@@ -1,10 +1,9 @@
import { useRoute, useRouter, useRequestHeaders, useCookie as useNuxtCookie } from '#imports'
import { useRequestHeaders, useCookie as useNuxtCookie } from '#imports'
import { ref, computed, watch, onUnmounted } from 'vue'
import { parseAcceptLanguage } from '../internal'
import { localeCodes, normalizedLocales, nuxtI18nOptions } from '#build/i18n.options.mjs'
import { getActiveHead } from 'unhead'
import { useI18n } from 'vue-i18n'
import { getNormalizedLocales } from '../utils'
import { getNormalizedLocales, initCommonComposableOptions } from '../utils'
import {
getAlternateOgLocales,
getCanonicalLink,
Expand All @@ -18,17 +17,27 @@ import {
localeRoute,
switchLocalePath
} from '../routing/compatibles'
import { findBrowserLocale, getLocale, getLocales } from '../routing/utils'
import { findBrowserLocale, getComposer, getLocale, getLocales } from '../routing/utils'

import type { Ref } from 'vue'
import type { Locale } from 'vue-i18n'
import type { RouteLocation, RouteLocationNormalizedLoaded, RouteLocationRaw, Router } from 'vue-router'
import type { I18nHeadMetaInfo, I18nHeadOptions, SeoAttributesOptions } from '#build/i18n.options.mjs'
import type { HeadParam } from '../utils'
import type { CommonComposableOptions, HeadParam } from '../utils'

export * from 'vue-i18n'
export * from './shared'

type TailParameters<T> = T extends (first: CommonComposableOptions, ...rest: infer R) => unknown ? R : never

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function wrapComposable<F extends (common: CommonComposableOptions, ...args: any[]) => any>(
fn: F,
common = initCommonComposableOptions()
) {
return (...args: TailParameters<F>) => fn(common, ...args)
}

/**
* Returns a function to set i18n params.
*
Expand All @@ -40,28 +49,29 @@ export * from './shared'
*/
export type SetI18nParamsFunction = (params: Record<string, unknown>) => void
export function useSetI18nParams(seoAttributes?: SeoAttributesOptions): SetI18nParamsFunction {
const route = useRoute()
const common = initCommonComposableOptions()
const head = getActiveHead()
const i18n = getComposer(common.i18n)
const router = common.router

const i18n = useI18n()
const locale = getLocale(i18n)
const locales = getNormalizedLocales(getLocales(i18n))
const _i18nParams = ref({})

const i18nParams = computed({
get() {
return route.meta.nuxtI18n ?? {}
return router.currentRoute.value.meta.nuxtI18n ?? {}
},
set(val) {
_i18nParams.value = val
route.meta.nuxtI18n = val
router.currentRoute.value.meta.nuxtI18n = val
}
})

const stop = watch(
() => route.fullPath,
() => router.currentRoute.value.fullPath,
() => {
route.meta.nuxtI18n = _i18nParams.value
router.currentRoute.value.meta.nuxtI18n = _i18nParams.value
}
)

Expand All @@ -85,12 +95,12 @@ export function useSetI18nParams(seoAttributes?: SeoAttributesOptions): SetI18nP

// prettier-ignore
metaObject.link.push(
...getHreflangLinks(locales, idAttribute),
...getCanonicalLink(idAttribute, seoAttributes)
...getHreflangLinks(common, locales, idAttribute),
...getCanonicalLink(common, idAttribute, seoAttributes)
)

metaObject.meta.push(
...getOgUrl(idAttribute, seoAttributes),
...getOgUrl(common, idAttribute, seoAttributes),
...getCurrentOgLocale(currentLocale, currentLocaleIso, idAttribute),
...getAlternateOgLocales(locales, currentLocaleIso, idAttribute)
)
Expand All @@ -105,10 +115,26 @@ export function useSetI18nParams(seoAttributes?: SeoAttributesOptions): SetI18nP
}
}

/**
* The `localeHead` function returns localized head properties for locale-related aspects.
*
* @remarks
* The parameter signature of this function is the same as {@link localeHead}.
*
* @param options - An options object, see {@link I18nHeadOptions}
*
* @returns the route object for a given route, the route object is resolved by vue-router rather than just a full route path.
*
* @see {@link localeHead}
*
* @public
*/
export type LocaleHeadFunction = (options: I18nHeadOptions) => ReturnType<typeof localeHead>

/**
* The `useLocaleHead` composable returns localized head properties for locale-related aspects.
*
* @param options - An options, see about details {@link I18nHeadOptions}
* @param options - An options object, see {@link I18nHeadOptions}
*
* @returns The localized {@link I18nHeadMetaInfo | head properties} with Vue `ref`.
*
Expand All @@ -119,8 +145,7 @@ export function useLocaleHead({
addSeoAttributes = false,
identifierAttribute = 'hid'
}: I18nHeadOptions = {}): Ref<I18nHeadMetaInfo> {
const router = useRouter()

const common = initCommonComposableOptions()
const metaObject: Ref<I18nHeadMetaInfo> = ref({
htmlAttrs: {},
link: [],
Expand All @@ -136,12 +161,16 @@ export function useLocaleHead({
}

function updateMeta() {
metaObject.value = localeHead({ addDirAttribute, addSeoAttributes, identifierAttribute }) as I18nHeadMetaInfo
metaObject.value = localeHead(common, {
addDirAttribute,
addSeoAttributes,
identifierAttribute
}) as I18nHeadMetaInfo
}

if (process.client) {
const stop = watch(
() => router.currentRoute.value,
() => common.router.currentRoute.value,
() => {
cleanMeta()
updateMeta()
Expand Down Expand Up @@ -190,7 +219,7 @@ export function useRouteBaseName(): RouteBaseNameFunction {
* The function that resolve locale path.
*
* @remarks
* The parameter sygnatures of this function is same as {@link localePath}.
* The parameter signature of this function is same as {@link localePath}.
*
* @param route - A route location. The path or name of the route or an object for more complex routes.
* @param locale - A locale optional, if not specified, uses the current locale.
Expand All @@ -214,14 +243,14 @@ export type LocalePathFunction = (route: RouteLocation | RouteLocationRaw, local
* @public
*/
export function useLocalePath(): LocalePathFunction {
return localePath
return wrapComposable(localePath)
}

/**
* The function that resolve route.
*
* @remarks
* The parameter sygnatures of this function is same as {@link localeRoute}.
* The parameter signature of this function is same as {@link localeRoute}.
*
* @param route - A route location. The path or name of the route or an object for more complex routes.
* @param locale - A locale optinal, if not specified, uses the current locale.
Expand All @@ -248,14 +277,14 @@ export type LocaleRouteFunction = (
* @public
*/
export function useLocaleRoute(): LocaleRouteFunction {
return localeRoute
return wrapComposable(localeRoute)
}

/**
* The function that resolve locale location.
*
* @remarks
* The parameter sygnatures of this function is same as {@link localeLocation}.
* The parameter signature of this function is same as {@link localeLocation}.
*
* @param route - A route location. The path or name of the route or an object for more complex routes.
* @param locale - A locale optional, if not specified, uses the current locale.
Expand All @@ -279,14 +308,14 @@ export type LocaleLocationFunction = (route: RouteLocationRaw, locale?: Locale)
* @public
*/
export function useLocaleLocation(): LocaleLocationFunction {
return localeLocation
return wrapComposable(localeLocation)
}

/**
* The functin that swtich locale path.
*
* @remarks
* The parameter sygnatures of this function is same as {@link switchLocalePath}.
* The parameter signature of this function is same as {@link switchLocalePath}.
*
* @param locale - A locale optional, if not specified, uses the current locale.
*
Expand All @@ -308,8 +337,9 @@ export type SwitchLocalePathFunction = (locale: Locale) => string
*
* @public
*/

export function useSwitchLocalePath(): SwitchLocalePathFunction {
return switchLocalePath
return wrapComposable(switchLocalePath)
}

/**
Expand Down
23 changes: 1 addition & 22 deletions src/runtime/internal.ts
Expand Up @@ -19,11 +19,10 @@ import {
nuxtI18nOptions,
normalizedLocales
} from '#build/i18n.options.mjs'
import { findBrowserLocale, getLocalesRegex, getI18nTarget } from './routing/utils'

import type { NuxtApp } from '#app'
import type { Locale } from 'vue-i18n'
import type { DetectBrowserLanguageOptions, LocaleObject } from '#build/i18n.options.mjs'
import { findBrowserLocale, getLocalesRegex, getI18nTarget } from './routing/utils'
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router'

export function formatMessage(message: string) {
Expand All @@ -47,26 +46,6 @@ export function defineGetter<K extends string | number | symbol, V>(obj: Record<
Object.defineProperty(obj, key, { get: () => val })
}

export function proxyNuxt<T extends (...args: any) => any>(nuxt: NuxtApp, target: T) {
return function () {
return Reflect.apply(
target,
{
i18n: (nuxt as any).$i18n,
getRouteBaseName: nuxt.$getRouteBaseName,
localePath: nuxt.$localePath,
localeRoute: nuxt.$localeRoute,
switchLocalePath: nuxt.$switchLocalePath,
localeHead: nuxt.$localeHead,
route: (nuxt as any).$router.currentRoute.value,
router: (nuxt as any).$router
},
// eslint-disable-next-line prefer-rest-params
arguments
) as (this: NuxtApp, ...args: Parameters<T>) => ReturnType<T>
}
}

/**
* Parses locales provided from browser through `accept-language` header.
*
Expand Down

0 comments on commit a94d606

Please sign in to comment.