Utilities for the libGDX I18NBundle
.
As useful as I18NBundle
is, it is often overlooked in pure Java applications due to sheer amount of work that it
requires to set up and use consistently throughout the project. This module provides a thin layer of syntax sugar
that makes it easier to work with I18NBundle
instances and provide some useful code completion.
Direct I18NBundle
usage with strings as line IDs suffers from no code completion and compile-time validation. Sadly,
using simple string IDs as bundle lines might turn out to be not much better than using plain strings altogether. This
is why BundleLine
interface was created: it allows you to turn enums into I18NBundle
representations with minimal
effort.
For example, given this nls.properties
file:
key1=Value.
key2=Value with {0} argument.
...you can create an enum similar to this:
package ktx.i18n.example
import ktx.i18n.BundleLine
enum class Nls : BundleLine {
key1,
key2
}
This allows you to access bundle lines via enum instances:
import ktx.i18n.get
import ktx.i18n.example.Nls
bundle[Nls.key1]
Listing - or generating - all expected bundle lines is basically all you have to do to enjoy less verbose and safer
syntax with code completion and compile-time validation. See usage examples below to explore BundleLine
API.
Note that BundleLine
assumes that toString()
implementation returns a valid bundle line ID. If you want to change
the way BundleLine
implementations extract lines from I18NBundle
, override toString()
method.
You can use the following Gradle Groovy script to generate a Kotlin enum implementing BundleLine
according to an
existing .properties
bundle:
task nls << {
def project = 'core' // Will contain generated enum class.
def source = 'src/main/kotlin' // Kotlin source path of the project.
def pack = 'com.your.company' // Enum target package.
def name = 'Nls' // Enum class name.
def fileName = 'nls.kt' // Name of Kotlin file containing the enum.
def bundle = 'core/assets/i18n/nls.properties' // Path to i18n bundle file.
println("Processing i18n bundle file at ${bundle}...")
def builder = new StringBuilder()
builder.append("""package ${pack}
import ktx.i18n.BundleLine
/** Generated from ${bundle} file. */
enum class ${name} : BundleLine {
""")
def newLine = System.getProperty("line.separator")
file(bundle).eachLine {
def data = it.trim()
def separator = data.indexOf('=')
if (!data.isEmpty() && separator > 0 && !data.startsWith('#')) {
def id = data.substring(0, separator)
builder.append(' ').append(id).append(',').append(newLine)
}
}
// If you want a custom enum body, replace the following append:
builder.append(' ;').append(newLine).append('}').append(newLine)
source = source.replace('/', File.separator)
pack = pack.replace('.', File.separator)
def path = project + File.separator + source + File.separator + pack +
File.separator + fileName
println("Saving i18n bundle enum at ${path}...")
def enumFile = file(path)
delete enumFile
enumFile.getParentFile().mkdirs()
enumFile.createNewFile()
enumFile << builder << newLine
println("Done. I18n bundle enum generated.")
}
The first few lines contain task configuration, so make sure to pass the correct paths and names before running the task
with gradle nls
or ./gradlew nls
. Feel free to modify generated enum source to your needs. Be careful: current task
implementation replaces the enum class at the selected path.
You can access any bundle line with bundle["key"]
an bundle["key", arguments]
syntax. These methods also accept
BundleLine
instances, so if you would prefer not to assign static I18NBundle
instance in an enum, you can still
benefit from type-safe i18n with a pleasant syntax: bundle[key]
.
It is recommended to use import ktx.i18n.*
import when working directly with I18NBundle
instances.
Examples below assume the following bundle .properties
file content:
key=Value.
keyWithArguments=Accepts {0} arguments. {1}!
Using I18NBundle
with strings as IDs:
import ktx.i18n.*
import com.badlogic.gdx.utils.I18NBundle
val bundle = I18NBundle.createBundle(file)
bundle["key"] // Value.
bundle["keyWithArguments", 2, "Hello"] // Accepts 2 arguments. Hello!
Using I18NBundle
with BundleLine
enum instances as IDs:
package example
import example.Nls.*
import ktx.i18n.*
import com.badlogic.gdx.utils.I18NBundle
enum class Nls : BundleLine {
key,
keyWithArguments
}
// Usage:
val bundle = I18NBundle.createBundle(file)
bundle[key] // Value.
bundle[keyWithArguments, 2, "Hello"] // Accepts 2 arguments. Hello!
Using BundleLine
enum with assigned I18NBundle
instance:
package example
import example.Nls.*
import ktx.i18n.*
import com.badlogic.gdx.utils.I18NBundle
enum class Nls : BundleLine {
key,
keyWithArguments;
override val bundle: I18NBundle
get() = i18nBundle
companion object {
lateinit var i18nBundle: I18NBundle
}
}
// Assigning I18NBundle instance:
Nls.i18nBundle = I18NBundle.createBundle(FileHandle(File("i18n/gradle")))
// Reading lines from the bundle:
key.nls // Value.
keyWithArguments.nls(2, "Hello") // Accepts 2 arguments. Hello!
// More concise, but somewhat less obvious syntax:
key() // Value.
keyWithArguments(2, "Hello") // Accepts 2 arguments. Hello!
- LML supports translations of
Scene2D
widgets internally using libGDXI18NBundle
API. However, it requires creating views with HTML-like templates rather Java (or Kotlin) code.