The IDE UI (:ide-ui, Compose Multiplatform commonMain) is localized through the Compose
resources system. All user-facing text lives in string resources so it can be translated per
locale; no user-facing string is hardcoded in a composable.
app/ide-ui/src/commonMain/composeResources/
values/strings.xml # default (English) — the source of truth for keys
values-zh/strings.xml # Chinese translation
values-<lang>/strings.xml # add a locale by adding a directory
A locale file only needs the keys it translates. Any key missing from values-<lang>/ falls
back to the same key in values/, so a partial translation is safe and English shows through
until a translator fills the gap. New keys are added to values/strings.xml only; translators
add the matching entries to each locale later.
The generated accessor class is dev.ide.ui.generated.resources.Res (configured in
app/ide-ui/build.gradle.kts under compose.resources { ... }, publicResClass = false so it stays
an internal dev.ide.ui detail).
Each key is a generated top-level property that must be imported individually:
import dev.ide.ui.generated.resources.Res
import dev.ide.ui.generated.resources.save
import org.jetbrains.compose.resources.stringResource
Text(stringResource(Res.string.save))
stringResource and pluralStringResource are @Composable. Call them inside a composable (or a
composable lambda). For a Kotlin keyword used as a key, backtick the import and use:
import dev.ide.ui.generated.resources.`continue` then stringResource(Res.string.`continue`).
Use positional %1$s / %1$d placeholders, not string interpolation:
<string name="run_building_module">Building %1$s…</string>
Text(stringResource(Res.string.run_building_module, moduleName))
<plurals name="modules">
<item quantity="one">%1$d module</item>
<item quantity="other">%1$d modules</item>
</plurals>
import org.jetbrains.compose.resources.pluralStringResource
Text(pluralStringResource(Res.plurals.modules, count, count))
When a string is needed outside composition (a suspend function, a coroutine, a data layer), use
the suspending accessors instead of stringResource:
import org.jetbrains.compose.resources.getString
val message = getString(Res.string.some_error) // suspend
// getPluralString(Res.plurals.some_count, n, n) for plurals
Prefer hoisting: resolve the string in the composable with stringResource and pass the String
down, rather than threading a suspend call through non-UI code.
snake_case, lowercase, derived from meaning, not from location (dep_no_results, not
screen3_text1).save, close, cancel,
delete, add, remove, rename, edit, apply, retry, copy, search, back,
error, and so on. Reuse an existing bare key rather than minting a synonym.dep_ (dependencies), modcfg_ (module config), buildc_ (build console),
filetree_ (file navigator), settings_, keystore_, sdk_, run_, preview_, and so on.Do not route these through strings.xml:
settings.<page>.<key>), enum names, node/block/valueKind kinds.contentDescription = null.Any new user-facing text (a label, title, message, placeholder, tooltip, empty state) gets a key
in values/strings.xml and is read with stringResource / pluralStringResource. Do not commit
a hardcoded user-facing literal in a composable.