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 in lib/i18n.ts and uses JSON files to store translations organized by feature area. All translations are loaded dynamically and made available throughout your application.

lib/i18n.ts
import { getRequestConfig } from "next-intl/server"

export const locales = ["en"] as const
export const defaultLocale = "en" as const

export type Locale = (typeof locales)[number]

// Dynamically load all JSON files from the locale directory
const loadMessages = async (locale: Locale) => {
  const messagesPath = join(process.cwd(), "messages", locale)
  const files = glob(`${messagesPath}/*.json`)
  // ... loads all JSON files
}

export default getRequestConfig(async () => {
  const locale = defaultLocale
  const messages = await loadMessages(locale)
  return { locale, messages }
})

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}."
  }
}
messages/en/common.json
{
  "or": "or",
  "built_with": "Built with",
  "verified": "Verified",
  "reset": "Reset",
  "send": "Send",
  "cancel": "Cancel",
  "loading": "Loading...",
  "visit": "Visit {name}"
}

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"
}

The file will be automatically loaded and available as custom.* namespace.

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

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

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
  • emails.json - Email templates
  • 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

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.

Buy Dirstarter Now