Translation & i18n skill
Fill in missing translations (msgstr "") in all
web-next/src/locales/*/messages.po files and ensure every translation
meets the quality standards below.
Adding translatable strings to components
Import from ~/lib/i18n/macro.d.ts. Always call useLingui() at the
top of the component to get the translation helpers.
Simple text
import { useLingui } from "~/lib/i18n/macro.d.ts";
function MyComponent() {
const { t } = useLingui();
return <Button>{t`Save`}</Button>;
}
t works in JSX children, attributes, and any expression context:
<Button title={t`Reply`}>…</Button>
<TextFieldLabel>{t`Content`}</TextFieldLabel>
placeholder={t`Search a language…`}
aria-label={t`Language`}
Variable interpolation
Embed runtime values directly in the template literal:
const { t } = useLingui();
t`Failed to sign out: ${error.message}`
t`To follow ${displayName()}, enter your Fediverse handle.`
Translations with JSX elements (Trans component)
When a translation needs inline JSX (links, bold, components), use the
<Trans> component from ~/components/Trans.tsx. Placeholder keys use
SCREAMING_SNAKE_CASE:
import { Trans } from "~/components/Trans.tsx";
import { useLingui } from "~/lib/i18n/macro.d.ts";
function MyComponent() {
const { t } = useLingui();
return (
<Trans
message={t`Verified that this link is owned by ${"OWNER"} ${"RELATIVE_TIME"}`}
values={{
OWNER: () => <strong>{actor().name}</strong>,
RELATIVE_TIME: () => <Timestamp value={verified()} />,
}}
/>
);
}
Pluralization
Use i18n._(msg…) combined with plural(). The # symbol is
replaced with the count:
import { msg, plural, useLingui } from "~/lib/i18n/macro.d.ts";
function FollowerCount(props: { count: number }) {
const { i18n } = useLingui();
return (
<span>
{i18n._(
msg`${
plural(props.count, {
one: "# follower",
other: "# followers",
})
}`,
)}
</span>
);
}
Accessing the current locale
Use i18n.locale for locale-aware formatting (e.g., Intl APIs):
const { i18n } = useLingui();
date.toLocaleString(i18n.locale, { dateStyle: "full" })
Extracting messages
Before translating, run the extraction command to ensure .po files are
up to date with the latest source code:
cd web-next && pnpm extract
This runs the extract script from web-next/package.json
(lingui extract), which scans web-next/src/ for
translatable strings (t`...`, msg`...`, plural(...)) and
updates all web-next/src/locales/*/messages.po files. New strings
appear as entries with empty msgstr "".
Run this before starting translation work to pick up any newly added or changed strings.
Workflow
- Run
cd web-next && pnpm extractto update.pofiles. - For each locale, read both of these before translating:
web-next/src/locales/{locale}/glossary.txt— mandatory terminologyweb/locales/{corresponding-legacy-locale}.json— reference translations from the legacy stack (locale mapping:en-US→en,ja-JP→ja,ko-KR→ko,zh-CN→zh-CN,zh-TW→zh-TW)
- Fill every empty
msgstr ""with a translation that follows all rules below. - After translating, review existing non-empty
msgstrentries for rule violations and fix them too.
Glossary enforcement
- Always use the exact term from
glossary.txtwhen the source text contains a glossary term. - If you encounter a term that should be standardized but is not in the
glossary, ask the user via
AskUserQuestionwhether to add it. - If the user agrees, add the entry to every locale's
glossary.txtin the formatterm → translation(one per line, sorted alphabetically).
Punctuation & typography
All languages
| Rule | Correct | Wrong |
|---|---|---|
| Ellipsis | … (U+2026) |
... |
| Em dash | — (U+2014) |
-- |
| Casing | Sentence case | Title Case |
en-US
- Quotation marks:
""(curly double),''(curly single) - Standard English punctuation rules
- Use sentence case for buttons, links, labels, headings, tabs, menu
items, placeholders, and other UI strings. Do not use title case
unless the text is a proper noun, brand name, protocol name, acronym,
or another term that is conventionally capitalized. For example,
write
Create note,Save draft, andRemote follow, notCreate Note,Save Draft, orRemote Follow.
ja-JP
- Quotation marks:
「」(single),『』(double/nested) - Fullwidth punctuation:
。、!? - No space before/after punctuation
ko-KR
- Quotation marks:
「」(single),『』(double/nested) - Period: halfwidth
.(not fullwidth。— this is Korean standard) - Comma, exclamation, question:
,!?(halfwidth, Korean standard) - No space before punctuation
zh-CN
- Quotation marks:
「」(single),『』(double/nested) - Fullwidth punctuation:
。、!? - No space before/after punctuation
zh-TW
- Quotation marks:
「」(single),『』(double/nested) - Fullwidth punctuation:
。、!? - No space before/after punctuation
Formality & tone
| Locale | Style |
|---|---|
| ko-KR | 합쇼체(~합니다) 기본. 리듬감을 위해 가끔 해요체 허용. |
| ja-JP | です/ます form for UI text |
| en-US | Neutral, professional |
| zh-CN | 简体中文标准用语 |
| zh-TW | 繁體中文標準用語 |
CJK-Latin spacing
When Latin characters (English words, brand names, numbers) appear next to CJK characters, follow these language-specific rules:
| Locale | Rule | Example |
|---|---|---|
| ja-JP | No space — particles attach directly | GitHubリポジトリ, Markdownを使用 |
| ko-KR | Space before Latin text; particles attach to Latin | GitHub 저장소, Markdown을 사용 |
| zh-CN | Space on both sides of Latin text | 启用了 ActivityPub 的社交网络 |
| zh-TW | Space on both sides of Latin text | 啟用了 ActivityPub 的社交網路 |
Brand names & proper nouns
Never translate brand names or protocol names. Keep them as-is:
- Hackers' Pub, GitHub, Mastodon, Misskey, Pixelfed
- ActivityPub, Markdown, WebFinger
For “fediverse”, follow glossary — some languages use a localized term (ja-JP: フェディバース, ko-KR: 연합우주) while others keep the original.
Terminal punctuation by UI element
| UI element | Punctuation |
|---|---|
| Button / action label | No trailing punctuation in any language |
| Menu item | No trailing punctuation |
| Description / help text | End with period (en: ., ja: 。, ko: ., zh: 。) |
| Question / confirmation | End with question mark (en: ?, ja: ?, ko: ?, zh: ?) |
| Title / heading | No trailing punctuation |
For English UI elements, combine the punctuation rule with sentence case:
Load more, Edit draft, and Permission denied are correct; title-case
variants such as Load More, Edit Draft, and Permission Denied are not.
Escape sequences
- Preserve
\nline breaks frommsgidexactly inmsgstr. - Use
\n\nfor paragraph breaks (e.g., in email content). - Never convert
\nto literal newlines or vice versa.
Gender-neutral language
- en-US: Use “they/their” for third-person singular when gender is unknown. Prefer second person “you/your” where natural.
- ja-JP: Use honorific “さん” for person references.
- ko-KR / zh-CN / zh-TW: These languages are largely gender-neutral by default; avoid unnecessarily gendered terms.
Placeholder & syntax rules
- Preserve all placeholders (
{0},{1},#, etc.) exactly as they appear inmsgid. - Preserve ICU MessageFormat syntax (
plural,select,selectordinal) exactly—only translate the human-readable parts inside. - Never translate placeholder variable names or format specifiers.
Quality checklist
Before finishing, verify each translation:
- Glossary terms match
glossary.txtexactly - Punctuation follows the locale-specific rules above
- CJK-Latin spacing follows the locale-specific rules
- Brand names are kept as-is, not translated
- Terminal punctuation matches UI element type
- All placeholders from
msgidare present inmsgstr - ICU MessageFormat syntax is valid
- Escape sequences (
\n) are preserved - Formality level matches the locale convention
- Gender-neutral language is used
- Translation is concise and natural for UI context