跳到主要内容
版本:1.21 - 1.21.1

国际化与本地化

I18n(internationalization,国际化的缩写)是指将程序设计为能够支持多种语言的方式。 L10n(localization,本地化的缩写)是指将文本翻译成用户所用语言的过程。 Minecraft 通过 Component 来实现这两者。

Component​

Component 是一段带有元数据的文本,其中的元数据包含诸如文本格式之类的信息。它可以通过以下方式之一创建(以下都是 Component 接口中的静态方法):

方法说明
empty创建一个空的 component。
literal创建一个带有给定文本的 component,并直接显示该文本而不进行翻译。
nullToEmpty当传入 null 时创建一个空的 component,否则创建一个字面量 component。
translatable创建一个可翻译的 component。给定的字符串随后会被解析为翻译键(见下文)。
keybind创建一个 component,其中包含给定按键绑定的(已翻译的)显示名称。
nbt创建一个表示给定路径处 NBT 的 component。
score创建一个包含计分板目标值的 component。
selector创建一个 component,其中包含给定实体选择器所匹配的实体名称列表。

Component.translatable() 还额外带有一个可变参数(vararg),用于接受字符串插值元素。它的工作方式类似于 Java 的 String#format,但始终使用 %s,而不使用 %i、 %d、 %f 或任何其他格式说明符,并在需要时调用 #toString()。

每个 Component 都可以通过 #getString() 解析。解析通常是惰性的,也就是说服务端可以指定一个 Component,把它发送给各个客户端,各个客户端再各自解析这些 Component(不同的语言可能会得到不同的文本)。 Minecraft 中的许多地方也会直接接受 Component 并为你负责解析。

警告

切勿让服务端翻译 Component。始终把 Component 发送给客户端,并在客户端解析。

MutableComponent​

所有构造出来的 component 通常都是 MutableComponent。 MutableComponent 提供了一些方法,用于添加会被追加到该 component 末尾的新兄弟 component,以及设置文本的样式。构造或修改 component 时,都应当以 MutableComponent 为准。

文本格式​

Component 可以使用 Style 进行格式化。 Style 是不可变的,修改时会创建一个新的 Style 对象,因此可以创建一次然后按需重复使用。

备注

样式只能通过 MutableComponent 上的方法来设置,所以务必确认该 Component 是 MutableComponent。

Style.EMPTY 通常可以作为一个基础来使用。多个 Style 可以通过 Style#applyTo(Style other) 合并,该方法返回一个新的 Style:它从调用 applyTo() 方法所在的 Style 中取设置,除非相应设置缺失,此时则使用作为参数传入的 Style 中的设置。 Style 随后可以像这样应用到 component 上:

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

另一种更复杂的格式化选项是使用点击事件和悬停事件:

// 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);

语言文件​

语言文件是 JSON 文件,其中包含从翻译键(见下文)到实际名称的映射。它们位于 assets/<modid>/lang/language_name.json。例如,id 为 examplemod 的 Mod 的美式英语翻译位于 assets/examplemod/lang/en_us.json。 Minecraft 支持的完整语言列表可在此处找到。

语言文件通常长这样:

{
"translation.key.1": "Translation 1",
"translation.key.2": "Translation 2"
}

翻译键​

翻译键是翻译中使用的键。在许多情况下,它们遵循 registry.modid.name 的格式。例如,id 为 examplemod 的 Mod 提供了一个名为 example_block 的方块,那么它多半会想为键 block.examplemod.example_block 提供翻译。不过,你基本上可以用任意字符串作为翻译键。

如果某个翻译键在所选语言中没有对应的翻译,游戏会回退到美式英语(en_us),前提是它还不是当前所选语言。如果美式英语也没有翻译,翻译将静默失败,转而显示原始的翻译键。

Minecraft 中的一些地方为你提供了获取翻译键的辅助方法。例如,方块和物品都提供了 #getDescriptionId 方法。这些方法不仅可以查询,必要时还可以覆盖。一个常见用例是那些名称随其 NBT 值而变化的物品。这类物品通常会覆盖带有 ItemStack 参数的那个 #getDescriptionId 变体,并根据堆叠的 NBT 返回不同的值。另一个常见用例是 BlockItem,它覆盖该方法以改用其关联方块的翻译键。

提示

翻译键的唯一用途是本地化。不要把它们用于游戏逻辑,那是注册名的职责。

翻译 Mod 元数据​

自 NeoForge 20.4.179 起,翻译文件可以使用以下键来覆盖 mod 信息的某些部分(其中 modid 需替换为实际的 mod id):

翻译键覆盖对象
描述fml.menu.mods.info.description.modid也可以在 [[mods]] 部分放置一个名为 description 的字段来替代。

数据生成​

语言文件可以数据生成。为此,扩展 LanguageProvider 类,并在 addTranslations() 方法中添加你的翻译:

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");
}
}

然后,像注册其他任何提供器一样,在 GatherDataEvent 中注册该提供器。