Closed AndreyYolkin closed 1 month ago
As workaround, I added this snippet inside d.ts file and it works:
/// <reference types="unplugin-vue-router/client" />
import type { RouteLocation, RouteLocationNormalizedLoaded, RouteLocationRaw, Router } from '#vue-router';
declare module '#i18n' {
export type StubbedLocalePathFunction = (route: RouteLocation | RouteLocationRaw, locale?: Locale) => string;
export declare function useLocalePath(): StubbedLocalePathFunction;
}
Thanks @AndreyYolkin! I modified it a bit, because lately vue-router
started providing typed routes by itself. Your stub wasn't working as is for me, because nuxt-i18n
suffixes route names with ___<locale>
. Hopefully this is useful to anyone needing this.
utils/locales.ts
:
export const locales = [
'nl',
'en',
'fr',
'de',
] as const;
export type AppLocale = typeof locales[number];
/typings/i18n.d.ts
:
import type { RouteNamedMap } from 'vue-router/auto-routes';
import type { RouteLocation, RouteLocationRaw } from 'vue-router';
type RouteName = keyof RouteNamedMap;
type WithoutLocale<T extends string> = T extends `${infer Base}___${AppLocale}` ? Base : T;
type OriginalRouteDefinition<T extends RouteBaseName> = RouteNamedMap[`${T}___${AppLocale}`];
type ReplaceParamsInPath<
Path extends string,
Params extends Record = Record<PropertyKey, never>,
> = Params extends Record<PropertyKey, never>
? Path
: Path extends `${infer Before}:${infer Param}()${infer Rest}`
? ReplaceParamsInPath<`${Before}${Params[Param]}${Rest}`, Omit<Params, Param>>
: Path;
declare global {
type RouteBaseName = WithoutLocale<RouteName>;
}
declare module '#i18n' {
// This makes sure `useLocalePath` uses the typed router, but with every
// route's base name, instead of the `___{locale}` suffixed one.
type StubbedUseLocalePathFunction = <
RouteName,
Locale extends AppLocale = AppLocale,
RouteDefinition = OriginalRouteDefinition<RouteName, Locale>,
const Params = RouteDefinition['params'],
>(
route: RouteName extends RouteBaseName
? (RouteName | {
name: RouteName;
params?: RouteDefinition['params'];
})
: (RouteLocation | RouteLocationRaw),
locale?: Locale
) => RouteName extends RouteBaseName
? ReplaceParamsInPath<RouteDefinition['path'], Params>
: string;
export function useLocalePath (): StubbedLocalePathFunction;
}
It would indeed be very useful to have this built in, so no stubs are needed. It seems to me however, that this might become quite complicated, as vue-router
's internal typed router types seem to be quite entangled with the RouteNamedMap
type. It might need to become a joint effort with the vue-router
team to make this work.
A collaborative route would be the best, with the current state of Nuxt and Vue Router we would need to reimplement type generation and rely on internal types, which could be changed at any time.
I do have a branch that has these types generated, I might implement it at some point under an experimental flag but with no promise that it will keep on working, you can check it out here https://github.com/BobbieGoede/i18n/pull/49.
@Anoesj
export const locales = [ 'nl', 'en', 'fr', 'de', ] as const;
In v9 we've added a generated Locale
type based on all merged configs, should make your types a little bit easier to maintain. Will be publishing a release candidate in the coming weeks (probably).
Oh that's nice, I see you're way ahead of me! Looking forward to it! :smile:
Here's a demo using a preview build (the types only work in the typescript files cause of stackblitz): https://stackblitz.com/edit/bobbiegoede-nuxt-i18n-starter-srohiz?file=nuxt.config.ts&file=composables%2Ftest.ts
There's other stuff not quite working yet, you can track its progress in #3142 and chime in with any feedback!
Nice start! Shouldn't params
be required in the example below, since [slug].vue
is not catch-all, and the slug
param therefore required? Once you uncomment that line, it actually does require you to provide the slug
param, but it should also require params
altogether if the route has non-optional route params.
const test = localePath({
name: 'test-slug',
// params: {}
});
@Anoesj
I thought so too, but the same happens with the types of vue-router
, see https://stackblitz.com/edit/github-woay8w?file=composables%2Ftest.ts.
Hmm yeah, it does result in a runtime error though. That's not very nice. People use TypeScript to prevent runtime errors. I wouldn't mind nuxt-i18n
to be a little more opiniated about this. Maybe raise this as an issue in the vue-router
repo?
Oh I wasn't even aware it threw a runtime error (too focused on the types right now 😅)
I would expect there already being an issue open about this 🤔 I can open one if there isn't one (or if you would like to do so feel free and link back here).
https://github.com/vuejs/router/issues/2372
EDIT: There already seems to be an issue for this: https://github.com/posva/unplugin-vue-router/issues/285
Ah, it's actually not always wrong to omit params
. Let's say you have the following routes in a Nuxt app:
/
(pages/index.vue
)/user/:id
(pages/user/[id]/index.vue
)/user/:id/settings
(pages/user/[id]/settings.vue
)In the component that renders /user/anoesj
, you can use:
<RouterLink :to="{ name: 'user-id-settings' }">
⚙️ {{ $route.params.id }}'s settings
</RouterLink>
Here, we can omit the params without causing a runtime error, as vue-router
will then automatically use any params of the current route that have the same name as the target route. In this case, both routes have an id
param, so it'll automatically fall back to anoesj
and link to /users/anoesj/settings
.
In fact, it doesn't look at the hierarchy of the routes, it just looks for params in the current route with the same name as params in the target route and automatically fall back on the current route's param values.
See https://github.com/posva/unplugin-vue-router/issues/285:
[...] doing router.push({ name: 'route-name' }) for a route like { name: 'route-name', path: '/route/:id' } is valid and can work depending on where you are. This feature might disappear in major versions of Vue Router [...]
I have just published the first release candidate for v9 which includes the experimental typed routes experimental.typedPages
.
Try it out and please open new issues if you experience any with this feature 🙏
Describe the feature
According to the code here, nuxt i18n imports types from
vue-router
package: https://github.com/nuxt-modules/i18n/blob/c43842f11027253c07c45b7a9e8e6ab81bae7d54/src/runtime/composables/index.ts#L24However, nuxt itself registers
#vue-router
alias to handle both situations with enabled/disabled typed routes. Example of usage: https://github.com/nuxt/nuxt/blob/d326e054d372bd2eb5bf75f3feca6a291169ff76/packages/nuxt/src/pages/runtime/utils.ts#L2Since nuxt/i18n is a nuxt module, I suppose we can rely on this alias too and enable type imports by replacing
vue-router
with#vue-router
for types importsAdditional information
Final checks