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.
export const locales = ["en"] as const
export const defaultLocale = "en" as const
export type Locale = (typeof locales)[number]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:
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:
"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 translationscomponents.*- Component translationsforms.*- Form labels and messagesnavigation.*- Navigation itemscommon.*- Common UI elements
Translation File Structure
Translation files are stored in messages/{locale}/ directories. Each JSON file represents a namespace:
{
"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:
{
"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}/:
{
"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/esCopy 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:
{
"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:
export const locales = ["en", "es"] as constAdd 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:
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
- Organize by Feature: Group related translations in the same namespace
- Use Descriptive Keys: Choose clear, descriptive keys that indicate context
- Parameterize Strings: Use placeholders like
{name}for dynamic content - Keep Keys Consistent: Use the same key structure across locales
- 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 elementspages.json- Page-specific contentcomponents.json- Component translationsforms.json- Form labels and messagesnavigation.json- Navigation itemsdialogs.json- Dialog and modal contentschema.json- Validation error messagestools.json- Tool-related translationscategories.json- Category translationstags.json- Tag translationsads.json- Advertisement contentposts.json- Blog post translationsbrand.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