Skip to main content
Version: 1.20.x

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 Components.

Components

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):

MethodDescription
emptyCreates an empty component.
literalCreates a component with the given text and directly displays that text without translating.
nullToEmptyCreates an empty component when given null, and a literal component otherwise.
translatableCreates a translatable component. The given string is then resolved as a translation key (see below).
keybindCreates a component containing the (translated) display name of the given keybind.
nbtCreates a component representing the NBT at the given path.
scoreCreates a component containing a scoreboard objective value.
selectorCreates 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 Components on their own (where different languages may result in different text). Many places in Minecraft will also directly accept Components and take care of resolving for you.

caution

Never let a server translate a Component. Always send Components to the client and resolve them there.

Text Formatting

Components can be formatted using Styles. Styles are immutable, creating a new Style object when modified, and thus allowing them to be created once and then be reused as needed.

Style.EMPTY can generally be used as a base to work off. Multiple Styles 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. Styles can then be applied to components like so:

Component 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 BlockItems, which override the method to use the associated block's translation key instead.

tip

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 KeyOverriding
Descriptionfml.menu.mods.info.description.modidA 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.