PKU harness
Build a plugin

Languages (i18n)

How a bundled plugin provides Chinese and English. Manifest strings get English versions in `i18n.en`, screen and widget text goes in a `defineStrings` table, dates go through the SDK formatters, and text for the model stays untranslated.

The app is available in Simplified Chinese (zh-Hans) and English (en). Chinese is the source language: you write every string in Chinese first and then add the English version. Users choose 跟随系统 (follow the system), 简体中文 or English in Settings.

Every bundled plugin MUST be complete in both languages. The test plugins/i18n.test.ts in the plugins repository checks each plugin and fails when an English string is missing. This page covers bundled plugins. A plugin document puts all its English text in the i18n.en section of its manifest (see Declarative plugins).

What gets translated

Text that people read is translated. Text that only the model reads, such as tool descriptions and instructions, is written once and not translated.

TextWhereTranslate?
Plugin name, descriptiondefinePlugin (Chinese) + i18n.enYes
Tool titlestools.<name>.title + i18n.en.tools.<name>.titleYes
Tool summary (plugin documents only)summary in the tool file + i18n.en.tools.<name>.summaryYes
Widget titles and descriptionscontributes.widgets + i18n.en.widgetsYes
Screen titlescontributes.screens + i18n.en.screensYes
Settings rowscontributes.settings + i18n.en.settings.<screen>Yes
Commandscontributes.commands + i18n.en.commands.<name> (description, argHint, aliases, prompt)Yes
Data source titlesdataSources + i18n.en.dataSourcesYes
Provider names, hints, model namesproviders + i18n.en.providersYes
Screen and widget textsrc/strings.ts (defineStrings)Yes
Tool description, instructions, context, tool resultsFor the modelNo

Manifest overlay

The Chinese strings stay where they are in definePlugin. The English versions go in i18n.en. This example is from the shuttle plugin:

definePlugin({
  id: 'shuttle',
  name: '校车',
  description: '燕园—新燕园(昌平)通勤班车和新燕园—地铁北安河快速公交时刻表,算出下一班车。',
  i18n: {
    en: {
      name: 'Shuttle bus',
      description: 'Timetables for the Yanyuan–Xinyanyuan (Changping) commuter shuttle and the Xinyanyuan–Bei’anhe metro express bus, with the next departure worked out for you.',
      tools: { next: { title: 'Next shuttle' }, timetable: { title: 'Shuttle timetable' } },
      widgets: { next: { title: 'Next shuttle', description: '…' } },
      commands: { shuttle: { description: 'Next shuttle (works offline)', argHint: 'e.g. Yanyuan to Xinyanyuan', aliases: ['bus'] } },
      dataSources: { next: { title: 'Next shuttle' } },
    },
  },
  // …
});

At runtime, anything missing from en falls back to Chinese. The coverage test still fails, so fill in everything.

String tables

Text inside screens and widgets lives in a string table, one per plugin, in src/strings.ts. You define its keys once as a type, then give a value for each language:

src/strings.ts
import { defineStrings } from '@pku-harness/sdk';

type Strings = {
  none: string;
  later: (list: string) => string;
};

export const strings = defineStrings<Strings>({
  'zh-Hans': { none: '今明两天没有班次', later: (list) => `之后 ${list}` },
  en: { none: 'No buses today or tomorrow', later: (list) => `Then ${list}` },
});
const s = strings[host.useLocale()];
<Text>{s.none}</Text>

In a component, pick the table for the user's language with host.useLocale().

defineStrings type-checks en against zh-Hans, so both need the same keys with the same value types. A string with parameters is a function. Never build a sentence by joining translated fragments, because word order differs between the two languages.

Dates and numbers

Hermes, the JavaScript engine the app runs on, ignores Intl locales. toLocaleDateString and similar functions therefore give the wrong format. Use the SDK formatters instead. Most of them take the locale l from host.useLocale():

Functionzh-Hansen
formatClock(d)21:0421:04
formatWeekday(d, l)周一Mon
formatLocalDate(d, l)10月18日Oct 18
formatLocalDateTime(d, l)10月18日 21:04Oct 18, 21:04
formatRelative(ms, l)刚刚, 3 分钟前, 昨天 21:04Just now, 3 min ago, Yesterday 21:04
formatDay(d, l)今天, 明天, 周三Today, Tomorrow, Wed
formatCountdown(ms, l)2小时10分2h 10m
plural(n, one)—1 book, 3 books

Details in SDK → format.

English copy rules

The coverage test also checks how English strings are written:

  • No Chinese except inside a parenthesised gloss or quotes. Treehole (树洞) is fine.
  • Curly apostrophes (’), not straight ones (').
  • No exclamation marks.
  • Keep it short and in sentence case, which capitalises only the first word and proper nouns. Campus proper nouns use the official English name or pinyin: Peking University, Treehole (树洞), Course (教学网 / Blackboard), Campus Card.

Chinese text follows the same idea: keep it short and use no exclamation marks.

On this page