Work In Progress
This documentation is in beta. It's missing lots of content, search is broken, and many links go nowhere. These problems will be fixed before release, but there's plenty of work left!
Skip to main content

Keys

Translation keys represent individual, named strings in your translation bundle files. These are represented by Key objects that contain information on their containing bundle and refer to the string's name, and can be copied along with a host of extra data to be used when those strings are eventually translated.

Generally speaking, you don't need to create your own Key objects, as they're generated by the Gradle plugin and our code generators.

General Usage

Key objects are immutable data classes. They can be serialised using kotlinx.serialization, and you can use their API functions to create clones with extra data and edited settings.

We designed these Key objects to be copied and passed around because this makes them considerably more flexible when compared to passing around Strings representing translation keys, providing a single container type that does everything you'd need.

Basic Translations

All translations happen in the context of a specific locale, represented by a Java Locale object. The following example assumes ICU message format v2, with the following key:

  • command.slap.name -> slap
// Implicitly creating a clone with the given locale,
// and immediately translating it.
Translations.Command.Slap.name
.translateLocale(locale)

// Equivalent call using withLocale directly,
// creating a clone with the given locale explicitly
// before translating it.
Translations.Command.Slap.name
.withLocale(locale)
.translate()

Placeholders

Placeholder Types

Keys support both ordinal and named placeholder types. However, they only support one type at a time, and mixing them may result in unpredictable behaviour.

Additionally, please note that some message formats only support named placeholders, and will throw an exception if you try to use ordinal placeholders. For that reason, we recommend you only use named placeholders, unless you know for sure you'll only ever need to use ordinal placeholders.

Depending on the message format you're using, this framework supports two types of placeholders — ordinal and named.

The following examples assume ICU message format v1, with the following keys:

  • command.mappings.ordinal -> Information on the {0} mappings for Minecraft.
  • command.mappings.named -> Information on the {name} mappings for Minecraft.

Ordinal

Ordinal placeholders can be thought of as a simple indexed array, where placeholders are represented by the number referring to their index in the array.

// Implicit, without storing placeholders in an
// extra `Key` copy.
Translations.Command.Mappings.ordinal
.translateNamedLocale(locale, "name" to "hashed")

// Implicit, storing placeholders in an
// extra `Key` copy before translating.
Translations.Command.Mappings.ordinal
.withNamedPlaceholders("name" to "hashed")
.translateLocale(locale)

// Explicit, without storing placeholders in an
// extra `Key` copy.
Translations.Command.Mappings.ordinal
.withLocale(locale)
.translateNamed("name" to "hashed")

// Explicit, storing placeholders in an
// extra `Key` copy before translating.
Translations.Command.Mappings.ordinal
.withLocale(locale)
.withNamedPlaceholders("name" to "hashed")
.translate()

Named

Named placeholders can be thought of as a map, where placeholders are represented by the string key referring to their place in the map.

// Implicit, without storing placeholders in an
// extra `Key` copy.
Translations.Command.Mappings.named
.translateNamedLocale(locale, "name" to "hashed")

// Implicit, storing placeholders in an
// extra `Key` copy before translating.
Translations.Command.Mappings.named
.withNamedPlaceholders("name" to "hashed")
.translateLocale(locale)

// Explicit, without storing placeholders in an
// extra `Key` copy.
Translations.Command.Mappings.named
.withLocale(locale)
.translateNamed("name" to "hashed")

// Explicit, storing placeholders in an
// extra `Key` copy before translating.
Translations.Command.Mappings.named
.withLocale(locale)
.withNamedPlaceholders("name" to "hashed")
.translate()

Advanced Concepts

We designed Key objects to support several advanced use-cases, solving problems we ran into when we were designing our Discord bot framework.

Nesting Keys

When you provide values for placeholders in your translation strings, sometimes you need to replace a placeholder with another translated string. By default, Key objects will detect any nested Keys in your placeholders and automatically translate them, which makes it easier to nest them.

Nested Keys will inherit the parent key's bundle and locale, but only if the nested Key doesn't already contain them.

The following example assumes ICU message format v2, with the following keys:

  • command.time -> Get the current time in {$country}.
  • country.france -> France
// Manual approach.
Translations.Command.time
.withLocale(locale)
.translateNamed(
"country" to Translations.Country.France
.withLocale(locale)
.translate()
)

// With nesting.
Translations.Command.time
.withLocale(locale)
.translateNamed("country" to Translations.Country.France)

If you need to disable this feature, you can use the withTranslateNestedKeys() function.

// Disable nested `Key` support.
Translations.Command.time
.withTranslateNestedKeys(false)
// ...

Post-Processors

Post-processors are applied after a Key is translated, to modify the translated value before the translation functions return it. These are simply lambdas, of the form: Key.(translation: String) -> String

You can register a single post-processor using the withPostProcessor() function, which you can also use as a builder. If you need to register multiple post-processors at once, you can pass a collection to the withPostProcessors() function.

We also provide a handful of convenience functions you can use to add common post-processors to a Key. These functions use the locale stored in the Key, or the configured default locale if the Key doesn't have one set.

Key.capitalize(...)Returns:Key

Capitalise the first letter of the translated string, if that makes sense in the current locale.

Key.capitalizeWords(...)Returns:Key

Capitalise the first letter of each word in the translated string, if that makes sense in the current locale.

Key.lowercase(...)Returns:Key

Transform the translated string to lower-case letters, if that makes sense in the current locale.

Key.uppercase(...)Returns:Key

Transform the translated string to upper-case letters, if that makes sense in the current locale.

Preset Placeholders

Sometimes, it is useful to be able to store placeholders in a copy of a Key object, to be passed around and translated elsewhere. Keys can store both named and ordinal placeholders, and you can create clones containing them using the withNamedPlaceholders() and withOrdinalPlaceholders() functions, respectively.

These placeholders are stored in the namedPlaceholders and ordinalPlaceholders properties, allowing other functions to retrieve them and act accordingly.

The following example assumes ICU message format v2, with the following key:

  • command.time.response -> The time in {$country} is {$time}.
// Create `Key` clone with a locale and a preset placeholder.
val key = Translations.Command.Time.response
.withLocale(locale)
.withNamedPlaceholders("country" to "France")

// Pass the key to another function, which translates the `Key`,
// providing the correct time.
user.sendTime(key)

You can use withPresetPlaceholderPosition() to change whether the preset placeholders are processed before or after any placeholders passed to the translation functions, a particularly useful tool for ordinal placeholders.

Provide FIRST (the default) to use the preset placeholders first, or LAST to use the translation function's arguments first:

  • Named Placeholders: Combine them into a single map in the given order, giving priority to any duplicate values in the second map.
  • Ordinal Placeholders: Combine them into a single list, placing the first list at the beginning, and the second list at the end.

Removing Settings

Key objects also provide several functions you can use to copy them with filtered preset placeholders, or without certain settings.

Filtering Functions

filterNamedPlaceholders { ... }Function Returns: Key

Returns a copy of this Key, containing a filtered set of named placeholders based on the provided predicate.

filterOrdinalPlaceholders { ... }Function Returns: Key

Returns a copy of this Key, containing a filtered set of ordinal placeholders based on the provided predicate.

filterPostProcessors { ... }Function Returns: Key

Returns a copy of this Key, containing a filtered set of post-processors based on the provided predicate.

Removal Functions

withoutBoth(...)Returns:Key

Returns a copy of this Key, with the stored bundle and locale removed.

Returns the same Key if both are already null.

withoutBundle(...)Returns:Key

Returns a copy of this Key, with the stored bundle removed.

Returns the same Key if the bundle is already null.

withoutLocale(...)Returns:Key

Returns a copy of this Key, with the stored locale removed.

Returns the same Key if the locale is already null.

withoutNamedPlaceholders(...)Returns:Key

Returns a copy of this Key, with the stored named preset placeholders removed.

Returns the same Key if there are already no named preset placeholders.

withoutOrdinalPlaceholders(...)Returns:Key

Returns a copy of this Key, with the stored ordinal preset placeholders removed.

Returns the same Key if there are already no ordinal preset placeholders.

Replacing Settings

You can replace specific Key object settings using the following functions.

withBoth(...)Returns:Key

Returns a copy of this Key, potentially with a new bundle and locale set.

Arguments
bundleType: Bundle

The new bundle to store in the Key.

localeType: Locale

The new locale to store in the Key.

overwriteBundleType: BooleanDefault: true

Whether to overwrite the bundle, if this Key already contains one.

Provide false and the existing bundle will not be overwritten.

overwriteLocaleType: BooleanDefault: true

Whether to overwrite the locale, if this Key already contains one.

Provide false and the existing locale will not be overwritten.

withBundle(...)Returns:Key

Returns a copy of this Key, potentially with a new bundle set.

Arguments
bundleType: Bundle

The new bundle to store in the Key.

overwriteType: BooleanDefault: true

Whether to overwrite the bundle, if this Key already contains one.

Provide false and the existing bundle will not be overwritten.

withLocale(...)Returns:Key

Returns a copy of this Key, potentially with a new locale set.

Arguments
localeType: Bundle

The new locale to store in the Key.

overwriteType: BooleanDefault: true

Whether to overwrite the locale, if this Key already contains one.

Provide false and the existing locale will not be overwritten.

Other APIs

toString(...)

Returns a string in this form: Key "$name" (Bundle $bundle, Locale $locale).

The bundle and locale will be omitted if they're missing.