docs/contributing/managing-strings.md
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.
en).Source strings are always stored in English (en). They must be managed carefully to avoid breaking
existing translations.
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.kmpCompose for a KMP library module with Compose support.
plugins {
id(ThunderbirdPlugins.Library.kmpCompose)
}
Configure compose.resources block:
Set publicResClass to false and provide a unique packageOfResClass.
compose.resources {
publicResClass = false
packageOfResClass = "net.thunderbird.feature.yourfeature.resources"
}
Ensure android namespace is set:
The Android target requires a namespace in the kotlin block.
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):
This ensures translators do not work on outdated strings and avoids merge conflicts.
res/values/strings.xmlsrc/commonMain/composeResources/values/strings.xmlThere are two kinds of changes to source strings:
Correcting minor errors (spelling, capitalization, punctuation, grammar) in the English source is allowed:
Example:
[!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):
values-*/strings.xml).This ensures there are no stale or misleading translations left behind.
Example:
action_check_mail)action_sync_mail)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
Resclass is generated by the Compose Multiplatform Gradle plugin. You may need to build the project to generate it after adding new strings.
When merging Weblate-generated PRs:
many and other forms are present.
many or other is acceptable.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.
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
--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 in app-k9mail/build.gradle.kts and app-thunderbird/build.gradle.ktssupported_languages in legacy/core/src/res/values/arrays_general_settings_values.xmlandroidResources.localeFilters in:
app-thunderbird/build.gradle.ktsapp-k9mail/build.gradle.ktssupported_languages in:
app/core/src/main/res/values/arrays_general_settings_values.xmlandroidResources.localeFilters in both app modules.supported_languages in:
app/core/src/main/res/values/arrays_general_settings_values.xmlapp/ui/legacy/src/main/res/values/arrays_general_settings_strings.xml (sorted by Unicode default collation order).language_entries and language_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.
Steps:
feature:notification:api).path/to/module/src/main/res/values-*/strings.xmlpath/to/module/src/commonMain/composeResources/values-*/strings.xmlpath/to/module/src/main/res/values/strings.xmlpath/to/module/src/commonMain/composeResources/values/strings.xmlAndroid 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