Documentation

Internationalization (i18n)

Learn how to use and extend the built-in internationalization system powered by next-intl

Dirstarter includes comprehensive internationalization (i18n) support built on top of next-intl. This allows you to create directory websites that support multiple languages, making your content accessible to global audiences.

Overview

The i18n system is configured across two files: lib/i18n-config.ts defines the available locales, and lib/i18n.ts loads the translation messages using static imports. All translations are made available throughout your application.

lib/i18n-config.ts
export const locales = ["en"] as const
export const defaultLocale = "en" as const

export type Locale = (typeof locales)[number]
lib/i18n.ts
import { getRequestConfig } from "next-intl/server"
import { defaultLocale as locale } from "~/lib/i18n-config"
import common from "../messages/en/common.json"
import pages from "../messages/en/pages.json"
// ... other namespace imports (forms, navigation, components, etc.)

export default getRequestConfig(async () => {
  return {
    locale,
    messages: {
      common,
      pages,
      // ... other namespaces
    },
  }
})

Using Translations

Server Components

In server components, use getTranslations() from next-intl/server:

app/(web)/about/page.tsx
import { getTranslations } from "next-intl/server"

export default async function AboutPage() {
  const t = await getTranslations("pages.about")

  return (
    <div>
      <h1>{t("title")}</h1>
      <p>{t("description", { siteName: "My Directory" })}</p>
    </div>
  )
}

Client Components

In client components, use useTranslations() hook:

components/my-component.tsx
"use client"

import { useTranslations } from "next-intl"

export function MyComponent() {
  const t = useTranslations("components.my_component")

  return <button>{t("button_label")}</button>
}

Translation Namespaces

Translations are organized into namespaces based on the JSON file structure:

  • pages.* - Page-specific translations
  • components.* - Component translations
  • forms.* - Form labels and messages
  • navigation.* - Navigation items
  • common.* - Common UI elements

Translation File Structure

Translation files are stored in messages/{locale}/ directories. Each JSON file represents a namespace:

messages/en/pages.json
{
  "about": {
    "title": "About Us",
    "description": "{siteName} is a community driven list of tools."
  },
  "advertise": {
    "title": "Advertise",
    "description": "Promote your business on {siteName}."
  }
}

Adding New Translations

Adding Translation Keys

To add a new translation, simply add the key to the appropriate JSON file:

messages/en/pages.json
{
  "about": {
    "title": "About Us",
    "description": "...",
    "new_section": "New section content"
  }
}

Then use it in your component:

const t = await getTranslations("pages.about")
const content = t("new_section")

Adding New Translation Files

To add a new namespace, create a new JSON file in messages/{locale}/:

messages/en/custom.json
{
  "greeting": "Hello",
  "farewell": "Goodbye"
}

You must also add a static import for the new file in lib/i18n.ts and include it in the messages object.

Adding New Locales

To add support for a new language:

Create locale directory

Create a new directory for your locale in messages/:

mkdir -p messages/es

Copy translation files

Copy all JSON files from your base locale (e.g., messages/en/) to the new locale directory:

cp messages/en/*.json messages/es/

Translate content

Translate all the values in the JSON files to your target language. Keep the keys the same:

messages/es/pages.json
{
  "about": {
    "title": "Acerca de",
    "description": "{siteName} es una lista comunitaria de herramientas."
  }
}

Update locales array

Add your new locale to the locales array in lib/i18n-config.ts:

lib/i18n-config.ts
export const locales = ["en", "es"] as const

Add static imports

Add static imports for all JSON files of the new locale in lib/i18n.ts and include them in the messages object. You will need to add logic to select the correct imports based on the active locale.

Date and Time Formatting

Use useFormatter from next-intl for localized date and time formatting:

import { useFormatter } from "next-intl"

export function DateDisplay({ date }: { date: Date }) {
  const format = useFormatter()

  return (
    <div>
      <p>{format.dateTime(date, { dateStyle: "long" })}</p>
      <p>{format.relativeTime(date)}</p>
    </div>
  )
}

Form Validation with i18n

Validation schemas can be made locale-aware by using translation functions:

server/web/shared/schema.ts
import { z } from "zod"

export const createAdDetailsSchema = (t: (key: string) => string) => {
  return z.object({
    name: z.string().min(1, t("name.required")),
    websiteUrl: z.string().url(t("website_url.invalid")),
  })
}

Then use it in your form:

"use client"

import { useTranslations } from "next-intl"
import { createAdDetailsSchema } from "~/server/web/shared/schema"

export function AdForm() {
  const tSchema = useTranslations("schema")
  const schema = createAdDetailsSchema(tSchema)
  // ... use schema with form
}

Best Practices

  1. Organize by Feature: Group related translations in the same namespace
  2. Use Descriptive Keys: Choose clear, descriptive keys that indicate context
  3. Parameterize Strings: Use placeholders like {name} for dynamic content
  4. Keep Keys Consistent: Use the same key structure across locales
  5. Test All Locales: Ensure translations work correctly in all supported languages

Current Translation Files

The following translation files are included by default:

  • common.json - Common UI elements
  • pages.json - Page-specific content
  • components.json - Component translations
  • forms.json - Form labels and messages
  • navigation.json - Navigation items
  • dialogs.json - Dialog and modal content
  • schema.json - Validation error messages
  • tools.json - Tool-related translations
  • categories.json - Category translations
  • tags.json - Tag translations
  • ads.json - Advertisement content
  • posts.json - Blog post translations
  • brand.json - Brand and site information

The i18n system is fully integrated with Next.js App Router and works seamlessly with server components, client components, and server actions. All translations are type-safe and checked at build time.

Last updated on

On this page

Join hundreds of directory builders

Build your directory, launch, earn

Don't waste time on Stripe subscriptions or designing a pricing section. Get started today with our battle-tested stack and built-in monetization features.

Get Lifetime Access