
Generates and manages resources such as strings and images, supporting various formats and localization options. Integrates with Jetpack Compose, handles platform-specific image processing, and ensures seamless resource access across platforms.
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.
Add the Libres Gradle plugin artifact to the project buildscript:
// build.gradle.kts (project)
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:
// build.gradle.kts (module)
plugins {
id("io.github.skeptick.libres")
}
libres {
generatedClassName = "MainRes"
generateNamedArguments = true
generateInternalResourceClasses = true
baseLocaleTag = "en"
}| Option | Default | Description |
|---|---|---|
generatedClassName |
"Res" |
Name of the generated root object. |
generatedClassPackageName |
Android namespace, or "libres.resources" when no Android namespace is available |
Package for the generated root object. |
generateNamedArguments |
false |
When true, resources with template parameters get generated formatter classes with named format(...) arguments. When false, formatted resources use generic format(vararg args: String) helpers. |
generateInternalResourceClasses |
false |
When true, generated resource classes use internal visibility instead of public. |
baseLocaleTag |
"en" |
Locale that must contain every resource and acts as the final fallback. |
camelCaseNamesForAppleFramework |
false |
Adds Swift/Objective-C camelCase names for generated string properties. Kotlin property names stay unchanged. |
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.
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
String files use an Android-like <resources> structure:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_title">Libres sample</string>
<string name="welcome_message">Hello ${name}!</string>
<plurals name="resource_count">
<item quantity="one">${count} resource</item>
<item quantity="other">${count} resources</item>
</plurals>
<string-array name="planets">
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
<item>Mars</item>
</string-array>
</resources>Supported resource tags:
<string name="...">...</string><plurals name="...">...</plurals> with zero, one, two, few, many, or other quantity items<string-array name="...">...</string-array>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
Script: optional, 4 letters, for example Hans, Cyrl
REGION: optional, 2 letters, for example US, CN
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.
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.
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_titleIf 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.
Libres chooses the current locale from the platform when possible:
| Platform | Default behavior |
|---|---|
| Android | Reads the current Android locale when a resource is accessed. |
| JVM | Reads Locale.getDefault() when a resource is accessed. |
| Apple platforms | Reads the app bundle preferred localization once at process start. |
| JS and WasmJS | Reads navigator.languages / navigator.language once at application start. |
| Linux, Windows, WasmWASI | No platform locale is detected automatically; the base locale is used unless you provide one. |
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.
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.
Add the Libres Gradle plugin artifact to the project buildscript:
// build.gradle.kts (project)
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:
// build.gradle.kts (module)
plugins {
id("io.github.skeptick.libres")
}
libres {
generatedClassName = "MainRes"
generateNamedArguments = true
generateInternalResourceClasses = true
baseLocaleTag = "en"
}| Option | Default | Description |
|---|---|---|
generatedClassName |
"Res" |
Name of the generated root object. |
generatedClassPackageName |
Android namespace, or "libres.resources" when no Android namespace is available |
Package for the generated root object. |
generateNamedArguments |
false |
When true, resources with template parameters get generated formatter classes with named format(...) arguments. When false, formatted resources use generic format(vararg args: String) helpers. |
generateInternalResourceClasses |
false |
When true, generated resource classes use internal visibility instead of public. |
baseLocaleTag |
"en" |
Locale that must contain every resource and acts as the final fallback. |
camelCaseNamesForAppleFramework |
false |
Adds Swift/Objective-C camelCase names for generated string properties. Kotlin property names stay unchanged. |
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.
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
String files use an Android-like <resources> structure:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_title">Libres sample</string>
<string name="welcome_message">Hello ${name}!</string>
<plurals name="resource_count">
<item quantity="one">${count} resource</item>
<item quantity="other">${count} resources</item>
</plurals>
<string-array name="planets">
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
<item>Mars</item>
</string-array>
</resources>Supported resource tags:
<string name="...">...</string><plurals name="...">...</plurals> with zero, one, two, few, many, or other quantity items<string-array name="...">...</string-array>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
Script: optional, 4 letters, for example Hans, Cyrl
REGION: optional, 2 letters, for example US, CN
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.
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.
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_titleIf 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.
Libres chooses the current locale from the platform when possible:
| Platform | Default behavior |
|---|---|
| Android | Reads the current Android locale when a resource is accessed. |
| JVM | Reads Locale.getDefault() when a resource is accessed. |
| Apple platforms | Reads the app bundle preferred localization once at process start. |
| JS and WasmJS | Reads navigator.languages / navigator.language once at application start. |
| Linux, Windows, WasmWASI | No platform locale is detected automatically; the base locale is used unless you provide one. |
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.