- Published on
Best Practices for Implementing Internationalization (i18n) in Next.js Static Exports
- Authors
- Name
- adyingdeath
- @adyingdeath
Context
Recently, I decided to rewrite one of my side projects using Next.js. I've been learning Next.js and wanted to practice by cleaning up my old code.
However, I encountered a challenge: while there are many i18n libraries available, few support static exports. I found a few libraries that claimed to support this feature, but their documentation was sparse, with only brief instructions. This led to a lengthy research process.
Here are the solutions I found:
next-international
Note: The following code examples are in Typescript and are for Next.js using the App Router.
Installation
First, install next-international
.
For npm:
npm install next-international
For yarn:
yarn add next-international
server.ts
and client.ts
Create Create the following file structure:
project/
└── locales/
├── server.ts
└── client.ts
server.ts
and client.ts
:
// server.ts
import { createI18nServer } from 'next-international/server'
export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({
en: () => import('./en'),
zh: () => import('./zh'),
});
// client.ts
"use client"
import { createI18nClient } from 'next-international/client'
export const { useI18n, useScopedI18n, I18nProviderClient } = createI18nClient({
en: () => import('./en'),
zh: () => import('./zh'),
})
Prepare the language files
Create file {language name}.ts
under locales
folder. Like:
project/
└── locales/
├── en.ts
└── zh.ts
// Example language file: en.ts
export default {
"header": {
"home": "Home",
"blog": "blog"
},
"siteName": "Adyingdeath Blog"
} as const
Your current file structure looks like this:
project/
└── locales/
├── server.ts
├── client.ts
├── en.ts
└── zh.ts
Modify Your Existing Files
Move all your routes into an app/[locale]/
folder:
// Before moving
project/
└── app/
├── layout.tsx
└── page.tsx
// After moving
project/
└── app/
└── [locale]/
├── layout.tsx
└── page.tsx
Note: The [locale]
placeholder is a literal string, not a variable reference. You should name the folder exactly as [locale]
.
If your app contains client components, wrap the lowest level components with I18nProviderClient
within a layout:
// app/[locale]/layout.tsx
import { I18nProviderClient } from '../../locales/client'
/* other codes */
export default async function RootLayout({
params,
children,
}: Readonly<{
params: Promise<{ locale: string }>;
children: React.ReactNode;
}>) {
const { locale } = await params;
setStaticParamsLocale(locale);
return (
<I18nProviderClient locale={locale}>
{children}
</I18nProviderClient>
);
}
Note: Add a params
property to the layout to capture the [locale]
value from the URL. For example, visiting /en/
sets [locale]
to en
, allowing the layout to access the language parameter via params
.
export default async function RootLayout({
params,
children,
}: Readonly<{
params: Promise<{ locale: string }>;
children: React.ReactNode;
}>)
setStaticParamsLocale
and getStaticParams
Add Add the following:
- Within the root layout, call the
setStaticParamsLocale
function, passing it thelocale
page parameter. - Export a
generateStaticParams
function from the root layout.
Note: You can also add setStaticParamsLocale
and getStaticParams
to individual pages. If you prefer not to add them to the root layout, you can include them in the specific pages where needed. However, keep in mind that these functions can only be used in server components.
// app/[locale]/layout.tsx
import { setStaticParamsLocale } from 'next-international/server'
import { getStaticParams } from '../../locales/server'
export default async function RootLayout({ params: { locale } }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
setStaticParamsLocale(locale);
return (
/* ... */
)
}
export function generateStaticParams() {
return getStaticParams()
}
Now, your layout.tsx
should be something like this:
// app/[locale]/layout.tsx
/* other imports */
import { I18nProviderClient } from '../../locales/client'
import { setStaticParamsLocale } from 'next-international/server'
import { getStaticParams } from '../../locales/server'
/* other code */
export default async function RootLayout({ params: { locale } }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
setStaticParamsLocale(locale);
return (
<I18nProviderClient locale={locale}>
{children}
</I18nProviderClient>
);
}
export function generateStaticParams() {
return getStaticParams()
}
Translate Now
Inside server components:
// Example page.tsx
import { getI18n } from '@/locales/server'
export default async function Page() {
const t = await getI18n();
return (
<div>{t("siteName")}</div>
)
}
Inside client components:
// Example Header.tsx
"use client";
import { useI18n } from '@/locales/client';
export default function Header() {
const t = useI18n();
return (
<div className='w-full flex items-center p-2 gap-4'>
{t("header.blog")}
</div>
)
}
Static Exports
Inside your next.config.ts
, set output
to export
:
const nextConfig: NextConfig = {
/* config options here */
"output": "export",
};
Then, export it to out
folder in your project root:
npm run build