QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.2k stars 52 forks source link

fix: execute function in I18nProviderWrapper instead of I18nProvider #316

Closed Yovach closed 6 months ago

Yovach commented 6 months ago

Closes #290

With this fix, I wasn't able to reproduce the crash.

What is this PR ?

It moves the call of () => import("./en") from I18nProvider to I18nProviderWrapper.

I changed this because according to the React documentation of use hook (https://react.dev/reference/react/use#streaming-data-from-server-to-client), the promise is executed and passed as a prop from the parent but awaited in the child.

vercel[bot] commented 6 months ago

Someone is attempting to deploy a commit to a Personal Account owned by @QuiiBz on Vercel.

@QuiiBz first needs to authorize it.

vercel[bot] commented 6 months ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
next-international ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 21, 2023 5:33pm
Yovach commented 6 months ago

Thanks for the update. Unfortunately adding back the artificial delay shows that we now have a loading state when switching locale in client components, which we don't want:

Screen.Recording.2023-12-23.at.08.57.47.mov I believe we'll need to add back the localesCache. use() always triggers suspense so we should only use it when loading the page for the first time.

Based on your feedback, the I18nProvider now checks if there's a cached value for the current locale, otherwise it'll call the use hook with the import and store the result in the localesCache variable.

I've also added a check in changeLocale to execute the call the import function only if the newLocale is supported.

I think in the client.ts, we can add a parameter here :

// locales/client.ts

export const { useI18n, useScopedI18n, I18nProviderClient, useChangeLocale, defineLocale, useCurrentLocale } =
  createI18nClient(
    {
      en: async (isChanging: boolean = false) => { // <---- HERE
        if (!isChanging) {
          await new Promise(resolve => setTimeout(resolve, 100));
        }
        return import('./en');
      },
      fr: async (isChanging: boolean = false) => { // <---- HERE
        if (!isChanging) {
          await new Promise(resolve => setTimeout(resolve, 100));
        }
        return import('./fr');
      },
    },
    {
      // Uncomment to set base path
      // basePath: '/base',
      // Uncomment to use custom segment name
      // segmentName: 'locale',
      // Uncomment to set fallback locale
      // fallbackLocale: en,
    },
  );

and in the

// packages/next-international/src/app/client/create-use-change-locale.ts
export function createUseChangeLocale<LocalesKeys>(
  useCurrentLocale: () => LocalesKeys,
  locales: ImportedLocales,
  config: I18nClientConfig,
) {
  return function useChangeLocale(changeLocaleConfig?: I18nChangeLocaleConfig) {
    const { push, refresh } = useRouter();
    const currentLocale = useCurrentLocale();
    const path = usePathname();
    // We call the hook conditionally to avoid always opting out of Static Rendering.
    // eslint-disable-next-line react-hooks/rules-of-hooks
    const searchParams = changeLocaleConfig?.preserveSearchParams ? useSearchParams().toString() : undefined;
    const finalSearchParams = searchParams ? `?${searchParams}` : '';

    let pathWithoutLocale = path;

    if (config.basePath) {
      pathWithoutLocale = pathWithoutLocale.replace(config.basePath, '');
    }

    if (pathWithoutLocale.startsWith(`/${currentLocale}/`)) {
      pathWithoutLocale = pathWithoutLocale.replace(`/${currentLocale}/`, '/');
    } else if (pathWithoutLocale === `/${currentLocale}`) {
      pathWithoutLocale = '/';
    }

    return function changeLocale(newLocale: LocalesKeys) {
      const importFnLocale = locales[newLocale as keyof typeof locales];
      if (!importFnLocale) {
        warn(`The locale '${newLocale}' is not supported.`);
        return;
      }

      importFnLocale(true).then(module => { // <---- HERE
        localesCache.set(newLocale as string, module.default);
        push(`/${newLocale}${pathWithoutLocale}${finalSearchParams}`);
        refresh();
      });
    };
  };
}

As we don't need to wait for 100ms when changing the locale from the client