Skip to content

Latest commit

 

History

History
172 lines (126 loc) · 5.51 KB

File metadata and controls

172 lines (126 loc) · 5.51 KB

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:

import { useI18n } from "@/hooks/useI18n";

export function ExampleButton() {
  const { t } = useI18n();

  return <button aria-label={t("example.open")}>{t("example.open")}</button>;
}

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:

<span>{t("session.messageCount", { count: messageCount })}</span>

The corresponding message can use {count}:

"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:

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:

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:

// en.ts
"files.uploadedCount": "{count} uploaded"

// zh-CN.ts
"files.uploadedCount": "已上传 {count} 个文件"
<span>{t("files.uploadedCount", { count: uploadedCount })}</span>

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:

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.