I write a simple implementation for translations using only server actions and cookies in Next.js 13 with App Router and Server Actions enabled.
If do you want the source code:
First, enable experimental Server Actions feature in next.config.js
:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
};
module.exports = nextConfig;
Then, create a simple file with translations:
src/lib/i18n.ts
export enum Locale {
en = "en",
es = "es",
}
export const DEFAULT_LOCALE = Locale.en;
export const translations = {
[Locale.en]: {
home: {
title: "Simple i18n example with Next.js Server Actions",
paragraph:
"This example provides a simple i18n implementation with Next.js Server Actions using cookies.",
button: "Join the waitlist",
subtitle: "Join the waitlist to get early access to the app.",
terms: "Terms & Conditions",
placeholder: "Enter your email",
},
buttons: {
en: "English",
es: "Spanish",
},
},
[Locale.es]: {
home: {
title: "Ejemplo simple de i18n con Next.js Server Actions",
paragraph:
"Este ejemplo proporciona una implementación simple de i18n con Next.js Server Actions usando cookies.",
button: "Únete a la lista de espera",
subtitle:
"Únete a la lista de espera para obtener acceso anticipado a la aplicación.",
terms: "Términos y Condiciones",
placeholder: "Ingresa tu email",
},
buttons: {
en: "Inglés",
es: "Español",
},
},
};
export type TranslationKey = keyof (typeof translations)[Locale];
Next, create a file for server action for set cookie and redirect to home:
src/app/actions.ts
"use server";
import { DEFAULT_LOCALE, Locale } from "@/lib/i18n";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export async function changeLocale(formData: FormData) {
const localeValue = formData.get("locale");
if (!localeValue || typeof localeValue !== "string") {
return redirect("/");
}
const locale = localeValue as Locale;
if (!(locale in Locale)) {
cookies().set("locale", DEFAULT_LOCALE);
return redirect("/");
}
cookies().set("locale", localeValue);
return redirect("/");
}
Finally, read cookies for get translation in any page:
src/app/page.tsx
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import Link from "next/link";
import { changeLocale } from "./actions";
import { cookies } from "next/headers";
import { Locale, translations } from "@/lib/i18n";
import type { Metadata, ResolvingMetadata } from "next";
type Props = {
params: { id: string };
searchParams: { [key: string]: string | string[] | undefined };
};
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const cookieStore = cookies();
const locale = (cookieStore.get("locale")?.value || "en") as Locale;
return {
title: translations[locale].home.title,
};
}
export default function Home() {
const cookieStore = cookies();
const locale = (cookieStore.get("locale")?.value || "en") as Locale;
return (
<main className="w-full h-screen py-12 md:py-24 lg:py-32 xl:py-48 bg-black">
<div className="container px-4 md:px-6">
<div className="grid gap-6 items-center">
<div className="flex flex-col justify-center space-y-4 text-center">
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tighter sm:text-5xl xl:text-6xl/none bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-500">
{translations[locale].home.title}
</h1>
<p className="max-w-[600px] text-zinc-200 md:text-xl dark:text-zinc-100 mx-auto">
{translations[locale].home.paragraph}
</p>
</div>
<div className="w-full max-w-sm space-y-2 mx-auto">
<form className="flex flex-col sm:flex-row items-center gap-2">
<Input
className="max-w-lg flex-1 bg-gray-800 text-white border-gray-900"
placeholder={translations[locale].home.placeholder}
type="email"
/>
<Button className="bg-white text-black" type="submit">
{translations[locale].home.button}
</Button>
</form>
<p className="text-xs text-zinc-200 dark:text-zinc-100 space-x-2">
<span>{translations[locale].home.subtitle}</span>
<Link
className="underline underline-offset-2 text-white"
href="/terms">
{translations[locale].home.terms}
</Link>
</p>
<form action={changeLocale} method="post">
<input type="hidden" name="locale" value="en" />
<button type="submit">{translations[locale].buttons.en}</button>
</form>
<form action={changeLocale} method="post">
<input type="hidden" name="locale" value="es" />
<button type="submit">{translations[locale].buttons.es}</button>
</form>
</div>
</div>
</div>
</div>
</main>
);
}
I using shadcn/ui for UI Components.
The results are in this url:
Top comments (1)
The Github repo address in the blog doesn't work anymore, here the link: github.com/AngelAlexQC/nextjs-i18n...