Overview

This guide shows how to integrate Unlingo with Next.js applications using next-intl.

Prerequisites

  • Next.js 13+ application
  • Basic knowledge of Next.js
  • An Unlingo project with translations

Installation

Install the required dependencies:
npm install next-intl

Project Structure

Set up your Next.js project structure for internationalization:
src/
├── app/
│   ├── [locale]/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   └── dashboard/
│   │       └── page.tsx
│   └── layout.tsx
├── i18n/
│   ├── request.ts
│   └── routing.ts
└── middleware.ts

Routing Configuration

Configure your locales and routing:
import { defineRouting } from 'next-intl/routing';

export const routing = defineRouting({
    // A list of all locales that are supported
    locales: ['en', 'es', 'fr', 'de'],

    // Used when no locale matches
    defaultLocale: 'en',
});

Request Configuration

Create a request configuration that fetches translations from Unlingo:
import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { routing } from './routing';

export default getRequestConfig(async ({ requestLocale }) => {
    // This typically corresponds to the `[locale]` segment
    const requested = await requestLocale;
    const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;

    try {
        // Fetch translations from Unlingo
        const url = new URL('/v1/translations', 'https://api.unlingo.com');
        url.searchParams.set('release', process.env.UNLINGO_RELEASE_TAG);
        url.searchParams.set('namespace', process.env.UNLINGO_NAMESPACE);
        url.searchParams.set('lang', locale);

        const response = await fetch(url.toString(), {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${process.env.UNLINGO_API_KEY}`,
                'Content-Type': 'application/json',
            },
            // Cache for 5 minutes
            next: { revalidate: 300 },
        });

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const messages = await response.json();

        return {
            locale,
            messages,
        };
    } catch (error) {
        console.error('Failed to load translations:', error);

        // Fallback to empty messages or default translations
        return {
            locale,
            messages: {},
        };
    }
});

Environment Variables

Add your Unlingo credentials to your environment file:
.env.local
UNLINGO_API_KEY=your_api_key_here
UNLINGO_RELEASE_TAG=1.0.0
UNLINGO_NAMESPACE=translation

Middleware Configuration

Set up middleware to handle locale detection and routing:
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';

export default createMiddleware(routing);

export const config = {
    // Match only internationalized pathnames
    matcher: ['/', '/(de|en|es|fr)/:path*'],
};

Next.js Configuration

Update your next.config.js to include the internationalization plugin:
const withNextIntl = require('next-intl/plugin')(
    // This is the default (also the `src` folder is supported out of the box)
    './src/i18n/request.ts'
);

module.exports = withNextIntl({
    // Other Next.js configuration
});

Root Layout

Set up the root layout:
import { ReactNode } from 'react';

type Props = {
    children: ReactNode;
};

// Since we have a `not-found.tsx` page on the root, a layout file
// is required, even if it's just passing children through.
export default function RootLayout({ children }: Props) {
    return children;
}

Locale Layout

Create a layout for your localized pages:
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';

export default async function LocaleLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  // Ensure that the incoming `locale` is valid
  if (!routing.locales.includes(locale as any)) {
    notFound();
  }

  // Providing all messages to the client
  // side is the easiest way to get started
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

Using Translations in Server Components

Basic Usage

import { getTranslations } from 'next-intl/server';

export default async function HomePage() {
  const t = await getTranslations('common');

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

With Interpolation

import { getTranslations } from 'next-intl/server';

export default async function UserPage({ params }: { params: { userId: string } }) {
  const t = await getTranslations('user');
  const user = await getUserById(params.userId);

  return (
    <div>
      <h1>{t('greeting', { name: user.name })}</h1>
      <p>{t('lastSeen', { date: new Date(user.lastSeen) })}</p>
    </div>
  );
}

Using Translations in Client Components

For client components, use the useTranslations hook:
'use client';

import { useLocale, useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';

export default function LanguageSwitcher() {
  const t = useTranslations('common');
  const locale = useLocale();
  const router = useRouter();

  const languages = [
    { code: 'en', name: 'English' },
    { code: 'es', name: 'Español' },
    { code: 'fr', name: 'Français' },
    { code: 'de', name: 'Deutsch' }
  ];

  const changeLanguage = (newLocale: string) => {
    router.push(`/${newLocale}`);
  };

  return (
    <div className="language-switcher">
      <label>{t('selectLanguage')}</label>
      <select
        value={locale}
        onChange={(e) => changeLanguage(e.target.value)}
      >
        {languages.map((lang) => (
          <option key={lang.code} value={lang.code}>
            {lang.name}
          </option>
        ))}
      </select>
    </div>
  );
}

Advanced Features

Multiple Namespaces

Configure multiple namespaces in your request configuration:
// Enhanced i18n/request.ts
export default getRequestConfig(async ({ requestLocale }) => {
    const requested = await requestLocale;
    const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;

    const namespaces = ['common', 'navigation', 'dashboard', 'auth'];
    const messages = {};

    // Fetch all namespaces in parallel
    await Promise.all(
        namespaces.map(async namespace => {
            try {
                const url = new URL('/v1/translations', 'https://api.unlingo.com');
                url.searchParams.set('release', process.env.UNLINGO_RELEASE_TAG);
                url.searchParams.set('namespace', namespace);
                url.searchParams.set('lang', locale);

                const response = await fetch(url.toString(), {
                    method: 'GET',
                    headers: {
                        Authorization: `Bearer ${process.env.UNLINGO_API_KEY}`,
                        'Content-Type': 'application/json',
                    },
                    next: { revalidate: 300 },
                });

                if (response.ok) {
                    const namespaceMessages = await response.json();
                    messages[namespace] = namespaceMessages;
                }
            } catch (error) {
                console.error(`Failed to load ${namespace} namespace:`, error);
                messages[namespace] = {};
            }
        })
    );

    return {
        locale,
        messages,
    };
});

Next Steps