# Next.js i18n with next-intl: How prompts.chat Handles Translation Management

> Learn how prompts.chat uses Next.js i18n with next-intl for efficient translation management. Explore server and client-side internationalization techniques.

- Repository: [Fatih Kadir Akın/prompts.chat](https://github.com/f/prompts.chat)
- Tags: deep-dive
- Published: 2026-04-02

---

**The prompts.chat repository implements full-stack internationalization by combining Next.js App Router server components with the next-intl library, using `getLocale()` and `getMessages()` for server-side detection and `NextIntlClientProvider` with `useTranslations()` for client-side consumption.**

The open-source prompts.chat project demonstrates a production-ready approach to Next.js i18n that leverages Next.js 12+ App Router capabilities alongside the next-intl library. This architecture delivers SEO-friendly, server-rendered translations while maintaining reactive client-side language switching capabilities. By examining the actual implementation in [`src/app/layout.tsx`](https://github.com/f/prompts.chat/blob/main/src/app/layout.tsx) and supporting files, we can see how the application manages translation namespaces and handles hydration across the component tree.

## Server-Side Locale Detection in the Root Layout

The internationalization flow begins in [`src/app/layout.tsx`](https://github.com/f/prompts.chat/blob/main/src/app/layout.tsx), where the application detects the user's locale and loads corresponding translation messages before rendering. The code imports `getLocale` and `getMessages` from `next-intl/server` to handle these operations asynchronously during the server render cycle.

According to the prompts.chat source code, the root layout calls `await getLocale()` to determine the current locale (e.g., "en", "es") and `await getMessages()` to load the compiled JSON translation file for that specific language. These values are then passed as props to the `Providers` component, ensuring the entire React tree has access to localization data from the initial HTML payload.

```tsx
// src/app/layout.tsx
import { getMessages, getLocale } from "next-intl/server";
import { Providers } from "@/components/providers";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = await getLocale();          // → "en" | "es" | …
  const messages = await getMessages();     // loads messages/<locale>.json

  return (
    <html lang={locale}>
      <body>
        <Providers
          locale={locale}
          messages={messages}
          // ... other props
        >
          {children}
        </Providers>
      </body>
    </html>
  );
}

```

*Source: [layout.tsx#L147-L199](https://github.com/f/prompts.chat/blob/main/src/app/layout.tsx#L147-L199)*

## Wiring NextIntlClientProvider in the Provider Stack

Inside [`src/components/providers/index.tsx`](https://github.com/f/prompts.chat/blob/main/src/components/providers/index.tsx), the application wraps the children with `NextIntlClientProvider` from next-intl. This client-side provider receives the `locale` and `messages` props from the server, injecting them into the React context tree.

The `NextIntlClientProvider` component makes the translation data available to all descendant client components without prop-drilling. As implemented in prompts.chat, this provider sits alongside other context providers like `SessionProvider` and `ThemeProvider`, ensuring internationalization data persists across authentication and theme state changes.

```tsx
// src/components/providers/index.tsx
"use client";

import { NextIntlClientProvider, AbstractIntlMessages } from "next-intl";

export function Providers({
  children,
  locale,
  messages,
  // ... other props
}: {
  children: React.ReactNode;
  locale: string;
  messages: AbstractIntlMessages;
}) {
  return (
    <SessionProvider>
      <NextIntlClientProvider locale={locale} messages={messages}>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </NextIntlClientProvider>
    </SessionProvider>
  );
}

```

*Source: [providers/index.tsx#L34-L38](https://github.com/f/prompts.chat/blob/main/src/components/providers/index.tsx#L34-L38)*

## Client-Side Translation Consumption with useTranslations

Once the provider is mounted, any client component can import the `useTranslations` hook from next-intl to access localized strings. The prompts.chat implementation in [`src/components/prompts/prompt-card.tsx`](https://github.com/f/prompts.chat/blob/main/src/components/prompts/prompt-card.tsx) demonstrates how to scope translations to specific namespaces.

The hook returns a function `t(key)` that looks up the appropriate string in the loaded messages object. By calling `useTranslations("prompts")`, components access only the "prompts" namespace, while `useTranslations("common")` provides shared UI strings like button labels and navigation elements.

```tsx
// src/components/prompts/prompt-card.tsx
import { useTranslations } from "next-intl";

export function PromptCard(/* props */) {
  const t = useTranslations("prompts");       // namespace = "prompts"
  const tCommon = useTranslations("common"); // shared UI strings

  return (
    <div>
      <h2>{t("title")}</h2>
      <button>{tCommon("edit")}</button>
    </div>
  );
}

```

*Source: [prompt-card.tsx#L90-L91](https://github.com/f/prompts.chat/blob/main/src/components/prompts/prompt-card.tsx#L90-L91)*

## Server Component Rendering with getTranslations

For server-only pages in the Next.js App Router, prompts.chat uses `getTranslations` from `next-intl/server` instead of the client hook. This function returns a translation function that can be awaited during the server render phase, ensuring the correct language is baked into the HTML before reaching the client.

In [`src/app/page.tsx`](https://github.com/f/prompts.chat/blob/main/src/app/page.tsx), the homepage implementation calls `await getTranslations("homepage")` to acquire a translator function for the homepage namespace. This approach eliminates client-side hydration mismatches for static content while improving initial page load performance.

```tsx
// src/app/page.tsx (homepage)
import { getTranslations } from "next-intl/server";

export default async function HomePage() {
  const tHomepage = await getTranslations("homepage");
  const tNav = await getTranslations("nav");

  return (
    <main>
      <h1>{tHomepage("welcome")}</h1>
      <nav>{tNav("explore")}</nav>
    </main>
  );
}

```

*Source: [app/page.tsx#L22-L23](https://github.com/f/prompts.chat/blob/main/src/app/page.tsx#L22-L23)*

## Translation File Structure and Namespaces

All translation data in prompts.chat resides in the `messages/` directory as JSON files named by locale (e.g., [`messages/en.json`](https://github.com/f/prompts.chat/blob/main/messages/en.json), [`messages/es.json`](https://github.com/f/prompts.chat/blob/main/messages/es.json)). Each file follows a flat object structure organized by namespaces, where each namespace contains key-value pairs for specific UI sections.

The server loads the matching JSON file based on the detected locale and supplies it to `NextIntlClientProvider`. This creates a single source of truth for translations that both server components (via `getTranslations`) and client components (via `useTranslations`) access through their respective APIs.

## End-to-End Request Flow

The complete Next.js i18n implementation in prompts.chat follows this lifecycle:

1. An HTTP request arrives with a locale indicator (e.g., `/es` or header-based detection)
2. `getLocale()` determines the active language in [`src/app/layout.tsx`](https://github.com/f/prompts.chat/blob/main/src/app/layout.tsx)
3. `getMessages()` reads the corresponding `messages/<locale>.json` file
4. The `Providers` component wraps the application with `NextIntlClientProvider`, injecting locale data
5. Server components use `getTranslations()` to render localized HTML immediately
6. Client components hydrate and access the same translations via `useTranslations()` without additional network requests

Because the provider mounts once at the root level, every component—regardless of depth—shares the same locale context without prop-drilling or re-fetching translation files.

## Summary

- **Server-side detection**: `getLocale()` and `getMessages()` from `next-intl/server` run in [`src/app/layout.tsx`](https://github.com/f/prompts.chat/blob/main/src/app/layout.tsx) to detect language and load JSON translations during the initial render
- **Provider architecture**: `NextIntlClientProvider` in [`src/components/providers/index.tsx`](https://github.com/f/prompts.chat/blob/main/src/components/providers/index.tsx) hydrates the React tree with locale data, enabling client-side access
- **Client hooks**: `useTranslations()` allows client components like [`prompt-card.tsx`](https://github.com/f/prompts.chat/blob/main/prompt-card.tsx) to access namespaced strings with full type safety
- **Server rendering**: `getTranslations()` provides asynchronous translation functions for server components, ensuring SEO-friendly markup
- **File organization**: All translations live in `messages/*.json` files, organized by namespace for maintainable localization management

## Frequently Asked Questions

### What is the difference between getTranslations and useTranslations in next-intl?

**`getTranslations`** is an asynchronous server-side function imported from `next-intl/server` that returns a translation function during the React Server Component render phase. It ensures translations are embedded in the initial HTML. **`useTranslations`** is a client-side React hook that accesses the context provided by `NextIntlClientProvider` and is used exclusively in client components marked with the `"use client"` directive.

### How does prompts.chat detect the user's locale without middleware?

The repository leverages Next.js 12+ App Router's built-in internationalization routing capabilities combined with next-intl's `getLocale()` function. In [`src/app/layout.tsx`](https://github.com/f/prompts.chat/blob/main/src/app/layout.tsx), the locale is determined automatically based on the URL path or domain configuration, then passed to the provider stack without requiring custom middleware for basic locale detection.

### Where are translation files stored in the prompts.chat repository?

All translation files are stored in the `messages/` directory at the project root. Each supported language has its own JSON file (e.g., [`messages/en.json`](https://github.com/f/prompts.chat/blob/main/messages/en.json), [`messages/es.json`](https://github.com/f/prompts.chat/blob/main/messages/es.json)) containing flat objects organized by namespaces like "prompts", "common", "homepage", and "nav".

### Can I use next-intl with the Next.js Pages Router, or is it App Router only?

While the prompts.chat implementation uses the **App Router**, next-intl supports both App Router and Pages Router architectures. In the Pages Router, you would typically use `getStaticProps` or `getServerSideProps` to load messages and pass them to `NextIntlClientProvider` in your custom [`_app.tsx`](https://github.com/f/prompts.chat/blob/main/_app.tsx) file, rather than using the async `getTranslations` function available only in Server Components.