
Automates internationalization by processing YAML translation files, generating platform-specific resources, and type-safe code. Offers type-safe access, streamlined team collaboration, and seamless production readiness with efficient, structured translation management.
Kloca is a KSP (Kotlin Symbol Processing) Gradle plugin that automates internationalization (i18n) for Kotlin Multiplatform projects. It processes YAML translation files and generates platform-specific resources and type-safe Kotlin code.
Picture this: You're building a Kotlin Multiplatform app that needs to support multiple languages. Your team includes iOS and Android developers, designers, and translators. Each platform handles localization differently - Android uses XML string resources, iOS uses .strings files, and your shared business logic needs type-safe access to translations.
Traditional approaches lead to:
Kloca transforms this complexity into simplicity with a single source of truth approach:
# One YAML file for all platforms
i18n:
onboarding:
welcome: "Welcome to our app!"
get_started: "Get Started"
learn_more: "Learn more about %s"One command generates everything:
./gradlew generateTranslationsResult: Platform-native resources + type-safe code
# Translators work with familiar YAML
# Designers can easily reference keys
# Developers get compile-time safety
i18n:
user_profile:
title: "Profile"
edit_button: "Edit"
save_changes: "Save Changes"Kloca's YAML tree structure naturally organizes translations by feature, module, and screen, making it incredibly easy to manage translations and understand where they're used:
i18n:
# Feature-based organization
authentication:
login:
title: "Sign In"
email_hint: "Email address"
password_hint: "Password"
forgot_password: "Forgot password?"
sign_in_button: "Sign In"
registration:
title: "Create Account"
terms_agreement: "I agree to the %s and %s"
create_account_button: "Create Account"
# Screen-based organization
home:
welcome_message: "Welcome back, %s!"
quick_actions: "Quick Actions"
recent_activity: "Recent Activity"
# Module-based organization
payment:
checkout:
total: "Total: %s"
pay_button: "Pay Now"
history:
title: "Payment History"
empty_state: "No payments yet"Why This Matters:
authentication.login.title immediately tells you this is the login screen titleGenerated Type-Safe Keys:
object StringKeys {
const val AUTHENTICATION_LOGIN_TITLE = "authentication.login.title"
const val AUTHENTICATION_LOGIN_EMAIL_HINT = "authentication.login.email_hint"
const val HOME_WELCOME_MESSAGE = "home.welcome_message"
const val PAYMENT_CHECKOUT_TOTAL = "payment.checkout.total"
}This hierarchical approach makes your feature modules more flexible and manageable because:
Add to your build.gradle.kts:
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca") version "0.1.0"
}kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.rlce:kloca-runtime:0.1.0")
}
}
}Create YAML files in your configured source directory:
src/main/kloca/i18n.en.yaml:
i18n:
greeting:
hello: "Hello"
goodbye: "Goodbye"
welcome: "Welcome, %s!"
navigation:
home: "Home"
settings: "Settings"src/main/kloca/i18n.es.yaml:
i18n:
greeting:
hello: "Hola"
goodbye: "Adiós"
welcome: "¡Bienvenido, %s!"
navigation:
home: "Inicio"
settings: "Configuración"import dev.rlce.kloca.runtime.DefaultAndroidLanguagePersistence
import dev.rlce.kloca.runtime.Kloca
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize with system language only
Kloca.initialize(
context = this,
languagePersistenceFactory = { DefaultAndroidLanguagePersistence(this) }
)
// OR initialize with user language selection support
val availableLanguages = arrayOf("en", "es", "fr", "de")
Kloca.initialize(
context = this,
availableLanguages = availableLanguages,
languagePersistenceFactory = { DefaultAndroidLanguagePersistence(this) }
)
}
}import dev.rlce.kloca.runtime.DefaultIosLanguagePersistence
import dev.rlce.kloca.runtime.Kloca
// In your shared KMP initialization code
fun initializeApp() {
// Initialize with system language only
Kloca.initialize(
context = Unit,
languagePersistenceFactory = { DefaultIosLanguagePersistence() }
)
// OR initialize with user language selection support
val availableLanguages = arrayOf("en", "es", "fr", "de")
Kloca.initialize(
context = Unit,
availableLanguages = availableLanguages,
languagePersistenceFactory = { DefaultIosLanguagePersistence() }
)
}val greeting = Kloca.getString("greeting.hello")
val welcome = Kloca.getString("greeting.welcome", "John")val greeting = Kloca.getString(StringKeys.GREETING_HELLO)
val welcome = Kloca.getString(StringKeys.GREETING_WELCOME, "John")@Composable
fun GreetingScreen() {
Column {
Text(localizedString("greeting.hello"))
Text(localizedString(StringKeys.GREETING_WELCOME, "John"))
// Or using extension functions
Text(StringKeys.GREETING_HELLO.localized())
Text(StringKeys.GREETING_WELCOME.localized("John"))
}
}// Check available languages
val languages = Kloca.getAvailableLanguages()
println("Available: ${languages.joinToString()}")
// Set user preferred language
Kloca.setUserLanguage("es")
// Switch back to system language
Kloca.useSystemLanguage()
// Check current mode
val isSystemMode = Kloca.isUsingSystemLanguage()
val currentLang = Kloca.getCurrentLanguage()In your shared module's build.gradle.kts:
plugins {
kotlin("multiplatform")
id("com.android.library")
id("io.github.rlce.kloca") version "0.1.0"
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.rlce:kloca-runtime:0.1.0")
}
}
}Create or update your Application class:
// android/src/main/kotlin/com/yourapp/MyApplication.kt
import android.app.Application
import dev.rlce.kloca.runtime.DefaultAndroidLanguagePersistence
import dev.rlce.kloca.runtime.Kloca
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Kloca with application context and language persistence factory
Kloca.initialize(
context = this,
languagePersistenceFactory = { DefaultAndroidLanguagePersistence(this) }
)
}
}The plugin automatically hooks into the Android build process. You can also run generation manually:
# Generate translations for Android
./gradlew generateTranslations
# Build with fresh translations
./gradlew clean buildKloca generates standard Android string resources:
android/
├── src/main/res/
│ ├── values/strings.xml # Default language (en)
│ ├── values-es/strings.xml # Spanish
│ ├── values-fr/strings.xml # French
│ └── values-de/strings.xml # German
// In Activities or Fragments
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Direct usage
val greeting = Kloca.getString("greeting.hello")
// Type-safe usage
val title = Kloca.getString(StringKeys.NAVIGATION_HOME)
// With parameters
val welcome = Kloca.getString(StringKeys.GREETING_WELCOME, "John")
setContent {
MyAppTheme {
GreetingScreen()
}
}
}
}@Composable
fun GreetingScreen() {
Column(
modifier = Modifier.padding(16.dp)
) {
// Direct string access
Text(
text = localizedString("greeting.hello"),
style = MaterialTheme.typography.headlineMedium
)
// Type-safe access
Text(
text = StringKeys.GREETING_WELCOME.localized("User"),
style = MaterialTheme.typography.bodyLarge
)
// With formatting
Text(
text = localizedString("greeting.welcome", "John Doe"),
style = MaterialTheme.typography.bodyMedium
)
}
}Kloca automatically responds to system locale changes:
The same configuration in your shared module works for iOS:
// In your shared module's build.gradle.kts
kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
iosMain.dependencies {
implementation("dev.rlce.kloca:runtime:0.1.0")
}
}
}Kloca automatically generates localization resources in iosMain/resources/ with proper bundle structure:
shared/src/iosMain/resources/
├── en.lproj/Localizable.strings
├── es.lproj/Localizable.strings
├── fr.lproj/Localizable.strings
└── de.lproj/Localizable.strings
The improved iOS StringProvider automatically detects and loads these resources using NSBundle - no manual Xcode setup required!
The iOS StringProvider automatically:
NSLocale.currentLocale.languageCode).lproj bundle for that languageUse the generated translations in Swift:
// ViewController.swift
import UIKit
import shared // Your KMP shared module
class ViewController: UIViewController {
@IBOutlet weak var greetingLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Using Kloca from KMP
greetingLabel.text = Kloca.shared.getString(key: "greeting.hello")
// Type-safe access
let title = Kloca.shared.getString(key: StringKeys.shared.NAVIGATION_HOME)
self.title = title
// With parameters
let welcome = Kloca.shared.getString(key: StringKeys.shared.GREETING_WELCOME, args: ["John"])
print(welcome)
}
}// SwiftUI View
import SwiftUI
import shared
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text(Kloca.shared.getString(key: "greeting.hello"))
.font(.largeTitle)
Text(Kloca.shared.getString(key: StringKeys.shared.GREETING_WELCOME, args: ["SwiftUI"]))
.font(.body)
Button(Kloca.shared.getString(key: StringKeys.shared.NAVIGATION_HOME)) {
// Action
}
}
.padding()
}
}The plugin generates several artifacts:
object StringKeys {
const val GREETING_HELLO = "greeting.hello"
const val GREETING_GOODBYE = "greeting.goodbye"
const val GREETING_WELCOME = "greeting.welcome"
const val NAVIGATION_HOME = "navigation.home"
const val NAVIGATION_SETTINGS = "navigation.settings"
}<!-- values/strings.xml -->
<resources>
<string name="greeting_hello">Hello</string>
<string name="greeting_goodbye">Goodbye</string>
<string name="greeting_welcome">Welcome, %s!</string>
<string name="navigation_home">Home</string>
<string name="navigation_settings">Settings</string>
</resources>/* en.lproj/Localizable.strings */
"greeting.hello" = "Hello";
"greeting.goodbye" = "Goodbye";
"greeting.welcome" = "Welcome, %s!";
"navigation.home" = "Home";
"navigation.settings" = "Settings";
The plugin works with minimal configuration. Most settings use opinionated defaults:
kloca {
defaultLanguage = "en" // Default language (default: "en")
namespacePrefix = "MyApp" // Class prefix (default: project name)
}✅ What defaultLanguage DOES:
values/strings.xml foldervalues-<lang>/strings.xml folders❌ What defaultLanguage DOES NOT do:
Example with Spanish as default:
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca") version "0.1.0"
}
kloca {
defaultLanguage = "es" // Spanish in Android values/ folder
namespacePrefix = "MyApp" // Generates MyAppStringKeys class
}Generated Android Resources:
android/res/
├── values/strings.xml ← Spanish content (default)
├── values-en/strings.xml ← English content
└── values-fr/strings.xml ← French content
Generated iOS Resources (unchanged by defaultLanguage):
shared/src/iosMain/resources/
├── es.lproj/Localizable.strings ← Spanish
├── en.lproj/Localizable.strings ← English
└── fr.lproj/Localizable.strings ← French
✅ What namespacePrefix DOES:
Examples:
// Default (uses project name)
namespacePrefix = "" // → StringKeys (or ProjectStringKeys)
// Custom prefix
namespacePrefix = "MyApp" // → MyAppStringKeys
namespacePrefix = "Auth" // → AuthStringKeys
namespacePrefix = "Profile" // → ProfileStringKeysGenerated Code:
// With namespacePrefix = "MyApp"
object MyAppStringKeys {
const val GREETING_HELLO = "greeting.hello"
const val NAVIGATION_HOME = "navigation.home"
}Kloca supports nested YAML structures with dot notation flattening:
i18n:
feature:
screen:
title: "Screen Title"
button: "Click Me"
dialog:
message: "Are you sure?"
confirm: "Yes"
cancel: "No"This generates keys like:
feature.screen.titlefeature.screen.buttonfeature.dialog.messageIf you prefer to manage your translation files manually or use a different build system, you can use Kloca runtime libraries independently:
kotlin {
sourceSets {
commonMain.dependencies {
// Core runtime for string access
implementation("io.github.rlce:kloca-runtime:0.1.0")
// Optional: Compose integration
implementation("io.github.rlce:kloca-runtime-compose:0.1.0")
}
}
}Place your translation files in standard Android resources:
android/src/main/res/
├── values/strings.xml # Default language
├── values-es/strings.xml # Spanish
└── values-fr/strings.xml # French
Example values/strings.xml:
<resources>
<string name="greeting_hello">Hello</string>
<string name="greeting_welcome">Welcome, %s!</string>
<string name="navigation_home">Home</string>
</resources>Place your translation files in standard iOS localization structure:
shared/src/iosMain/resources/
├── en.lproj/Localizable.strings
├── es.lproj/Localizable.strings
└── fr.lproj/Localizable.strings
Example en.lproj/Localizable.strings:
"greeting.hello" = "Hello";
"greeting.welcome" = "Welcome, %s!";
"navigation.home" = "Home";
Create your own StringKeys object for type safety:
// src/commonMain/kotlin/StringKeys.kt
object StringKeys {
const val GREETING_HELLO = "greeting.hello"
const val GREETING_WELCOME = "greeting.welcome"
const val NAVIGATION_HOME = "navigation.home"
}// Initialize Kloca in your application
Kloca.initialize(
context = context,
languagePersistenceFactory = { /* platform-specific persistence implementation */ }
)
// or with user language selection support:
Kloca.initialize(
context = context,
availableLanguages = supportedLanguages,
languagePersistenceFactory = { /* platform-specific persistence implementation */ }
)
// Use in your code
val greeting = Kloca.getString(StringKeys.GREETING_HELLO)
val welcome = Kloca.getString(StringKeys.GREETING_WELCOME, "John")
// In Compose (if using compose runtime)
@Composable
fun MyScreen() {
Text(StringKeys.GREETING_HELLO.localized())
}✅ Good for:
❌ Not recommended when:
For projects with multiple modules, simply apply the plugin to each module:
// Module 1: feature-auth
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca")
}
// Module 2: feature-profile
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca")
}Each module generates its own StringKeys class with project-based naming:
object AuthStringKeys { ... } // Generated for auth module
object ProfileStringKeys { ... } // Generated for profile moduleSupport for string formatting:
i18n:
messages:
welcome: "Welcome, %s!"
item_count: "You have %d items"
progress: "Progress: %d%%"
user_info: "User: %s, Age: %d, Email: %s"Usage:
// Single parameter
Kloca.getString("messages.welcome", "John")
// Multiple parameters
Kloca.getString("messages.user_info", "John", 25, "john@example.com")Symptom: Key returned instead of translated text Solution:
Symptom: YAML parsing failed
Solution:
i18n: root key in YAML filesSymptom: English text shows instead of localized Solution:
./gradlew generateTranslations to ensure resources are generated in iosMain/resources/
.lproj folders exist in shared/src/iosMain/resources/
Symptom: Duplicate resource errors
Solution:
Check generated files:
# View generated resources
find . -name "*generated*" -type d | grep kloca
# View generated StringKeys
cat build/generated/kloca/StringKeys.kt# Build all modules
./gradlew build
# Run tests
./gradlew test
# Generate translations (if you have a sample project configured)
./gradlew generateTranslationsThe sample directory contains a complete example showing both usage patterns:
We welcome contributions! Please see our Contributing Guide for details on:
This project is licensed under the MIT License - see the LICENSE file for details.
Kloca is a KSP (Kotlin Symbol Processing) Gradle plugin that automates internationalization (i18n) for Kotlin Multiplatform projects. It processes YAML translation files and generates platform-specific resources and type-safe Kotlin code.
Picture this: You're building a Kotlin Multiplatform app that needs to support multiple languages. Your team includes iOS and Android developers, designers, and translators. Each platform handles localization differently - Android uses XML string resources, iOS uses .strings files, and your shared business logic needs type-safe access to translations.
Traditional approaches lead to:
Kloca transforms this complexity into simplicity with a single source of truth approach:
# One YAML file for all platforms
i18n:
onboarding:
welcome: "Welcome to our app!"
get_started: "Get Started"
learn_more: "Learn more about %s"One command generates everything:
./gradlew generateTranslationsResult: Platform-native resources + type-safe code
# Translators work with familiar YAML
# Designers can easily reference keys
# Developers get compile-time safety
i18n:
user_profile:
title: "Profile"
edit_button: "Edit"
save_changes: "Save Changes"Kloca's YAML tree structure naturally organizes translations by feature, module, and screen, making it incredibly easy to manage translations and understand where they're used:
i18n:
# Feature-based organization
authentication:
login:
title: "Sign In"
email_hint: "Email address"
password_hint: "Password"
forgot_password: "Forgot password?"
sign_in_button: "Sign In"
registration:
title: "Create Account"
terms_agreement: "I agree to the %s and %s"
create_account_button: "Create Account"
# Screen-based organization
home:
welcome_message: "Welcome back, %s!"
quick_actions: "Quick Actions"
recent_activity: "Recent Activity"
# Module-based organization
payment:
checkout:
total: "Total: %s"
pay_button: "Pay Now"
history:
title: "Payment History"
empty_state: "No payments yet"Why This Matters:
authentication.login.title immediately tells you this is the login screen titleGenerated Type-Safe Keys:
object StringKeys {
const val AUTHENTICATION_LOGIN_TITLE = "authentication.login.title"
const val AUTHENTICATION_LOGIN_EMAIL_HINT = "authentication.login.email_hint"
const val HOME_WELCOME_MESSAGE = "home.welcome_message"
const val PAYMENT_CHECKOUT_TOTAL = "payment.checkout.total"
}This hierarchical approach makes your feature modules more flexible and manageable because:
Add to your build.gradle.kts:
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca") version "0.1.0"
}kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.rlce:kloca-runtime:0.1.0")
}
}
}Create YAML files in your configured source directory:
src/main/kloca/i18n.en.yaml:
i18n:
greeting:
hello: "Hello"
goodbye: "Goodbye"
welcome: "Welcome, %s!"
navigation:
home: "Home"
settings: "Settings"src/main/kloca/i18n.es.yaml:
i18n:
greeting:
hello: "Hola"
goodbye: "Adiós"
welcome: "¡Bienvenido, %s!"
navigation:
home: "Inicio"
settings: "Configuración"import dev.rlce.kloca.runtime.DefaultAndroidLanguagePersistence
import dev.rlce.kloca.runtime.Kloca
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize with system language only
Kloca.initialize(
context = this,
languagePersistenceFactory = { DefaultAndroidLanguagePersistence(this) }
)
// OR initialize with user language selection support
val availableLanguages = arrayOf("en", "es", "fr", "de")
Kloca.initialize(
context = this,
availableLanguages = availableLanguages,
languagePersistenceFactory = { DefaultAndroidLanguagePersistence(this) }
)
}
}import dev.rlce.kloca.runtime.DefaultIosLanguagePersistence
import dev.rlce.kloca.runtime.Kloca
// In your shared KMP initialization code
fun initializeApp() {
// Initialize with system language only
Kloca.initialize(
context = Unit,
languagePersistenceFactory = { DefaultIosLanguagePersistence() }
)
// OR initialize with user language selection support
val availableLanguages = arrayOf("en", "es", "fr", "de")
Kloca.initialize(
context = Unit,
availableLanguages = availableLanguages,
languagePersistenceFactory = { DefaultIosLanguagePersistence() }
)
}val greeting = Kloca.getString("greeting.hello")
val welcome = Kloca.getString("greeting.welcome", "John")val greeting = Kloca.getString(StringKeys.GREETING_HELLO)
val welcome = Kloca.getString(StringKeys.GREETING_WELCOME, "John")@Composable
fun GreetingScreen() {
Column {
Text(localizedString("greeting.hello"))
Text(localizedString(StringKeys.GREETING_WELCOME, "John"))
// Or using extension functions
Text(StringKeys.GREETING_HELLO.localized())
Text(StringKeys.GREETING_WELCOME.localized("John"))
}
}// Check available languages
val languages = Kloca.getAvailableLanguages()
println("Available: ${languages.joinToString()}")
// Set user preferred language
Kloca.setUserLanguage("es")
// Switch back to system language
Kloca.useSystemLanguage()
// Check current mode
val isSystemMode = Kloca.isUsingSystemLanguage()
val currentLang = Kloca.getCurrentLanguage()In your shared module's build.gradle.kts:
plugins {
kotlin("multiplatform")
id("com.android.library")
id("io.github.rlce.kloca") version "0.1.0"
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.rlce:kloca-runtime:0.1.0")
}
}
}Create or update your Application class:
// android/src/main/kotlin/com/yourapp/MyApplication.kt
import android.app.Application
import dev.rlce.kloca.runtime.DefaultAndroidLanguagePersistence
import dev.rlce.kloca.runtime.Kloca
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Kloca with application context and language persistence factory
Kloca.initialize(
context = this,
languagePersistenceFactory = { DefaultAndroidLanguagePersistence(this) }
)
}
}The plugin automatically hooks into the Android build process. You can also run generation manually:
# Generate translations for Android
./gradlew generateTranslations
# Build with fresh translations
./gradlew clean buildKloca generates standard Android string resources:
android/
├── src/main/res/
│ ├── values/strings.xml # Default language (en)
│ ├── values-es/strings.xml # Spanish
│ ├── values-fr/strings.xml # French
│ └── values-de/strings.xml # German
// In Activities or Fragments
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Direct usage
val greeting = Kloca.getString("greeting.hello")
// Type-safe usage
val title = Kloca.getString(StringKeys.NAVIGATION_HOME)
// With parameters
val welcome = Kloca.getString(StringKeys.GREETING_WELCOME, "John")
setContent {
MyAppTheme {
GreetingScreen()
}
}
}
}@Composable
fun GreetingScreen() {
Column(
modifier = Modifier.padding(16.dp)
) {
// Direct string access
Text(
text = localizedString("greeting.hello"),
style = MaterialTheme.typography.headlineMedium
)
// Type-safe access
Text(
text = StringKeys.GREETING_WELCOME.localized("User"),
style = MaterialTheme.typography.bodyLarge
)
// With formatting
Text(
text = localizedString("greeting.welcome", "John Doe"),
style = MaterialTheme.typography.bodyMedium
)
}
}Kloca automatically responds to system locale changes:
The same configuration in your shared module works for iOS:
// In your shared module's build.gradle.kts
kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
iosMain.dependencies {
implementation("dev.rlce.kloca:runtime:0.1.0")
}
}
}Kloca automatically generates localization resources in iosMain/resources/ with proper bundle structure:
shared/src/iosMain/resources/
├── en.lproj/Localizable.strings
├── es.lproj/Localizable.strings
├── fr.lproj/Localizable.strings
└── de.lproj/Localizable.strings
The improved iOS StringProvider automatically detects and loads these resources using NSBundle - no manual Xcode setup required!
The iOS StringProvider automatically:
NSLocale.currentLocale.languageCode).lproj bundle for that languageUse the generated translations in Swift:
// ViewController.swift
import UIKit
import shared // Your KMP shared module
class ViewController: UIViewController {
@IBOutlet weak var greetingLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Using Kloca from KMP
greetingLabel.text = Kloca.shared.getString(key: "greeting.hello")
// Type-safe access
let title = Kloca.shared.getString(key: StringKeys.shared.NAVIGATION_HOME)
self.title = title
// With parameters
let welcome = Kloca.shared.getString(key: StringKeys.shared.GREETING_WELCOME, args: ["John"])
print(welcome)
}
}// SwiftUI View
import SwiftUI
import shared
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text(Kloca.shared.getString(key: "greeting.hello"))
.font(.largeTitle)
Text(Kloca.shared.getString(key: StringKeys.shared.GREETING_WELCOME, args: ["SwiftUI"]))
.font(.body)
Button(Kloca.shared.getString(key: StringKeys.shared.NAVIGATION_HOME)) {
// Action
}
}
.padding()
}
}The plugin generates several artifacts:
object StringKeys {
const val GREETING_HELLO = "greeting.hello"
const val GREETING_GOODBYE = "greeting.goodbye"
const val GREETING_WELCOME = "greeting.welcome"
const val NAVIGATION_HOME = "navigation.home"
const val NAVIGATION_SETTINGS = "navigation.settings"
}<!-- values/strings.xml -->
<resources>
<string name="greeting_hello">Hello</string>
<string name="greeting_goodbye">Goodbye</string>
<string name="greeting_welcome">Welcome, %s!</string>
<string name="navigation_home">Home</string>
<string name="navigation_settings">Settings</string>
</resources>/* en.lproj/Localizable.strings */
"greeting.hello" = "Hello";
"greeting.goodbye" = "Goodbye";
"greeting.welcome" = "Welcome, %s!";
"navigation.home" = "Home";
"navigation.settings" = "Settings";
The plugin works with minimal configuration. Most settings use opinionated defaults:
kloca {
defaultLanguage = "en" // Default language (default: "en")
namespacePrefix = "MyApp" // Class prefix (default: project name)
}✅ What defaultLanguage DOES:
values/strings.xml foldervalues-<lang>/strings.xml folders❌ What defaultLanguage DOES NOT do:
Example with Spanish as default:
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca") version "0.1.0"
}
kloca {
defaultLanguage = "es" // Spanish in Android values/ folder
namespacePrefix = "MyApp" // Generates MyAppStringKeys class
}Generated Android Resources:
android/res/
├── values/strings.xml ← Spanish content (default)
├── values-en/strings.xml ← English content
└── values-fr/strings.xml ← French content
Generated iOS Resources (unchanged by defaultLanguage):
shared/src/iosMain/resources/
├── es.lproj/Localizable.strings ← Spanish
├── en.lproj/Localizable.strings ← English
└── fr.lproj/Localizable.strings ← French
✅ What namespacePrefix DOES:
Examples:
// Default (uses project name)
namespacePrefix = "" // → StringKeys (or ProjectStringKeys)
// Custom prefix
namespacePrefix = "MyApp" // → MyAppStringKeys
namespacePrefix = "Auth" // → AuthStringKeys
namespacePrefix = "Profile" // → ProfileStringKeysGenerated Code:
// With namespacePrefix = "MyApp"
object MyAppStringKeys {
const val GREETING_HELLO = "greeting.hello"
const val NAVIGATION_HOME = "navigation.home"
}Kloca supports nested YAML structures with dot notation flattening:
i18n:
feature:
screen:
title: "Screen Title"
button: "Click Me"
dialog:
message: "Are you sure?"
confirm: "Yes"
cancel: "No"This generates keys like:
feature.screen.titlefeature.screen.buttonfeature.dialog.messageIf you prefer to manage your translation files manually or use a different build system, you can use Kloca runtime libraries independently:
kotlin {
sourceSets {
commonMain.dependencies {
// Core runtime for string access
implementation("io.github.rlce:kloca-runtime:0.1.0")
// Optional: Compose integration
implementation("io.github.rlce:kloca-runtime-compose:0.1.0")
}
}
}Place your translation files in standard Android resources:
android/src/main/res/
├── values/strings.xml # Default language
├── values-es/strings.xml # Spanish
└── values-fr/strings.xml # French
Example values/strings.xml:
<resources>
<string name="greeting_hello">Hello</string>
<string name="greeting_welcome">Welcome, %s!</string>
<string name="navigation_home">Home</string>
</resources>Place your translation files in standard iOS localization structure:
shared/src/iosMain/resources/
├── en.lproj/Localizable.strings
├── es.lproj/Localizable.strings
└── fr.lproj/Localizable.strings
Example en.lproj/Localizable.strings:
"greeting.hello" = "Hello";
"greeting.welcome" = "Welcome, %s!";
"navigation.home" = "Home";
Create your own StringKeys object for type safety:
// src/commonMain/kotlin/StringKeys.kt
object StringKeys {
const val GREETING_HELLO = "greeting.hello"
const val GREETING_WELCOME = "greeting.welcome"
const val NAVIGATION_HOME = "navigation.home"
}// Initialize Kloca in your application
Kloca.initialize(
context = context,
languagePersistenceFactory = { /* platform-specific persistence implementation */ }
)
// or with user language selection support:
Kloca.initialize(
context = context,
availableLanguages = supportedLanguages,
languagePersistenceFactory = { /* platform-specific persistence implementation */ }
)
// Use in your code
val greeting = Kloca.getString(StringKeys.GREETING_HELLO)
val welcome = Kloca.getString(StringKeys.GREETING_WELCOME, "John")
// In Compose (if using compose runtime)
@Composable
fun MyScreen() {
Text(StringKeys.GREETING_HELLO.localized())
}✅ Good for:
❌ Not recommended when:
For projects with multiple modules, simply apply the plugin to each module:
// Module 1: feature-auth
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca")
}
// Module 2: feature-profile
plugins {
kotlin("multiplatform")
id("io.github.rlce.kloca")
}Each module generates its own StringKeys class with project-based naming:
object AuthStringKeys { ... } // Generated for auth module
object ProfileStringKeys { ... } // Generated for profile moduleSupport for string formatting:
i18n:
messages:
welcome: "Welcome, %s!"
item_count: "You have %d items"
progress: "Progress: %d%%"
user_info: "User: %s, Age: %d, Email: %s"Usage:
// Single parameter
Kloca.getString("messages.welcome", "John")
// Multiple parameters
Kloca.getString("messages.user_info", "John", 25, "john@example.com")Symptom: Key returned instead of translated text Solution:
Symptom: YAML parsing failed
Solution:
i18n: root key in YAML filesSymptom: English text shows instead of localized Solution:
./gradlew generateTranslations to ensure resources are generated in iosMain/resources/
.lproj folders exist in shared/src/iosMain/resources/
Symptom: Duplicate resource errors
Solution:
Check generated files:
# View generated resources
find . -name "*generated*" -type d | grep kloca
# View generated StringKeys
cat build/generated/kloca/StringKeys.kt# Build all modules
./gradlew build
# Run tests
./gradlew test
# Generate translations (if you have a sample project configured)
./gradlew generateTranslationsThe sample directory contains a complete example showing both usage patterns:
We welcome contributions! Please see our Contributing Guide for details on:
This project is licensed under the MIT License - see the LICENSE file for details.