Libres
Libres is a Gradle plugin that generates Kotlin accessors for localized string
resources. It is designed primarily for Kotlin Multiplatform projects, but it
can also be used in Android and Kotlin/JVM modules.
Generated accessors expose plain String values for simple resources and small
formatting helpers for resources with parameters. This keeps UI code close to
regular Kotlin string usage while still allowing locale-aware resource lookup.
[!IMPORTANT]
Libres 2.0.0 and newer support string resources only. Image resource sharing
was removed. If your project used Libres for images, migrate those assets to a
different solution, for example
Compose Multiplatform Resources.
The string API remains the focus of the plugin: it provides direct access to
strings without forcing UI code through resource wrappers. The old image
implementation depended on the CocoaPods plugin on iOS and was hard to keep
stable across platforms, so it is no longer maintained.
Installation
Add the Libres Gradle plugin artifact to the project buildscript:
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath("io.github.skeptick.libres:gradle-plugin:2.0.0-beta03")
}
}
Apply the plugin in the module that owns your string resources:
plugins {
id("io.github.skeptick.libres")
}
libres {
generatedClassName = "MainRes"
generateNamedArguments = true
generateInternalResourceClasses = true
baseLocaleTag = "en"
}
Configuration
Supported Projects
Libres supports Kotlin Multiplatform projects and generates common source code
that can be used from all configured targets. The repository currently includes
Android, JVM, iOS, watchOS, tvOS, macOS, Linux, Windows, JavaScript, WasmJS, and
WasmWASI targets in its setup.
The plugin can also be applied to non-multiplatform Android and Kotlin/JVM
modules that use the standard main source set layout.
Resource Location
For Kotlin Multiplatform projects, put string resources in commonMain:
├── src/
│ └── commonMain/
│ ├── kotlin/
│ └── libres/
│ └── strings/
│ ├── strings_en.xml
│ ├── strings_en-US.xml
│ ├── strings_zh-Hans-CN.xml
│ └── strings_ru.xml
For Android or Kotlin/JVM modules, put them in main:
├── src/
│ └── main/
│ ├── kotlin/
│ └── libres/
│ └── strings/
│ ├── strings_en.xml
│ ├── strings_en-US.xml
│ ├── strings_zh-Hans-CN.xml
│ └── strings_ru.xml
XML Format
String files use an Android-like <resources> structure:
Supported resource tags:
<string name="...">...</string>
<plurals name="...">...</plurals> with zero, one, two, , , or quantity items
Locale File Names
The locale tag is parsed from the part of the file name after the last underscore:
strings_en.xml
strings_en-US.xml
my_feature_strings_zh-Hans-CN.xml
Locale tags use the language[-Script][-REGION] BCP 47 shape:
language: required, 2 or 3 letters, for example en, ru, zh
You can split one locale across multiple files. Files for the same locale are merged during generation.
Every resource must exist in the base locale selected by baseLocaleTag. Other
locales may omit resources; Libres falls back per resource at runtime.
Template Parameters
Use Libres template parameters for formatted strings:
<string name="welcome_message">Hello ${name}!</string>
Each localized version of the same resource must use
the same set of parameter names as the base locale. The order may differ between locales.
Java/Android format specifiers such as %s and %1$s are not supported in XML
resources. Use ${name} style parameters instead.
Generated Accessors
With the configuration shown above, the generated root object is MainRes.
The examples below assume generateNamedArguments = true.
import com.example.application.MainRes
val title: String = MainRes.string.app_title
val welcome: String = MainRes.string.welcome_message.format(name = "John")
val count: String = MainRes.string.resource_count.format(number = 5, count = "5")
If generateNamedArguments = false, formatted resources use generic helpers:
MainRes.string.welcome_message.format("John")
MainRes.string.resource_count.format(5, "5")
From Swift, Kotlin objects are exposed through shared:
MainRes.shared.string.app_title
If camelCaseNamesForAppleFramework = true, generated string properties get
camelCase names in the Apple framework:
MainRes.shared.string.appTitle
[!NOTE]
Simple string accessors return the resolved String, not a long-lived resource
wrapper. Read them close to where the UI is rendered. If you cache a returned
string in your own state, your code is responsible for updating that cache when
the app locale changes.
Locale Selection
Libres chooses the current locale from the platform when possible:
Apple apps must declare supported localizations in Xcode project settings or in
the app bundle metadata. Otherwise Apple APIs may not report the expected app
localization to Libres.
You can override locale selection on any platform with LibresSettings:
import io.github.skeptick.libres.LibresLocale
import io.github.skeptick.libres.LibresSettings
LibresSettings.setLocaleProvider {
LibresLocale(language = "en", script = null, region = null)
}
The provider has priority over the platform locale and is called when Libres
resolves resources, so it can read from your app settings or state holder.
At runtime, Libres first looks for the best same-language match for the current
locale and then falls back to the base locale. For example, en-US prefers an
exact en-US resource, then other suitable en resources, and finally thebaseLocaleTag resource.