📝 Managing Strings & Languages
This document explains how developers manage our english source strings and add/remove languages in the Thunderbird for Android project.
note
Translators: If you want to contribute translations, see Translations. This document is developer-focused.
📖 Approach
- We use Android’s resource system for localizing strings and Compose Multiplatform Resources for localizing strings in common code (Kotlin Multiplatform).
- Source language is English (American English, represented as
en). - Source strings are modified only in this repository (via pull requests).
- Translations are managed exclusively in Weblate and merged into the repository by the Thunderbird team.
- Languages are added/removed when they reach 70% translation or fall below 60%.
🔄 Changing Source Strings
Source strings are always stored in res/values/strings.xml or (English, en).
They must be managed carefully to avoid breaking existing translations.
- Do not edit translation files directly in Git. Translations should always be updated in Weblate.
🔧 Mechanical/Global Changes
If a mechanical or global change to translations is required (for example, renaming placeholders or fixing formatting across all languages):
- Lock components in Weblate (maintenance page).
- Commit all outstanding changes.
- Push Weblate changes (creates a PR).
- Merge the Weblate PR.
- Apply your mechanical change in a separate PR.
- Wait for Weblate sync to propagate your merged PR.
- Unlock components in Weblate.
This ensures translators do not work on outdated strings and avoids merge conflicts.
➕ Adding a String
- Add the new string in the appropriate
res/values/strings.xmlfile. - Do not add translations.
- After merge, Weblate will pull the new string.
- Translators can then add translations in Weblate.
✏️ Changing a String
There are two kinds of changes to source strings:
🔤 Typos or Grammar Fixes
Correcting minor errors (spelling, capitalization, punctuation, grammar) in the English source is allowed:
- Keep the same key — translations will remain valid.
Example:
- Changing “Recieve” to “Receive” or “email” to “Email”.
🧭 Changing Meaning
caution
Never reuse an existing key for a changed meaning — this would cause translators’ work to become misleading or incorrect.
If the meaning of the string changes (new wording, different context, updated functionality):
- Add a new key with the new string.
- Update all references in the source code to use the new key.
- Delete the old key from
res/values/strings.xml. - Delete the old key’s translations from all
res/values-*/strings.xmlfiles. - Build the project to ensure there are no references to the old key remaining.
This ensures there are no stale or misleading translations left behind.
Example:
- Old: “Check mail now” (
action_check_mail) - New: “Sync mail” (
action_sync_mail)
Steps:
- Add new key
action_sync_mailwith value “Sync mail” tores/values/strings.xml. - Update all code references from
R.string.action_check_mailtoR.string.action_sync_mail. - Remove
action_check_mailfromres/values/strings.xmland allres/values-*/strings.xml - Build the project to ensure no references to
action_check_mailremain. - After the next sync, Weblate will prompt translators to provide translations for action_sync_mail.
❌ Removing a String
- Delete the key from
res/values/strings.xml. - Delete the key’s translations from all
res/values-*/strings.xmlfiles. - Build the project to ensure there are no references to the removed key remaining.
🔀 Merging Weblate PRs
When merging Weblate-generated PRs:
- Check plural forms for cs, lt, sk locales. Weblate does not handle these correctly (issue).
- Ensure both
manyandotherforms are present.- If unsure, reusing values from
manyorotheris acceptable.
- If unsure, reusing values from
🌍 Managing Languages
We use Gradle’s
androidResources.localeFilters to control which languages are bundled.
This must stay in sync with the string array supported_languages so the in-app picker shows only available locales.
🔎 Checking Translation Coverage
Before adding a language, we require that it is at least 70% translated in Weblate.
We provide a Translation CLI script to check translation coverage:
./scripts/translation --token <weblate-token>
# Specify the low 60% threshold
./scripts/translation --token <weblate-token> --threshold 60
- Requires a Weblate API token
- Default threshold is 70% (can be changed with
--threshold <N>)
For example code integration, run with –print-all:
./scripts/translation --token <weblate-token> --print-all
This output can be used to update:
resourceConfigurationsinapp-k9mail/build.gradle.ktsandapp-thunderbird/build.gradle.ktssupported_languagesinlegacy/core/src/res/values/arrays_general_settings_values.xml
➖ Removing a Language
- Remove language code from
androidResources.localeFiltersin:app-thunderbird/build.gradle.ktsapp-k9mail/build.gradle.kts
- Remove entry from
supported_languagesin:app/core/src/main/res/values/arrays_general_settings_values.xml
➕ Adding a Language
- Add the code to
androidResources.localeFiltersin both app modules. - Add entry to
supported_languagesin:app/core/src/main/res/values/arrays_general_settings_values.xml
- Add corresponding display name in:
app/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml(sorted by Unicode default collation order).
- Ensure indexes match between
language_entriesandlanguage_values.
important
The order of entries in language_entries and language_values must match exactly. Incorrect ordering will cause mismatches in the language picker.
🧩 Adding a Component to Weblate
When a new module contains translatable strings, a new Weblate component must be created.
Steps:
- Go to Add Component.
- Choose From existing component.
- Name your component.
- For Component, select Thunderbird for Android / K-9 Mail/ui-legacy.
- Continue → Select Specify configuration manually.
- Set file format to Android String Resource.
- File mask:
path/to/module/src/main/res/values-*/strings.xml - Base file:
path/to/module/src/main/res/values/strings.xml - Uncheck Edit base file.
- License: Apache License 2.0.
- Save.
⚠️ Language Code Differences
Android sometimes uses codes that differ from Weblate (e.g. Hebrew = iw in Android but he in Weblate).
Automation tools must map between systems. See LanguageCodeLoader.kt for an example.
You could find a more complete list of differences in the Android documentation and Unicode and internationalization support