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...