Implementing Multilingual i18n with Next.js 14
Introduction
๐This article introduces how to set up multilingual functionality in a Next.js project, including setting the default language, managing and reading language data, and dynamically rendering content based on the user's requested language. The default language in the example is English (en), with support for Traditional Chinese (zh-tw).
Setup Steps
๐1. Define Language Configuration
๐Create i18n.config.ts
in the root directory.
tsexport const i18n = {
defaultLocale: 'en',
locales: ['en', 'zh-tw']
} as const
export type Locale = (typeof i18n)['locales'][number]
Set the website's language and the default language.
2. Language Data Files
๐Create display information corresponding to each language (based on your own requirements).
src\dictionaries\zh-tw.json
src\dictionaries\en.json
Ex:
JSON{
"layout": {
"brand": "Sun Note",
"recentposts": "Recent Posts",
"readmore": "Read more",
"allposts": "All POSTS",
"recentprojects": "Recent Projects",
"categories": "CATEGORIES",
"tags": "TAGS",
"lastupdated":"Last updated"
}
}
Then, display the corresponding language data based on the key.
3. Function to Get Language Data
๐Create a function to retrieve language content.
src\lib\dictionary.ts
tsimport 'server-only'
import type { Locale } from '../../i18n.config'
const dictionaries: { [key: string]: () => Promise<any> } = {
'en': () => import('@/dictionaries/en.json').then(module => module.default),
'zh-tw': () => import('@/dictionaries/zh-tw.json').then(module => module.default)
}
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
Next, to implement multilingual functionality on a page, use getDictionary()
to obtain the data for the corresponding language.
JSimport { getDictionary } from '@/lib/dictionary'
const { layout } = await getDictionary(lang);
4. Next.js Dynamic Routing
๐To obtain language data, it can be retrieved through the website's path using Next.js dynamic routing. Create [lang]
in src\app\[lang]
, resulting in the actual project structure:
luamy-next-app/
โโโ node_modules/
โโโ public/
โ โโโ favicon.ico
โ โโโ vercel.svg
โโโ src/
โ โโโ app/
โ โ โโโ [lang]
โ โ โโโ globals.css
โ โ โโโ layout.tsx
โ โ โโโ page.tsx
โ โโโ lib/
โ โ โโโ dictionary.ts
โ โโโ components/
โ โโโ middleware.ts
โโโ .gitignore
โโโ i18n.config.ts
โโโ package.json
โโโ README.md
โโโ next.config.js
Pages under [lang]
can then obtain the corresponding language through the URL.
5. Reading User's Default Language
๐To determine the user's preferred system language and display the corresponding data, negotiator
can be used to obtain language information from the Header.
It needs to be used in middleware, create src\middleware.ts
Code
JSimport { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { i18n } from '../i18n.config'
import { match as matchLocale } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
/**
* Retrieves the preferred language from the request headers
* @param request - The incoming request
* @returns The matched language or undefined
*/
function getLocale(request: NextRequest): string | undefined {
const negotiatorHeaders: Record<string, string> = {}
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value))
// @ts-ignore locales are readonly
const locales: string[] = i18n.locales
const languages = new Negotiator({ headers: negotiatorHeaders }).languages()
const locale = matchLocale(languages, locales, i18n.defaultLocale)
return locale
}
/**
* Middleware function for handling language redirection
* @param request - The incoming request
* @returns A redirect response if needed
*/
export function middleware(request: NextRequest) {
// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl
const pathnameHasLocale = i18n.locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (pathnameHasLocale) {
if(pathname.startsWith('/en')){
const newPathname = pathname.replace(/^\/en/, '');
request.nextUrl.pathname = newPathname
return NextResponse.redirect(request.nextUrl)
}
return
}
// Rewrite to en/ content if there is no locale
// const locale = getLocale(request)
request.nextUrl.pathname = `/${i18n.defaultLocale}${pathname}`
return NextResponse.rewrite(request.nextUrl)
}
export const config = {
// Matcher ignoring `/_next/` and `/api/`
matcher: ['/((?!api|_next/static|img|pics|_next/image|favicon.ico).*)']
}
Detailed Explanation:
getLocale()
can obtain the user's preferred language from the request. If it does not match the website's available languages, it returns the default language.
The logic of middleware
mainly involves checking the URL based on the request to see if it contains a language code. If the language code does not match any of the languages offered by the website, it redirects to the default language.
Since the default language of the website is English
en
, we do not wanten
to appear in the URL. Therefore, some modifications were made. If the URL containsen
, it redirects to a URL without a language code. If the original URL does not contain any language information, it rewrites the content corresponding toen
. The entire middleware does not usegetLocale()
because we want the content corresponding to the URL to be displayed without redirection.
Conclusion
๐Through the methods described above, multilingual support is implemented, and content corresponding to the user's requested language is dynamically presented, enhancing the user experience. Finally, a slight modification was made in the middleware to allow users to access the site through a URL without a language code and still display the default language content without encountering a page not found error.
Reference
Alvin
Software engineer, interested in financial knowledge, health concepts, psychology, independent travel, and system design.
Related Posts
Discussion (0)
No comments yet.