📝 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.xml
file. - 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.xml
files. - 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_mail
with value “Sync mail” tores/values/strings.xml
. - Update all code references from
R.string.action_check_mail
toR.string.action_sync_mail
. - Remove
action_check_mail
fromres/values/strings.xml
and allres/values-*/strings.xml
- Build the project to ensure no references to
action_check_mail
remain. - 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.xml
files. - 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
many
andother
forms are present.- If unsure, reusing values from
many
orother
is 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:
resourceConfigurations
inapp-k9mail/build.gradle.kts
andapp-thunderbird/build.gradle.kts
supported_languages
inlegacy/core/src/res/values/arrays_general_settings_values.xml
➖ Removing a Language
- Remove language code from
androidResources.localeFilters
in:app-thunderbird/build.gradle.kts
app-k9mail/build.gradle.kts
- Remove entry from
supported_languages
in:app/core/src/main/res/values/arrays_general_settings_values.xml
➕ Adding a Language
- Add the code to
androidResources.localeFilters
in both app modules. - Add entry to
supported_languages
in: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_entries
andlanguage_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