Documentation
Adding Translations
Step-by-step guide for translating UI copy in Motoko Base features using next-intl message catalogs.
This guide walks through adding or updating translated strings in Motoko Base. Read Internationalization first if you need the routing and locale-resolution overview.
Quick checklist
When you add UI copy to a feature:
- Create or extend a JSON namespace under
messages/en/ - Export it from
messages/en/index.ts - Mirror the same keys in
messages/es/,messages/de/,messages/fr/, andmessages/pt-BR/ - Replace hardcoded strings in components with
useTranslations()orgetTranslations() - Use
formatDate()/formatNumber()from@/lib/i18n/formatfor locale-aware formatting
1. Define English messages
Add a namespace file for your feature. Follow the existing pattern — flat or nested keys, consistent naming:
// messages/en/my-feature.json
{
"page": {
"title": "My Feature",
"description": "Manage your items."
},
"actions": {
"create": "Create item",
"delete": "Delete"
},
"empty": "No items yet."
}Register the namespace in the locale index:
// messages/en/index.ts
import myFeature from "./my-feature.json";
const messages = {
// …existing namespaces
myFeature,
};
export default messages;Use camelCase for the export key (myFeature) — that becomes the namespace passed to useTranslations("myFeature").
2. Mirror keys in every locale
Each locale folder must expose the same key structure. Untranslated locales can temporarily copy English values, but every key must exist to avoid runtime missing-message errors.
Repeat for messages/es/index.ts, messages/de/index.ts, messages/fr/index.ts, and messages/pt-BR/index.ts.
Tip: when iterating quickly, copy the English JSON to other locales first, then translate in a second pass.
3. Use translations in a client component
Most feature pages are client components. Import useTranslations from next-intl:
"use client";
import { useTranslations } from "next-intl";
import { PageHeader } from "@/components/layout/page-header";
export function MyFeaturePage() {
const t = useTranslations("myFeature");
return (
<div className="p-6 md:p-8">
<PageHeader
title={t("page.title")}
description={t("page.description")}
/>
<p>{t("empty")}</p>
</div>
);
}Interpolation
ICU-style placeholders work out of the box:
{
"welcome": {
"title": "Welcome back, {name}"
}
}t("welcome.title", { name: firstName })Dynamic keys
When keys come from data (e.g. stat IDs), cast carefully or use a typed map — see src/features/dashboard/components/chat/chat-page.tsx for a reference pattern.
4. Use translations on the server
For Server Components, layouts, and generateMetadata:
import { getTranslations } from "next-intl/server";
export async function generateMetadata() {
const t = await getTranslations("myFeature");
return {
title: t("page.title"),
description: t("page.description"),
};
}
export default async function Page() {
const t = await getTranslations("myFeature");
return <h1>{t("page.title")}</h1>;
}Dashboard routes resolve locale via AppIntlProvider in src/app/dashboard/layout.tsx — you do not need a [locale] segment in the URL.
5. Format dates and numbers
Pass the active locale to shared helpers:
import { useLocale } from "next-intl";
import { formatDate } from "@/lib/i18n/format";
const locale = useLocale();
formatDate(new Date(), locale, { dateStyle: "medium" });On the server, use getLocale() from next-intl/server.
6. Translate dashboard navigation
Dashboard nav labels come from messages/{locale}/dashboard.json, merged in src/features/dashboard/components/sidebar/sidebar.tsx. When you add a new dashboard section:
- Add the nav label key to
dashboard.jsonin every locale - Add the route in
src/features/dashboard/config/nav.ts(structure only — icon and href) - Wire the label in the sidebar via
useTranslations("dashboard")
7. Translate marketing content
Do not put translatable marketing copy in src/features/marketing/config.ts (deprecated for copy). Instead:
- Add structural data in
src/features/marketing/marketing-structure.ts(id, hrefs, tech refs, ordering) - Add display strings in
messages/{locale}/marketing.jsonunder the matching id
useMarketingContent() merges structure + copy automatically.
8. Translate documentation pages
Docs are MDX files, not JSON namespaces. For each new page:
content/docs/my-section/my-page.mdx # English
content/docs/my-section/my-page.de.mdx # German
content/docs/my-section/my-page.es.mdx # Spanish
content/docs/my-section/my-page.fr.mdx # French
content/docs/my-section/my-page.pt-BR.mdx # Brazilian PortugueseList the slug once in content/docs/my-section/meta.json. Localized section titles go in meta.de.json, etc.
Docs chrome strings (Previous, Next, Copy MD) live in messages/{locale}/docs.json.
9. Translate emails (optional)
Email templates can load locale-specific copy via src/lib/i18n/email-messages.ts. Transactional emails currently default to English unless you extend the resolver — see src/lib/i18n/resolve-email-locale.ts when you need localized verification or password-reset emails.
Common mistakes
| Mistake | Fix |
|---|---|
| Hardcoded English in JSX | Move string to JSON namespace |
| Missing key in one locale | Add the key to all five locale files |
Using next/link on marketing pages | Use Link from @/i18n/navigation |
Hardcoded "en-US" in Intl | Use @/lib/i18n/format helpers |
Putting marketing copy in config.ts | Use marketing-structure.ts + marketing.json |
Reference implementations
Copy patterns from these translated areas:
| Feature | Namespace | Component |
|---|---|---|
| Chat (dashboard) | dashboard | src/features/dashboard/components/chat/chat-page.tsx |
| Appearance settings | settings | src/features/dashboard/components/settings/appearance-settings-page.tsx |
| Auth | auth | src/components/ui/auth-section-3.tsx |
| Marketing | marketing | src/features/marketing/lib/use-marketing-content.ts |
| Dashboard nav | dashboard | src/features/dashboard/components/sidebar/sidebar.tsx |
Next steps
Internationalization — Routing, locale resolution, and SEO.
Project structure — Feature folder layout.
Configuration — Nav, billing, and product config files.