Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ export function getDefineConfig(options: NuxtI18nOptions, server = false, nuxt =
__ROUTE_NAME_SEPARATOR__: JSON.stringify(options.routesNameSeparator),
__ROUTE_NAME_DEFAULT_SUFFIX__: JSON.stringify(options.defaultLocaleRouteNameSuffix),
__TRAILING_SLASH__: String(options.trailingSlash),
__DEFAULT_DIRECTION__: JSON.stringify(options.defaultDirection)
__DEFAULT_DIRECTION__: JSON.stringify(options.defaultDirection),
__I18N_ROUTE_RESOLUTION__: JSON.stringify(options.experimental?.routeResolutionEnhancement ?? false)
}

if (nuxt.options.ssr || !server) {
Expand Down
3 changes: 2 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export const DEFAULT_OPTIONS = {
localeDetector: '',
typedPages: true,
typedOptionsAndMessages: false,
alternateLinkCanonicalQueries: true
alternateLinkCanonicalQueries: true,
routeResolutionEnhancement: false as false | 'explicit' | 'implicit'
},
bundle: {
compositionOnly: true,
Expand Down
1 change: 1 addition & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ declare let __I18N_STRATEGY__: 'no_prefix' | 'prefix' | 'prefix_except_default'
declare let __ROUTE_NAME_SEPARATOR__: string
declare let __ROUTE_NAME_DEFAULT_SUFFIX__: string
declare let __DEFAULT_DIRECTION__: string
declare let __I18N_ROUTE_RESOLUTION__: boolean | 'implicit' | 'explicit'
17 changes: 16 additions & 1 deletion src/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ declare global {

// prettier-ignore
return `// Generated by @nuxtjs/i18n
import type { ${i18nType} } from 'vue-i18n'
import type { ${i18nType}, Locale } from 'vue-i18n'
import type { ComposerCustomProperties } from '${relative(
join(nuxt.options.buildDir, 'types'),
resolve(runtimeDir, 'types.ts')
Expand Down Expand Up @@ -228,6 +228,21 @@ declare module '#app' {

${typedRouterAugmentations}

declare module 'vue-router' {
interface Router {
resolve<Name extends keyof RouteMap = keyof RouteMap>(
to: RouteLocationAsRelativeTyped<RouteMap, Name>,
currentLocation?: RouteLocationNormalizedLoaded,
options?: { locale?: Locale | boolean }
): RouteLocationResolved<Name>
resolve(
to: RouteLocationAsString | RouteLocationAsRelative | RouteLocationAsPath,
currentLocation?: RouteLocationNormalizedLoaded,
options?: { locale?: Locale | boolean }
): RouteLocationResolved
}
}

${(options.autoDeclare && globalTranslationTypes) || ''}

export {}`
Expand Down
1 change: 1 addition & 0 deletions src/prepare/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export function prepareRuntime(ctx: I18nNuxtContext, nuxt: Nuxt) {
const { options, resolver } = ctx
// for core plugin
addPlugin(resolver.resolve('./runtime/plugins/i18n'))
addPlugin(resolver.resolve('./runtime/plugins/route-resolution-enhancement'))
addPlugin(resolver.resolve('./runtime/plugins/route-locale-detect'))
addPlugin(resolver.resolve('./runtime/plugins/ssg-detect'))
addPlugin(resolver.resolve('./runtime/plugins/switch-locale-path-ssr'))
Expand Down
7 changes: 4 additions & 3 deletions src/runtime/components/NuxtLinkLocale.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { isObject } from '@intlify/shared'
import { useLocalePath, type Locale } from '#i18n'
import { useLocaleRoute, type Locale } from '#i18n'
import { defineComponent, computed, h } from 'vue'
import { defineNuxtLink } from '#imports'
import { hasProtocol } from 'ufo'
Expand Down Expand Up @@ -28,7 +28,7 @@ export default defineComponent<NuxtLinkLocaleProps>({
}
},
setup(props, { slots }) {
const localePath = useLocalePath()
const localeRoute = useLocaleRoute()

// From https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/components/nuxt-link.ts#L57
const checkPropConflicts = (
Expand All @@ -43,7 +43,7 @@ export default defineComponent<NuxtLinkLocaleProps>({

const resolvedPath = computed(() => {
const destination = props.to ?? props.href
return (destination != null ? localePath(destination, props.locale) : destination) as string
return destination != null ? localeRoute(destination, props.locale) : destination
})

// Resolving link type
Expand Down Expand Up @@ -77,6 +77,7 @@ export default defineComponent<NuxtLinkLocaleProps>({
}

if (!isExternal.value) {
// @ts-expect-error type needs to expanded to allow route objects/paths as NuxtLinkProps
_props.to = resolvedPath.value
}

Expand Down
42 changes: 42 additions & 0 deletions src/runtime/plugins/route-resolution-enhancement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { defineNuxtPlugin, useNuxtApp } from '#imports'
import { resolveRoute } from '../routing/routing'
import { useRouter } from 'vue-router'
import { isString } from '@intlify/shared'

import type { Locale } from 'vue-i18n'

type ResolveParams = Parameters<import('#vue-router').Router['resolve']>

export default defineNuxtPlugin({
name: 'i18n:route-resolution-enhancement',
dependsOn: ['i18n:plugin'],
setup() {
const nuxt = /*#__PURE__*/ useNuxtApp()
if (!__I18N_ROUTE_RESOLUTION__) return

const ctx = nuxt._nuxtI18n
const router = useRouter()

/**
* disable enhancement
* - explicit mode without `locale`
* - implicit mode with `locale: false`
*/
const disableEnhancement = (locale?: Locale | boolean) =>
(__I18N_ROUTE_RESOLUTION__ !== 'implicit' && locale == null) || locale === false

const originalResolve = router.resolve.bind(router)
router.resolve = (
to: ResolveParams[0],
currentLocation: ResolveParams[1],
{ locale }: { locale?: Locale | boolean } = {}
) => {
if (disableEnhancement(locale)) {
return originalResolve(to, currentLocation)
}

// if `locale` is `false` or `undefined`, use the current locale
return resolveRoute(ctx, to, isString(locale) ? locale : ctx.getLocale())
}
}
})
10 changes: 5 additions & 5 deletions src/runtime/routing/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,21 @@ function normalizeRawLocation(route: RouteLocationRaw): RouteLike {
}

/**
* Try resolving route and throw on failure
* Resolve route, throws on failure
*/
function resolveRoute(ctx: ComposableContext, route: RouteLocationRaw, locale: Locale) {
export function resolveRoute(ctx: ComposableContext, route: RouteLocationRaw, locale: Locale) {
const normalized = normalizeRawLocation(route)
const resolved = ctx.router.resolve(ctx.resolveLocalizedRouteObject(normalized, locale))
const resolved = ctx.router.resolve(ctx.resolveLocalizedRouteObject(normalized, locale), undefined, { locale: false })
if (resolved.name) {
return resolved
}

// if unable to resolve route try resolving route based on original input
return ctx.router.resolve(route)
return ctx.router.resolve(route, undefined, { locale: false })
}

/**
* Try resolving route and return undefined on failure
* Resolve route, returns undefined on failure
*/
function tryResolveRoute(ctx: ComposableContext, route: RouteLocationRaw, locale: Locale = ctx.getLocale()) {
try {
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ export interface ExperimentalFeatures {
* @default true
*/
alternateLinkCanonicalQueries?: boolean

/**
* Enhance Vue Router's route resolution with localization
*
* @defaultValue `false`
*
* @remark `'explicit'` - resolve localized routes when passing `{ locale: Locale | true }` as third argument to router.resolve.
* @remark `'implicit'` - resolve localized routes by default
*/
routeResolutionEnhancement?: false | 'explicit' | 'implicit'
}

export interface BundleOptions
Expand Down