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.
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:
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}."
}
}{
"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:
{
"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"
}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/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.ts:
export const locales = ["en", "es"] as constDate 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 contentemails.json- Email templatesschema.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