English is the canonical product language. Android also ships Brazilian Portuguese, German, Japanese, Russian, Simplified Chinese, and Spanish catalogs; additional languages can be added without changing the runtime architecture.
Translation coverage and linguistic verification are separate. Shipped locale
status is recorded in docs/localization-status.json as ai-translated,
community-reviewed, or verified. AI-assisted catalogs may ship after the
technical gates pass; the status must not imply human review that did not occur.
See docs/translation-playbook.md for the required translation and critique
workflow.
Users can switch between System default, English, Brazilian Portuguese, German, Japanese, Russian, Spanish, and Simplified Chinese from Settings → Appearance → Language. The picker stays synchronized with Android's per-app language setting; Android 12 and lower use AppCompat's automatic locale storage.
- Canonical resources live in
app/src/main/res/values/strings.xml. - Locale catalogs live in Android-qualified directories such as
values-b+zh+Hans,values-b+zh+Hant,values-es, orvalues-b+pt+BR. - Use script-qualified Chinese directories. Do not put Simplified Chinese in
generic
values-zh, because that can incorrectly serve Simplified strings to Traditional Chinese locales. - Product-flavor strings have matching catalogs under their own source set, for
example
app/src/sideload/res/values-b+zh+Hans/strings.xml. - Brand and protocol terms may remain unchanged when translating them would make the UI less precise: Hermes, Relay, Bridge, Threads, Gateway, API, SOUL, and provider/model names.
Every shipped locale must contain the same translatable resource names and
resource types as English. Format arguments must use the same argument indexes
and conversion types. Plural categories may differ by language, but every
locale must provide other.
Run the fast catalog gate before Gradle:
python scripts/check-android-locales.pyThen run the release-relevant Android checks:
./gradlew :app:compileGooglePlayDebugKotlin :app:compileSideloadDebugKotlin
./gradlew lintReview at normal font size and at least 200% font size. Verify TalkBack labels, notifications, dialogs, onboarding, connection setup, Chat, Manage, Voice, and both product flavors. Android automatically falls back to English if a runtime resource is unavailable, but CI intentionally requires complete catalogs so new UI cannot silently remain English.
Each non-English status entry also records SHA-256 hashes of the canonical English catalogs it was translated from. Changing English without refreshing a locale therefore fails the catalog gate instead of silently leaving a structurally complete but semantically stale translation.
- Choose an Android qualifier that represents the language rather than a flag or country when possible.
- Copy the English resource structure into the new locale directory.
- Add the BCP-47 tag to
app/src/main/res/xml/locales_config.xmlso Android 13+ exposes the language in per-app system settings. - Add the language tag to
AppLanguage, add its picker label to every catalog, and cover tag resolution inAppLanguageTest. Use the language's own name for its label so it remains recognizable after an accidental switch. - Add the new option to the label map in
AppearanceSettingsScreen. - Translate user-facing text while preserving resource names, markup, escapes, and format arguments.
- Add any matching flavor catalogs.
- Run
python scripts/check-android-locales.py, the focused Android unit tests, and Android lint. - Test the locale on an emulator or device, including in-app and Android-system language switching, process restart, text expansion, and accessibility.
- Add a compact
docs/readme/README.<locale>.mdentrypoint, link it from the root README language list, and update the status registry. New AI-assisted locales start asai-translatedwith emptyreview_refs.
README.md remains canonical. Translated entrypoints live under
docs/readme/ as README.<locale>.md and link back to English. They are
deliberately compact onboarding and core-feature summaries, not full copies of
the fast-moving English README. Product documentation can be rolled out by
locale under user-docs/<locale>/; untranslated technical references should
link to the canonical English page rather than copying stale content.
docs/localization-status.json is the authoritative per-locale and per-surface
status. README and user-documentation translations may follow app translation;
maintainer docs/ and ADRs remain canonical English.
All shipped non-English Android locales have a compact translated README entrypoint. The public documentation localizes a deliberately bounded first-run set for German, Spanish, Japanese, Brazilian Portuguese, and Simplified Chinese. Russian currently falls back to the canonical English documentation:
- documentation home;
- Quick Start;
- condensed Installation & Setup;
- release-track choice;
- symptom-first Troubleshooting.
Fast-moving API, architecture, security, CLI, and operator references remain
canonical English and are linked from localized pages instead of copied. Each
localized page declares translation_status and canonical_source in its
frontmatter. docs_source_sha256 in the status registry records the exact
English page revision used for every locale.
Validate localized documentation and links with:
python scripts/check-user-docs-locales.pyAfter intentionally refreshing all five locale versions of a changed English core page, record the new canonical hashes with:
python scripts/check-user-docs-locales.py --refreshThe validator rejects stale source hashes, missing pages, broken internal links, unbalanced code fences, and translated or invented executable lines. VitePress runs this gate automatically before development and production builds.
The product site ships German, Spanish, Japanese, Brazilian Portuguese, and
Simplified Chinese under /de/, /es/, /ja/, /pt-BR/, and /zh-CN/.
Russian currently falls back to the canonical English site. Marketing copy,
navigation, accessibility labels, and page metadata are localized. Product
screenshots, command examples, and live UI
recreations remain unchanged so they continue to represent the shipped product.
Validate the typed copy dictionaries and their English-source freshness with:
python scripts/check-website-locales.pyAfter reviewing every marketing translation against an intentional English copy change, record the new source hash with:
python scripts/check-website-locales.py --refreshThe Astro development, check, and production-build commands run this gate automatically. Locale routes publish their own canonical URL, language metadata, alternate-language links, and sitemap entry.
Keep translation PRs scoped to one locale or one clearly described catalog
refresh. Do not include signing changes, custom APK release workflows, version
bumps, or fork-specific branding. AI-assisted translation is accepted when its
method and verification state are recorded honestly. Fluent review is
encouraged, not fabricated or treated as a prerequisite for initial coverage.
Community correction PRs are the canonical way to fix misses; once reviewed,
add their PR URLs to review_refs and advance the locale only to the
verification level actually completed.
If a valuable translation PR becomes too stale to merge safely, maintainers may
salvage it onto current dev under the contributor-credit policy in
CONTRIBUTING.md. The original contributor remains credited in Git history and
in the replacement PR lineage.