KLocale is a high-performance, developer-friendly localization library for Kotlin and PaperMC. It bridges the gap between raw configuration files and rich, interactive Adventure Components.
- High Performance: Pre-renders static messages to minimize object allocation.
- Smart Fallbacks: Automatic locale resolution (e.g., en_US -> en -> default).
- MiniMessage Ready: Native support for Kyori MiniMessage and legacy color codes.
- Single-Pass Replacement: Optimized placeholder system to prevent double-replacement issues.
- Multi-Provider: Load locales from YAML, GitHub, HTTP, or internal resources.
- Async Loading: Coroutine-based loading to keep your server tick-rate silky smooth.
- Fail-Safe: Customizable strategies for missing keys (MissingKeyHandler).
Add the library to your build.gradle.kts:
repositories {
maven("https://repo.nekroplex.com/releases")
}
dependencies {
implementation("gg.aquatic:KLocale:VERSION")
implementation("gg.aquatic:KLocale-Paper:VERSION")
}Initialize your locale manager using the clean Kotlin DSL:
val localeManager = KLocale.paper {
defaultLanguage = "en"
// Add one or more providers
providers += YamlLocaleProvider(
file = File(dataFolder, "lang.yml"),
serializer = YamlLocaleProvider.DefaultSerializer
)
// Optional: Use a custom MiniMessage instance (comes with 'ccmd' tag by default!)
// miniMessage = MiniMessage.miniMessage()
// Optional: Handle missing keys gracefully instead of throwing exceptions
missingKeyHandler = MissingKeyHandler.Throwing()
}
// Reload locales (suspended call)
scope.launch {
localeManager.invalidate()
}Fetching and sending messages is intuitive and chainable:
fun welcome(player: Player) {
localeManager.getOrDefault(player.locale(), "welcome-message")
.replace("player", player.name)
// Native support for replacing placeholders with rich Components
.replace("balance", Component.text("$500", NamedTextColor.GREEN))
.send(player)
}Implement CfgMessageHandler to access your messages globally with clean syntax:
Check out MessagesExample for a full example.
enum class Messages(override val path: String) : CfgMessageHandler<PaperMessage> {
WELCOME("welcome-message"),
STAFF_LIST("staff-list");
override val manager: LocaleManager<PaperMessage>
get() = MyPlugin.localeManager }
// Usage:
Messages.WELCOME.message(player.locale()).replace("player", name).send(player)If you need to support languages that aren't constants in java.util.Locale (like Czech, Slovak, or regional dialects), use IETF BCP 47 language tags:
// For Czech
val czech = Locale.forLanguageTag("cs-CZ")
// For custom/internal tags
val custom = Locale.forLanguageTag("pirate")
// Usage with manager
localeManager.getOrDefault(czech, "welcome-key")Attach logic directly to messages (useful for logging or triggering events):
message.withCallback { player, msg ->
plugin.logger.info("Sent ${msg.lines.size} lines to ${player.name}")
}.send(player)The MergedLocaleProvider allows you to combine base translations with local user overrides:
val localeManager = KLocale.paper(plugin) {
// You can specify your own minimessage with custom tag resolvers
miniMessage = MiniMessage.miniMessage()
provider = MergedLocaleProvider(
listOf(
// 1. Remote "Base" translations
GitHubLocaleProvider("User", "Repo", "path/to/locales", serializer),
// 2. Local "User" overrides (Takes priority)
YamlLocaleProvider(File(dataFolder, "overrides.yml"), serializer)
)
)
}You can define what happens when a key is missing by implementing MissingKeyHandler. The default behavior is to throw an exception, but you can override this globally:
class MyCustomHandler : MissingKeyHandler<PaperMessage> {
override fun handle(key: String, language: String): PaperMessage {
return PaperMessage.of(Component.text("Missing: $key", NamedTextColor.RED))
}
}Contributions are welcome! Please feel free to submit a Pull Request.
Got questions, need help, or want to showcase what you've built with KLocale? Join our community!
- Discord: Join the Aquatic Development Discord
- Issues: Open a ticket on GitHub for bugs or feature requests.
Built with ❤️ by Larkyy