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.
- We use Android’s resource system for localizing strings in Android-only modules.
- We use Compose Multiplatform Resources for localizing strings in Kotlin Multiplatform (KMP) modules.
- 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 via the Translation - Update workflow.
- Languages are added/removed when they reach 70% translation or fall below 60%.
Source strings are always stored in 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.
Stored in res/values/strings.xml (and plurals.xml if applicable).
Stored in src/commonMain/composeResources/values/strings.xml.
To use Compose Multiplatform Resources in a module, follow these steps in your build.gradle.kts:
-
Apply the plugin: Use
ThunderbirdPlugins.Library.kmpComposefor a KMP library module with Compose support.plugins { id(ThunderbirdPlugins.Library.kmpCompose) } -
Configure
compose.resourcesblock: SetpublicResClasstofalseand provide a uniquepackageOfResClass.compose.resources { publicResClass = false packageOfResClass = "net.thunderbird.feature.yourfeature.resources" } -
Ensure
androidnamespace is set: The Android target requires a namespace in thekotlinblock.kotlin { android { namespace = "net.thunderbird.feature.yourfeature.api" } }
If a mechanical or global change to translations is required (for example, renaming placeholders or fixing formatting across all languages), follow this workflow:
- Lock components in Weblate: Go to the maintenance page and lock all components to prevent new translations during the change.
- Commit outstanding changes: Ensure all pending translations in Weblate are committed to its internal Git repository.
- Pull latest translations:
Trigger the Translation - Update GitHub workflow manually using
workflow_dispatch. - Merge the pull request:
Review and merge the resulting PR to ensure your local
mainbranch is in sync with Weblate. - Apply your change: Apply your mechanical changes to the source and translation files in a new branch and merge it.
- Update Weblate configuration (if needed):
If your change involved moving files or changing directory structures, use the
weblate-cli updatecommand to ensure Weblate is correctly configured. - Unlock components: Once Weblate has pulled the changes from the repository, unlock the components.
See the Weblate CLI section for more details on the tooling.
- Add the new string in the appropriate source file:
- Android:
res/values/strings.xml - Compose:
src/commonMain/composeResources/values/strings.xml
- Android:
- Do not add translations.
- After merge, Weblate will pull the new string.
- Translators can then add translations in Weblate.
There are two kinds of changes to source strings:
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".
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 the source file.
- Delete the old key’s translations from all translation files (e.g.
values-*/strings.xml). - 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)
- Delete the key from the source file.
- Delete the key’s translations from all translation files.
- Build the project to ensure there are no references to the removed key remaining.
Used in Android-only modules or Android-specific source sets.
// In a Context-aware class
val title = context.getString(R.string.my_string_key)
// With arguments
val message = context.getString(R.string.welcome_message, userName)Used in KMP modules, primarily in commonMain.
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.getPluralString
import your.module.package.resources.Res
import your.module.package.resources.my_string_key
// In a Composable function (using the @Composable stringResource)
val title = stringResource(Res.string.my_string_key)
// In a non-Composable (suspend) function
val title = getString(Res.string.my_string_key)
// With arguments
val message = getString(Res.string.welcome_message, userName)
// Plurals
val messagesCount = getPluralString(Res.plurals.new_messages, count, count)Note
The Res class is generated by the Compose Multiplatform Gradle plugin. You may need to build the project to
generate it after adding new strings.
Translations are merged from Weblate via an automated GitHub workflow. This workflow:
- Fetches the latest changes from Weblate's Git export.
- Creates a pull request with the updated translation files.
- Preserves contributor attribution via
Co-authored-bytrailers.
When reviewing and merging these 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
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.
We provide several CLI tools to assist with translation management.
Used to check translation coverage before adding or removing languages.
./scripts/translation --token <weblate-token>
# Specify the low 60% threshold and verbose logging
./scripts/translation --token <weblate-token> --threshold 60 --log-level BODY- Requires a Weblate API token
- Default threshold is 70% (can be changed with
--threshold <N>) - Default log level is
NONE(can be changed with--log-level <LEVEL>)
For example code integration, run with --print-all:
./scripts/translation --token <weblate-token> --print-allThis 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
Used to manage component configurations and create missing components on Weblate.
# Update managed components with standard configuration
./scripts/weblate --token <weblate-token> update
# Create missing components based on local modules
./scripts/weblate --token <weblate-token> create
# Delete a component by slug
./scripts/weblate --token <weblate-token> delete --slug <slug>- Ensure your module follows the standard directory structure for strings.
- Run the
createcommand to identify and create missing components. - The tool will scan for modules containing
strings.xmland prompt you to create components for those missing from Weblate. - After creation, add the new component slug to
cli/weblate-cli/managed-components.txtto keep it updated with future configuration changes.
For more details, see the Weblate CLI README.
- 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
- 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.
When a new module contains translatable strings, a new Weblate component must be created. We provide a Weblate CLI to automate this process. See the Weblate CLI section for more details.
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