DEV Community

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

Posted on

2

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

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more