I used to implement I18n with next-i18next in Next.js. However, after upgrading Next.js 13, there is no need for next-i18next. Instead, you can directly use i18next and react-i18next by this tutorial. Meanwhile, the official doc shows how to use internationalization in app dir. and also provide an example of using next-intl. In this blog, I’ll demostrate how to use next-intl in Next.js 13 app dir.
In settings.ts, I have set locales and ns namespaces, which facilitate the easy addition of more locales or page-specific namespaces. Also, I’ve defined NsType as namespaces type and defaultLocale as en in this file.
constIntlWrapper = async ({ children, // will be a page or nested layout locale, namespaces = [...ns], }: { children: React.ReactNode; locale: string; namespaces?: NsType[]; }) => { let messages; try { messages = awaitgetTranslationJson(locale, namespaces); } catch (error) { notFound(); } return ( <NextIntlClientProviderlocale={locale}messages={messages}> {children} </NextIntlClientProvider> ); };
exportdefaultIntlWrapper;
Nothing special. just import NextIntlClientProvider and put children into it, and I can import IntlWrapper in the layout.tsx or page.tsx files. You may notice the getTranslationJson function, which is imported from utils. The utils could have the following structure:
According to locales, the getTranslationJson works as server side function to import multiple namespaces json sequentially. The json files could look like this:
exportdefaultcreateMiddleware({ locales, defaultLocale }); exportconst config = { // Skip all paths that should not be internationalized matcher: ['/((?!api|_next|.*\\..*).*)'] };
The createMiddleware handles accessing cookies NEXT_LOCALE, parsing headers, and redirecting to NEXT_LOCALE langs. Right now, settings and middleware are ready, so let’s start looking my app [locale] dir!
exportdefaultasyncfunctionMainLayout({ children, // will be a page or nested layout params, }: { children: React.ReactNode; params: { locale: string }; }) { return ( <IntlWrapperlocale={params.locale}namespaces={['common']}> <Layout> <mainstyle={{minHeight: 'calc(100vh-70px)', marginTop: '69px' }}>{children}</main> </Layout> </IntlWrapper> ); }
The params comes from dynamic routes. As you see, I pass params.locale and ['common'] into IntlWrapper props. Thanks for next-intl, now I can simply use useTranslations.
// DesktopLinks is one of the child components of Layout component exportconstDesktopLinks = memo(functionDesktopLinks() { const t = useTranslations('common'); const links = AppBarRoutes;
Lastly, let me describe how I change locale. I have a handleChangeLocale function, so that I can set cookie in this function and redirect to correct page.
I have converted one of my projects to use the Next.js 13 application directory structure and migrated from next-i18next to next-intl. The official documentation seems to suggest combining all namespaces together, but with my getTranslationJson function, I can separate them into multiple namespaces, similar to next-i18next.