Localize App or Component
Workflow Checklist
- [ ] 1. Extract hardcoded strings
- [ ] 2. Replace with t() calls in source files
- [ ] 3. Add English translations to en/translation.json
- [ ] 4. Sync translations across languages
- [ ] 5. Machine translate [TODO] keys
- [ ] 6. Validate coverage and terminology
Step 1: Extract Hardcoded Strings
bun run i18n:extract --pattern [PATTERN]
Step 2: Replace Strings with t() Calls
For each component:
- Add import:
import { useTranslation } from "react-i18next"; - Add hook:
const { t } = useTranslation(); - Replace strings:
t("apps.[appName].category.key") - Add
tto dependency arrays foruseMemo/useCallback
Key Structure
apps.[appName].menu.* # Menu labels
apps.[appName].dialogs.* # Dialog titles/descriptions
apps.[appName].status.* # Status messages
apps.[appName].toasts.* # Toast titles/descriptions
apps.[appName].empty.* # Empty-state titles/hints
apps.[appName].ariaLabels.* # Accessibility labels
apps.[appName].help.* # Help items (auto-translated)
apps.[appName].speech.* # Spoken feedback / speech labels
apps.[appName].conversion.* # Unit conversion labels
apps.[appName].angle.* # Angle-mode labels
common.auth.* # Shared login/signup/recovery dialog labels
Common Patterns
// Basic
t("apps.ipod.menu.file")
// With variables
t("apps.ipod.status.trackCount", { count: 5 })
// Conditional
isPlaying ? t("pause") : t("play")
// With symbol prefix
`✓ ${t("apps.ipod.menu.shuffle")}`
Step 3: Add English Translations
Add to src/lib/locales/en/translation.json:
{
"apps": {
"ipod": {
"menu": { "file": "File", "addSong": "Add Song..." },
"dialogs": { "clearLibraryTitle": "Clear Library" },
"status": { "shuffleOn": "Shuffle ON" }
}
}
}
Do not rely on defaultValue as the only copy of a new key. t("some.key", { defaultValue: "English" }) renders, but sync and audit scripts only compare locale JSON files, so missing keys stay invisible until the English catalog contains them.
Use Apple-style title casing for standalone English menu and control labels, such as Repeat All, Repeat One, Sign In, and Main Volume. When a casing or terminology rule should be enforced globally, add the key to ENGLISH_STYLE_EXPECTATIONS in scripts/apple-ui-terminology.ts and cover it with tests/test-translation-audit.test.ts.
Step 4: Sync Across Languages
bun run i18n:sync:mark-todo
Adds missing keys to all 11 language files, marked with [TODO].
Step 5: Machine Translate
bun run i18n:translate
Requires GOOGLE_GENERATIVE_AI_API_KEY env variable.
Step 6: Validate
bun run i18n:sync:dry-run
bun run i18n:audit
bun run i18n:find-untranslated
Use bun run i18n:audit:fix only for terminology drift the script can safely repair, then rerun bun run i18n:audit. When audit reports glossary mismatches that are not auto-fixable, edit the affected locale JSON values by hand and rerun the audit.
Component Guidelines
| Component | What to translate |
|-----------|-------------------|
| Menu bars | All labels, items, submenus |
| Dialogs | Titles, descriptions, button labels |
| Status | showStatus() calls and inline state labels |
| Toasts / empty states | Sonner toast copy, empty-state titles, empty-state hints |
| Help items | Auto-translated via useTranslatedHelpItems |
| Shared auth dialogs | common.auth.*, for login/signup/password recovery copy |
Notes
- Emoji/symbols (♪, ✓) can stay hardcoded
- Help items use pattern:
apps.[appName].help.[key].title/description - Help item key order lives in
src/hooks/useTranslatedHelpItems.ts; apps with longer localized help rows can export their key list (for examplesrc/apps/maps/helpKeys.ts,src/apps/calculator/helpKeys.ts, orsrc/apps/internet-explorer/helpKeys.ts) and spread it intoAPP_HELP_I18N_KEYS tests/test-help-i18n-alignment.test.tscovers every registered app; update it only if the global help-key contract changes- Include
tin dependency arrays when used inuseMemo/useCallback - If Apple terminology changes upstream, refresh the glossary source with
bun run i18n:apple-glossarybefore auditing locale copy