victorgarciaesgi / nuxt-typed-router

🚦Provide autocompletion and typecheck to Nuxt router
https://nuxt-typed-router.vercel.app
MIT License
362 stars 12 forks source link

Bad template generation with i18n module #135

Open skmd87 opened 10 months ago

skmd87 commented 10 months ago

Describe the bug Nuxt won't start when i18n is enabled. the following errors printed in console:

 ERROR(vue-tsc)  ';' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__paths.d.ts:23:8

    21 |
    22 |
  > 23 |   type ValidStringPath<T> = T extends ${string} ${string} ? false : T extends '' ? false : true;
       |        ^^^^^^^^^^^^^^^
    24 |
    25 |   type ValidParam<T, R extends boolean = true> = T extends ${infer A}/${infer B}
    26 |   ? A extends ${string} ${string}

 ERROR(vue-tsc)  ';' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__paths.d.ts:23:31

    21 |
    22 |
  > 23 |   type ValidStringPath<T> = T extends ${string} ${string} ? false : T extends '' ? false : true;
       |                               ^^^^^^^
    24 |
    25 |   type ValidParam<T, R extends boolean = true> = T extends ${infer A}/${infer B}
    26 |   ? A extends ${string} ${string}

 ERROR(vue-tsc)  ';' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__paths.d.ts:23:73

    21 |
    22 |
  > 23 |   type ValidStringPath<T> = T extends ${string} ${string} ? false : T extends '' ? false : true;
       |                                                                         ^^^^^^^
    24 |
    25 |   type ValidParam<T, R extends boolean = true> = T extends ${infer A}/${infer B}
    26 |   ? A extends ${string} ${string}

 ERROR(vue-tsc)  Type expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__paths.d.ts:91:13

    89 |       ? T extends '/'
    90 |         ? "index"
  > 91 |          :  : never
       |             ^
    92 |        : never;
    93 |
    94 |

 ERROR(vue-tsc)  Declaration or statement expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__paths.d.ts:92:8

    90 |         ? "index"
    91 |          :  : never
  > 92 |        : never;
       |        ^
    93 |
    94 |
    95 |

 ERROR(vue-tsc)  ';' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__routes.ts:27:10

    25 |    *
    26 |    * */
  > 27 |   export type RoutesParamsRecord = {
       |          ^^^^
    28 |
    29 |   }
    30 |

 ERROR(vue-tsc)  ';' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__routes.ts:54:10

    52 |    * By default the params are unknown
    53 |    * */
  > 54 |   export type RoutesNamedLocationsResolved =
       |          ^^^^
    55 |   {
    56 |     name: RoutesNamesList;
    57 |     params: unknown;

 ERROR(vue-tsc)  Function type notation must be parenthesized when used in an intersection type.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__routes.ts:58:6

    56 |     name: RoutesNamesList;
    57 |     params: unknown;
  > 58 |   } & (
       |      ^^
  > 59 |
       | ^^^^
  > 60 |       )
       | ^^^^
  > 61 |
       | ^^^^
  > 62 |
       | ^^^^
  > 63 |     export type RoutesNamesListRecord = {};
       | ^^^^^^^^^^^
    64 |
    65 |     export const routesNames = {};
    66 |

 ERROR(vue-tsc)  '=>' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__routes.ts:63:5

    61 |
    62 |
  > 63 |     export type RoutesNamesListRecord = {};
       |     ^^^^^^
    64 |
    65 |     export const routesNames = {};
    66 |

 ERROR(vue-tsc)  ';' expected.
 FILE  C:/www/nuxt3-boilerplate/.nuxt/typed-router/__routes.ts:63:12

    61 |
    62 |
  > 63 |     export type RoutesNamesListRecord = {};
       |            ^^^^
    64 |
    65 |     export const routesNames = {};
    66 |

[vue-tsc] Found 10 errors. Watching for file changes.

Expected behavior to run nuxt normally

Screenshots upon checking the generated .ts files, found those problems: __paths d ts-1 __paths d ts-2 __routes d ts-1 __routes d ts-2

Environnement infos

pages
├── admin │ └── index.vue └── index.vue

Your nuxt.config.ts

modules: [ '@pinia/nuxt', ['@nuxtjs/eslint-module', eslintConfig], ['@nuxtjs/i18n', i18nConfig], '@formkit/auto-animate', '@vueuse/nuxt', ['@nuxt/image', imageConfig], ['nuxt-simple-sitemap', sitemapConfig], ['nuxt-jsonld', jsonldConfig], ['nuxt-simple-robots', robotsConfig], 'nuxt-typed-router', ['@vite-pwa/nuxt', pwaConfig], ],

//i18n config import { type ModuleOptions } from '@nuxtjs/i18n'

const config: ModuleOptions = { defaultLocale: 'en-US', langDir: 'config/i18n/locales/', locales: [ { code: 'en', iso: 'en-US', file: 'en-US.json', }, { code: 'ar', iso: 'ar-JO', file: 'ar-JO.json', }, ], lazy: true,
strategy: 'prefix' }

export default config

victorgarciaesgi commented 10 months ago

Hi! Yeah there is a problem I spotted with the "prefix" strategy i'm sorry for this. I'll have to refactor a significant part of stuff on 4.0 and it's already in progress

skmd87 commented 10 months ago

looks promsing, is there any beta version yet?

victorgarciaesgi commented 10 months ago

Not yet it's not usable, i'll tell you when it's ready. But i'll surely will change how i18n types will work

mseeley commented 8 months ago

I'm also running into the same error. Error is different but still traces to strategy: 'prefix' and nuxt-typed-router.

$ pnpm nuxi prepare

 ERROR  Declaration or statement expected. (158:8)                                                                                                                                                     8:46:52 PM
  156 |         ? "index"
  157 |          : any : never
> 158 |        : never;
      |        ^
  159 |
  160 |
  161 |

    156 |         ? "index"
    157 |          : any : never
  > 158 |        : never;
        |        ^
    159 |
    160 |
    161 |
mseeley commented 8 months ago

What is the roadmap for this module when contrasted with Nuxt's experimental integration of unplugin-vue-router?

danwithabox commented 8 months ago

I second that question. For people ending up here thinking they'd have to use this module to get typed routes, I have successfully started using the experimental.typedPages option with i18n.

To me, that option seems to be the canonic approach, even if it's marked as experimental.

skmd87 commented 5 months ago

I second that question. For people ending up here thinking they'd have to use this module to get typed routes, I have successfully started using the experimental.typedPages option with i18n.

To me, that option seems to be the canonic approach, even if it's marked as experimental.

How did you enable i18n tying with experimental.typedPages ??

danwithabox commented 4 months ago

I second that question. For people ending up here thinking they'd have to use this module to get typed routes, I have successfully started using the experimental.typedPages option with i18n. To me, that option seems to be the canonic approach, even if it's marked as experimental.

How did you enable i18n tying with experimental.typedPages ??

I just enabled the option, and the linked documentation describes what effect it had: https://nuxt.com/docs/guide/going-further/experimental-features#typedpages

(...) this will enable typed usage of navigateTo, , router.push() and more

For other i18n typing support, you have to look elsewhere. I personally found the i18n module's typing support to be lackluster in general, but specifically for links, this experimental option worked well.

szulcus commented 4 months ago

Typed router works with @nuxtjs/i18n in 8.0.0-beta.10 version. Since we don't have v4 yet, I recommend removing the nuxt-typed-router module and writing your own workaround using experimental.typedPages.

In nuxt.config.ts:

export default defineNuxtConfig({
    experimental: {
        payloadExtraction: false,
        typedPages: true,
    },
    //...
});

In index.d.ts:

import type { RouteNamedMap } from 'vue-router/auto/routes';
import type { RouteLocationAsString, RouteLocationAsRelativeTypedList, RouteLocationAsPathTypedList } from 'unplugin-vue-router/types';

// I18n routes
type RemoveUnderscoreKeys<T> = {
    [K in keyof T as K extends `${infer _Prefix}___${infer _Suffix}` ? never : K]: T[K]
};

type NewRouteNamedMap = RemoveUnderscoreKeys<RouteNamedMap>;

type NewRouteLocationRaw<Name extends keyof NewRouteNamedMap = keyof NewRouteNamedMap> =
    | RouteLocationAsString<NewRouteNamedMap>
    | RouteLocationAsRelativeTypedList<NewRouteNamedMap>[Name]
    | RouteLocationAsPathTypedList<NewRouteNamedMap>[Name];

/**
 * The function that resolve locale path.
 *
 * @remarks
 * The parameter sygnatures 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.
 *
 * @returns Returns the localized URL for a given route.
 *
 * @see {@link useLocalePath}
 *
 * @public
 */
type NewLocalePathFunction = (route: NewRouteLocationRaw, locale?: Locale) => string;

declare global {
    /**
     * The `useLocalePath` composable returns a function that resolves a path according to the current locale.
     *
     * @remarks
     * The function returned by `useLocalePath` is the wrapper function with the same signature as {@link localePath}.
    *
    * `useLocalePath` is powered by [vue-i18n-routing](https://github.com/intlify/routing/tree/main/packages/vue-i18n-routing).
    *
    * @param options - An options object, see {@link I18nCommonRoutingOptionsWithComposable}
    *
    * @returns A {@link LocalePathFunction}.
    *
    * @public
    */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    function useLocalePath(options?: any): NewLocalePathFunction;
}

export {};
szulcus commented 4 months ago

@danwithabox yes, @nuxtjs/i18n has many shortcomings, but currently, we don't have any viable alternatives.

skmd87 commented 4 months ago

Typed router works with @nuxtjs/i18n in 8.0.0-beta.10 version. Since we don't have v4 yet, I recommend removing the nuxt-typed-router module and writing your own workaround using experimental.typedPages.

In nuxt.config.ts:

export default defineNuxtConfig({
  experimental: {
      payloadExtraction: false,
      typedPages: true,
  },
  //...
});

In index.d.ts:

import type { RouteNamedMap } from 'vue-router/auto/routes';
import type { RouteLocationAsString, RouteLocationAsRelativeTypedList, RouteLocationAsPathTypedList } from 'unplugin-vue-router/types';

// I18n routes
type RemoveUnderscoreKeys<T> = {
  [K in keyof T as K extends `${infer _Prefix}___${infer _Suffix}` ? never : K]: T[K]
};

type NewRouteNamedMap = RemoveUnderscoreKeys<RouteNamedMap>;

type NewRouteLocationRaw<Name extends keyof NewRouteNamedMap = keyof NewRouteNamedMap> =
  | RouteLocationAsString<NewRouteNamedMap>
  | RouteLocationAsRelativeTypedList<NewRouteNamedMap>[Name]
  | RouteLocationAsPathTypedList<NewRouteNamedMap>[Name];

/**
 * The function that resolve locale path.
 *
 * @remarks
 * The parameter sygnatures 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.
 *
 * @returns Returns the localized URL for a given route.
 *
 * @see {@link useLocalePath}
 *
 * @public
 */
type NewLocalePathFunction = (route: NewRouteLocationRaw, locale?: Locale) => string;

declare global {
  /**
   * The `useLocalePath` composable returns a function that resolves a path according to the current locale.
   *
   * @remarks
   * The function returned by `useLocalePath` is the wrapper function with the same signature as {@link localePath}.
  *
  * `useLocalePath` is powered by [vue-i18n-routing](https://github.com/intlify/routing/tree/main/packages/vue-i18n-routing).
  *
  * @param options - An options object, see {@link I18nCommonRoutingOptionsWithComposable}
  *
  * @returns A {@link LocalePathFunction}.
  *
  * @public
  */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  function useLocalePath(options?: any): NewLocalePathFunction;
}

export {};

Thank you for sharing: I had to do few modification in order to work. here is my final working version:


import type { RouteNamedMap } from 'vue-router/auto/routes';
import type { RouteLocationAsString, RouteLocationAsRelativeTypedList, RouteLocationAsPathTypedList } from 'unplugin-vue-router/types';

// I18n routes
type RemoveUnderscoreKeys<T> = {
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    [K in keyof T as K extends `${infer Prefix}___${infer Suffix}` ? Prefix : K]: T[K]
};

type NewRouteNamedMap = RemoveUnderscoreKeys<RouteNamedMap>;

type NewRouteLocationRaw<Name extends keyof NewRouteNamedMap = keyof NewRouteNamedMap> =
    | RouteLocationAsString<NewRouteNamedMap>
    | RouteLocationAsRelativeTypedList<NewRouteNamedMap>[Name]
    | RouteLocationAsPathTypedList<NewRouteNamedMap>[Name];

/**
 * The function that resolve locale path.
 *
 * @remarks
 * The parameter sygnatures 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.
 *
 * @returns Returns the localized URL for a given route.
 *
 * @see {@link useLocalePath}
 *
 * @public
 */
type NewLocalePathFunction = (route: NewRouteLocationRaw, locale?: Locale) => string;

declare module '../node_modules/@nuxtjs/i18n/dist/runtime/composables/index' {
    /**
     * The `useLocalePath` composable returns a function that resolves a path according to the current locale.
     *
     * @remarks
     * The function returned by `useLocalePath` is the wrapper function with the same signature as {@link localePath}.
    *
    * `useLocalePath` is powered by [vue-i18n-routing](https://github.com/intlify/routing/tree/main/packages/vue-i18n-routing).
    *
    * @param options - An options object, see {@link I18nCommonRoutingOptionsWithComposable}
    *
    * @returns A {@link LocalePathFunction}.
    *
    * @public
    */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    function useLocalePath(options?: any): NewLocalePathFunction;
}

export {};
victorgarciaesgi commented 4 months ago

Hello guys sorry I don't have much time to maintain efficiently this project, Nuxt team is speed running through updates and with a full time job it's hard to keep being aligned. Especially i18n which is a difficult aspect

szulcus commented 4 months ago

@victorgarciaesgi don't worry, I fully understand. It should be part of @nuxtjs/i18n.

szulcus commented 4 months ago

The previous solution stopped working for me. I did some investigation and came up with the following workaround in the index.d.ts file:

// I18n routes

declare module 'vue-router' {
    import { type RouteNamedMap } from 'vue-router/auto-routes';

    type RemoveUnderscoreKeys<T> = {
        [K in keyof T as K extends `${infer Prefix}___${infer _Suffix}` ? Prefix : K]: T[K];
    };

    // Workaround for: https://github.com/victorgarciaesgi/nuxt-typed-router/issues/135
    export declare interface TypesConfig {
        /**
         * Some things don't work:
         * - `<nuxt-link :to="{ name: 'dashboard' }" />` should be the wrong type
         * - `localePath('/dashboard/')` should be the correct type
         * - `localePath('/en-gb/dashboard/')` should be the wrong type
         */
        RouteNamedMap: RemoveUnderscoreKeys<RouteNamedMap>;
    }
}

export {};

This might be somewhat controversial since it overrides a type intended for internal use. Additionally, it causes a few other issues mentioned in the comments. However, I believe it can be a suitable temporary workaround.

exophunk commented 1 month ago

Is there any news if this may be fixed anytime soon?