Internationalization (i18n) patterns for server and client components using getTranslations and useTranslations. Use when working with translations, locales, multilingual content, translation files, TranslationContextProvider, locale switching, or when the user mentions i18n, translations, getTranslations, useTranslations, or translation.json files.
Translations are written inline using the t() function. A Babel plugin transforms these calls at compile time into hash-based lookups against generated JSON bundles. The same t() API works in both server and client components — the Babel plugin selects the correct runtime automatically.
Source: t({ en: "Hello", zh: "你好" })
↓ Babel plugin
Server: __i18n_lookup("a8cfb50c") — reads from server JSON bundle
Client: useI18nLookup("a8cfb50c") — reads from React context (hook)
en — Englishzh — ChineseDefined as SupportedLocale in src/types.ts. Routes use [locale] parameter: /en/about, /zh/about.
t() FunctionImport from #src/i18n.ts. Pass an object with en and zh string literal properties:
import { t } from "#src/i18n.ts";
// Plain text
t({ en: "Hello", zh: "你好" });
// With HTML markup — returns ReactNode instead of string
t(
{ en: "<strong>Bold text</strong>", zh: "<strong>粗体文字</strong>" },
{ parse: true },
);
Both en and zh values must be string literals — no variables, template literals, or expressions. The codegen and Babel plugin rely on statically extracting these at build time.
Supported HTML tags in { parse: true } mode: <strong>, <em>, <b>, <i>, <p>.
Use t() directly. No special setup needed — the Babel plugin handles everything:
import { t } from "#src/i18n.ts";
export default function Page() {
return <h1>{t({ en: "Welcome", zh: "欢迎" })}</h1>;
}
For page files (page.tsx under [locale]), the Babel plugin auto-injects setLocale() at the top of the default export function. You do not need to manually call setLocale or accept params — the plugin makes the function async, reads params.locale, and calls setLocale(validateLocale(params.locale)) for you.
Layout files that need the locale for other purposes (e.g. generateMetadata, routing logic) should still read params manually since they have non-i18n reasons to do so.
Use t() identically — the Babel plugin detects "use client" and swaps in client-specific lookup functions that read from React context:
"use client";
import { t } from "#src/i18n.ts";
export function SearchButton() {
return <button>{t({ en: "Search", zh: "搜索" })}</button>;
}
Client translations are provided automatically. The codegen generates a manifest mapping page/layout files to their client translation bundles. The Babel plugin reads this manifest and auto-wraps the default export's return value with <ClientTranslationsProvider>, so client components receive translations without any manual wiring.
Use the useLocale() hook when you need the locale value itself (not for translations):
"use client";
import { useLocale } from "#src/hooks/use-locale.ts";
export function LocaleAwareComponent() {
const locale = useLocale();
const formatter = new Intl.NumberFormat(locale);
return <span>{formatter.format(1234)}</span>;
}
import { getLocalePath } from "#src/utils/get-locale-path.ts";
getLocalePath("/about", locale); // → "/en/about" or "/zh/about"
pnpm codegen:i18n)The codegen script in packages/i18n-codegen/ does:
t() calls from source files via AST parsingsrc/_generated/i18n/translations.{en,zh}.jsonsrc/_generated/i18n/client/{name}.{en,zh}.jsonsrc/_generated/i18n/client-loaders/{name}.tssrc/_generated/i18n/manifest.jsonRun codegen after adding/changing any t() call, or the Babel plugin won't find the translation key at runtime.
packages/babel-plugins/src/i18n/)Runs at compile time (both dev and build). Transforms:
t({en, zh}) → __i18n_lookup(key) (server) or useI18nLookup(key) (client)t({en, zh}, { parse: true }) → __i18n_lookupParse(key) or useI18nLookupParse(key)setLocale for page files with t() calls<ClientTranslationsProvider> for manifest entries// src/app/[locale]/my-page/page.tsx
import { t } from "#src/i18n.ts";
export default function Page() {
return (
<div>
<h1>{t({ en: "My Page", zh: "我的页面" })}</h1>
<p>{t({ en: "Some content", zh: "一些内容" })}</p>
</div>
);
}
Then run pnpm codegen:i18n to regenerate bundles.
// src/components/my-component.tsx
"use client";
import { t } from "#src/i18n.ts";
export function MyComponent() {
return <span>{t({ en: "Click me", zh: "点击我" })}</span>;
}
The parent page/layout must be in the manifest for client translations to work. Run pnpm codegen:i18n — the codegen traces imports and auto-generates the client bundle.
{
t(
{
en: "Read our <strong>terms of service</strong>",
zh: "阅读我们的<strong>服务条款</strong>",
},
{ parse: true },
);
}
t() — values must be string literalspnpm codegen:i18n after adding new t() callssetLocale to page files — the Babel plugin does this automaticallytranslations.json files — translations live inline in the componentgetTranslations() or useTranslations() — these no longer exist; use t() everywheret() outside render scope — t() must be called directly in a React component body, custom hook, or generateMetadata(). It cannot be used in useEffect, event handlers, callbacks (.map(), .then()), setTimeout, module scope, or exported non-component functions. The ESLint rule @tuja/no-t-outside-render enforces this. Non-exported helper functions are allowed only if every call site is in render scope.Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer