# Internationalization Pi Web's interface uses a small internal i18n layer. It is intentionally kept inside the application instead of introducing another runtime dependency, so the UI can support more languages without coupling the server, API responses, or Pi's own output to a particular language. ## Current Languages The built-in language packages are: - `en` - English - `zh-CN` - Simplified Chinese The initial locale is inferred from the browser. Users can change the locale from the language control in the top bar. The selected locale is persisted in `localStorage` under `pi-locale`. ## Using Translations In A Component Client components should get the translation function from `useI18n`: ```tsx import { useI18n } from "@/hooks/useI18n"; export function ExampleButton() { const { t } = useI18n(); return ; } ``` The component must be rendered below `I18nProvider`. The application root already provides it in `app/page.tsx`. Use parameters for values that change at runtime instead of concatenating translated fragments: ```tsx {t("session.messageCount", { count: messageCount })} ``` The corresponding message can use `{count}`: ```ts "session.messageCount": "{count} messages" ``` This keeps the sentence structure under the control of each language package. ## Adding A New Language ### 1. Create The Message File Add a file under `lib/i18n/messages/`, using a BCP 47-style identifier. For example, a Japanese package would be `lib/i18n/messages/ja.ts`: ```ts import type { LocalePlugin } from "../types"; /** Pi Web Japanese language package. */ export const jaLocale: LocalePlugin = { id: "ja", label: "日本語", messages: { "common.language": "言語", // Add the rest of the shared keys here. }, }; ``` The message keys must remain stable and language-neutral. Copy the complete key set from `lib/i18n/messages/en.ts` when starting a new package. Missing keys fall back to English, but a complete package is preferred for a user facing language. ### 2. Register The Package Import and register the package in `lib/i18n/registry.ts`: ```ts import { jaLocale } from "./messages/ja"; registerLocale(jaLocale); ``` `LocalePlugin.id` must be unique and non-empty. The label is displayed in the language selector. `getSupportedLocales()` automatically exposes registered packages to the UI. ### 3. Update Locale-Specific Types If Needed The built-in `Locale` type currently lists `en` and `zh-CN` for compile-time safety. When adding a built-in language, update that union in `lib/i18n/types.ts` and update any locale validation that intentionally lists the built-in identifiers. ### 4. Add Tests Extend `lib/i18n/registry.test.mjs` when browser-language detection or registry behavior changes. Extend `lib/i18n/format.test.mjs` when interpolation or locale-aware formatting changes. A new language should at minimum verify its registry id and a representative translation. ## Adding Or Changing UI Text 1. Add a stable key to both `lib/i18n/messages/en.ts` and `lib/i18n/messages/zh-CN.ts`. 2. Put keys under a feature namespace such as `chat.*`, `files.*`, `settings.*`, or `common.*`. 3. Use `t("namespace.key")` in visible text, `title`, `aria-label`, and `placeholder` values. 4. Use interpolation for counts, names, paths, and other runtime values. 5. Keep product names, model names, provider names, commands, file paths, user content, tool output, and server-provided error details unchanged. 6. Do not translate an API error by matching its English text. Translate only the local UI fallback around it. Keep established technical terms in English when translating them would make the interface harder to map back to commands, configuration fields, or Pi documentation. This includes terms such as `Agent`, `API Key`, `Provider`, `Token`, `worktree`, `Diff`, `CWD`, `Shell`, `Git`, `HEAD`, `OAuth`, and `Mermaid`; preserve their conventional casing. Translate the surrounding actions and status text. Common concepts with clear localized forms, such as model, plugin, skill, prompt, context, and cache, can remain localized. For example: ```ts // en.ts "files.uploadedCount": "{count} uploaded" // zh-CN.ts "files.uploadedCount": "已上传 {count} 个文件" ``` ```tsx {t("files.uploadedCount", { count: uploadedCount })} ``` If a key is missing from the selected language, the formatter first tries the English package and finally returns the key itself. Missing keys also produce a development warning, which makes incomplete translations visible during development. ## Verification Run the following commands from the repository root: ```bash node_modules/.bin/tsc --noEmit npm run lint node_modules/.bin/jiti lib/i18n/registry.test.mjs node_modules/.bin/jiti lib/i18n/format.test.mjs node --test lib/*.test.mjs ``` Do not run `next build` during normal development. See the repository development notes for the reason. ## Design Principles Pi Web is a user-facing web interface. Internationalization is therefore an accessibility and usability feature: people should be able to understand and operate the interface in a language they are comfortable with. Language support should lower the barrier to using Pi Web, not be treated as a measure of technical expertise or as a reason to exclude otherwise useful contributions. The i18n layer is deliberately incremental. Contributors can add a language or improve a small group of UI messages without rewriting API contracts, changing Pi's output, or introducing a large translation framework.