Internationalization in Next 14 without using third-party libraries
There are many third-party internationalization libraries you can use in the Next.js 14 project. You will be able to encounter hydration failure in some of them while using them. I would like to tell you about the structure I use in my own projects. You do not need to install any third party libraries in this structure. There is no complex code structure. You can add as many languages as you want. You can run these languages on both the server side and the client side without any problems.
You can visit the address below for the source code
First, let's create a new next js project
npx create-next-app@latest newproject
Then, let's create a directory named "lang" in the main directory of the project and create the relevant files in it.
├── lang
│ ├── locales
│ │ ├── de.json
│ │ ├── en.json
│ ├── clientSide.tsx
│ ├── serverSide.tsx
│ ├── languages.tsx
We use the following codes to import language files on the server side.
//serverSide.tsx
import "server-only";
import { cookies } from "next/headers";
import en from "./locales/en.json";
import de from "./locales/de.json";
import { Languages, Locale } from "./languages";
const dictionaries = {
en,
de,
};
export const getLang = () => {
const hasLang = Languages.locales.some((item) => item === getCookie("lang"));
//@ts-ignore
const lang: Locale = hasLang ? getCookie("lang") : Languages.defaultLocale;
return lang;
};
export const getTranslate = () => dictionaries[getLang()];
We use the following codes to import language files on the client side.
//clientSide.tsx
"use client";
import { getCookie } from "cookies-next";
import en from "./locales/en.json";
import de from "./locales/de.json";
import { Languages, Locale } from "./languages";
const dictionaries = {
en,
de,
};
export const getLang = () => {
const hasLang = Languages.locales.some((item) => item === getCookie("lang"));
//@ts-ignore
const lang: Locale = hasLang ? getCookie("lang") : Languages.defaultLocale;
return lang;
};
export const getTranslate = (lang: Locale) => dictionaries[lang];
The getLang
function found here allows us to find out what the active language is on the client side.
On the server side, we can learn the active language both with the getLang
function and via params.
We define all our languages in the "languages.tsx" file below.
//languages.tsx
export const Languages = {
defaultLocale: "en",
locales: ["en", "de"],
} as const;
export type Locale = (typeof Languages)["locales"][number];
Finally, when we enter the address of our website, we create our "middleware.ts" file, which will direct us to the relevant language address.
//middleware.ts
import { NextResponse } from "next/server";
import { Languages, Locale } from "@/lang/languages";
export const config = {
matcher: "/((?!api|_next/static|_next/image|robot.txt).*)",
};
const cookieName = "lang";
export function middleware(req: any) {
let lang: Locale = Languages.defaultLocale;
if (req.cookies.has(cookieName)) {
const hasLang = Languages.locales.some(
(item) => item === req.cookies.get(cookieName)
);
lang = hasLang ? req.cookies.get(cookieName) : Languages.defaultLocale;
}
if (
!Languages.locales.some((loc) =>
req.nextUrl.pathname.startsWith(`/${loc}`)
) &&
!req.nextUrl.pathname.startsWith("/_next") &&
!req.nextUrl.pathname.startsWith("/api/") &&
!req.nextUrl.pathname.startsWith("/images/")
) {
return NextResponse.redirect(
new URL(`/${lang}${req.nextUrl.pathname}`, req.url)
);
}
if (req.headers.has("referer")) {
const response = NextResponse.next();
return response;
}
return NextResponse.next();
}
In this file, we also specify the folders that will be exempt from the language url.
You can visit the address below for the source code
You can test that the project works flawlessly on both the server side and the client side.