DEV Community

Cover image for Simple Internationalization (i18n) for Nextjs (App Router) with Server Actions and cookies
Ángel Quiroz
Ángel Quiroz

Posted on

Simple Internationalization (i18n) for Nextjs (App Router) with Server Actions and cookies

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:

Github Repo

First, enable experimental Server Actions feature in next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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("/");
}

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

I using shadcn/ui for UI Components.

The results are in this url:

English

Spanish

Top comments (1)

Collapse
 
lazurey profile image
lazurey

The Github repo address in the blog doesn't work anymore, here the link: github.com/AngelAlexQC/nextjs-i18n...