I18n and L10n
I18n (short for internationalization) is the way of designing a program to work with multiple languages. L10n (short for localization) is the process of translating text into the user's language. Minecraft implements these using Component
s.
Component
s
A Component
is a piece of text with metadata, with the metadata including things such as text formatting. It can be created in one of the following ways (all of the following are static methods in the Component
interface):
Method | Description |
---|---|
empty | Creates an empty component. |
literal | Creates a component with the given text and directly displays that text without translating. |
nullToEmpty | Creates an empty component when given null, and a literal component otherwise. |
translatable | Creates a translatable component. The given string is then resolved as a translation key (see below). |
keybind | Creates a component containing the (translated) display name of the given keybind. |
nbt | Creates a component representing the NBT at the given path. |
score | Creates a component containing a scoreboard objective value. |
selector | Creates a component containing a list of entity names for a given entity selector. |
Component.translatable()
additionally has a vararg parameter that accepts string interpolation elements. This works similar to Java's String#format
, but always uses %s
instead of %i
, %d
, %f
and any other format specifier, calling #toString()
where needed.
Every Component
can be resolved using #getString()
. Resolving is generally lazy, meaning that the server can specify a Component
, send it to the clients, and the clients will each resolve the Component
s on their own (where different languages may result in different text). Many places in Minecraft will also directly accept Component
s and take care of resolving for you.
Never let a server translate a Component
. Always send Component
s to the client and resolve them there.
MutableComponent
All constructed components are generally MutableComponent
s. A MutableComponent
provides methods to add new component siblings that are appended to the end of this component and set the style of the text. Constructing or modifying a component should always refer to MutableComponent
s.
Text Formatting
Component
s can be formatted using Style
s. Style
s are immutable, creating a new Style
object when modified, and thus allowing them to be created once and then be reused as needed.
Styles can only be set using methods on MutableComponent
, so make sure to check that the Component
is a MutableComponent
.
Style.EMPTY
can generally be used as a base to work off. Multiple Style
s can be merged with Style#applyTo(Style other)
, which returns a new Style
that takes settings from the Style
the applyTo()
method was called on unless the respective setting is absent, in which case the setting from the Style
passed in as a parameter is used. Style
s can then be applied to components like so:
MutableComponent text = Component.literal("Hello World!");
// Create a new style.
Style blue = Style.EMPTY.withColor(0x0000FF);
// Styles use a builder-like pattern.
Style blueItalic = Style.EMPTY.withColor(0x0000FF).withItalic(true);
// Besides italic, we can also make styles bold, underlined, strikethrough, or obfuscated.
Style bold = Style.EMPTY.withBold(true);
Style underlined = Style.EMPTY.withUnderlined(true);
Style strikethrough = Style.EMPTY.withStrikethrough(true);
Style obfuscated = Style.EMPTY.withObfuscated(true);
// Let's merge some styles together!
Style merged = blueItalic.applyTo(bold).applyTo(strikethrough);
// Set a style on a component.
text.setStyle(merged);
// Merge a new style into it.
text.withStyle(Style.EMPTY.withColor(0xFF0000));
Another, more elaborate option of formatting is to use click and hover events:
// We have a total of 6 options for a click event, and a total of 3 options for a hover event.
ClickEvent clickEvent;
HoverEvent hoverEvent;
// Opens the given URL in your default browser when clicked.
clickEvent = new ClickEvent(ClickEvent.Action.OPEN_URL, "http://example.com/");
// Opens the given file when clicked. For security reasons, this cannot be sent from a server.
clickEvent = new ClickEvent(ClickEvent.Action.OPEN_FILE, "C:/example.txt");
// Runs the given command when clicked.
clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/gamemode creative");
// Suggests the given command in the chat when clicked.
clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/gamemode creative");
// Changes a book page when clicked. Irrelevant outside of a book screen context.
clickEvent = new ClickEvent(ClickEvent.Action.CHANGE_PAGE, "1");
// Copies the given text to the clipboard.
clickEvent = new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "Hello World!");
// Shows the given component when hovered. May be formatted as well.
// Keep in mind that click or hover events won't work in a hover tooltip for obvious reasons.
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Hello World!"));
// Shows a complete tooltip of the given item stack when hovered.
// See the possible constructors of ItemStackInfo.
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(...));
// Shows a complete tooltip of the given entity when hovered.
// See the possible constructors of EntityTooltipInfo.
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ENTITY, new HoverEvent.EntityTooltipInfo(...));
// Apply the events to a style.
Style clickable = Style.EMPTY.withClickEvent(clickEvent);
Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent);
Language Files
Language files are JSON files that contain mappings from translation keys (see below) to actual names. They are located at assets/<modid>/lang/language_name.json
. For example, US English translations for a mod with id examplemod
would be located at assets/examplemod/lang/en_us.json
. A full list of languages supported by Minecraft can be found here.
A language file generally looks like this:
{
"translation.key.1": "Translation 1",
"translation.key.2": "Translation 2"
}
Translation Keys
Translation keys are the keys used in translations. In many cases, they follow the format registry.modid.name
. For example, a mod with the id examplemod
that provides a block named example_block
will probably want to provide translations for the key block.examplemod.example_block
. However, you can use basically any string as a translation key.
If a translation key does not have an associated translation in the selected language, the game will fall back to US English (en_us
), if that is not already the selected language. If US English does not have a translation either, the translation will fail silently, and the raw translation key will be displayed instead.
Some places in Minecraft offer you helper methods to get a translation keys. For example, both blocks and items provide #getDescriptionId
methods. These can not only be queried, but also overridden if needed. A common use case are items that have different names depending on their NBT value. These will usually override the variant of #getDescriptionId
that has an ItemStack
parameter, and return different values based on the stack's NBT. Another common use case are BlockItem
s, which override the method to use the associated block's translation key instead.
The only purpose of translation keys is for localization. Do not use them for game logic, that's what registry names are for.
Translating Mod Metadata
Starting with NeoForge 20.4.179, translation files can override certain parts of mod info using the following keys (where modid
is to be replaced with the actual mod id):
Translation Key | Overriding | |
---|---|---|
Description | fml.menu.mods.info.description.modid | A field named description may be placed in the [[mods]] section instead. |
Datagen
Language files can be datagenned. To do so, extend the LanguageProvider
class and add your translations in the addTranslations()
method:
public class MyLanguageProvider extends LanguageProvider {
public MyLanguageProvider(PackOutput output) {
super(
// Provided by the GatherDataEvent.
output,
// Your mod id.
"examplemod",
// The locale to use. You may use multiple language providers for different locales.
"en_us"
);
}
@Override
protected void addTranslations() {
// Adds a translation with the given key and the given value.
add("translation.key.1", "Translation 1");
// Helpers are available for various common object types. Every helper has two variants: an add() variant
// for the object itself, and an addTypeHere() variant that accepts a supplier for the object.
// The different names for the supplier variants are required due to generic type erasure.
// All following examples assume the existence of the values as suppliers of the needed type.
// Adds a block translation.
add(MyBlocks.EXAMPLE_BLOCK.get(), "Example Block");
addBlock(MyBlocks.EXAMPLE_BLOCK, "Example Block");
// Adds an item translation.
add(MyItems.EXAMPLE_ITEM.get(), "Example Item");
addItem(MyItems.EXAMPLE_ITEM, "Example Item");
// Adds an item stack translation. This is mainly for items that have NBT-specific names.
add(MyItems.EXAMPLE_ITEM_STACK.get(), "Example Item");
addItemStack(MyItems.EXAMPLE_ITEM_STACK, "Example Item");
// Adds an entity type translation.
add(MyEntityTypes.EXAMPLE_ENTITY_TYPE.get(), "Example Entity");
addEntityType(MyEntityTypes.EXAMPLE_ENTITY_TYPE, "Example Entity");
// Adds an enchantment translation.
add(MyEnchantments.EXAMPLE_ENCHANTMENT.get(), "Example Enchantment");
addEnchantment(MyEnchantments.EXAMPLE_ENCHANTMENT, "Example Enchantment");
// Adds a mob effect translation.
add(MyMobEffects.EXAMPLE_MOB_EFFECT.get(), "Example Effect");
addEffect(MyMobEffects.EXAMPLE_MOB_EFFECT, "Example Effect");
}
}
Then, register the provider like any other provider in the GatherDataEvent
.