跳到主要内容
版本: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 通常都是 MutableComponentMutableComponent 提供了一些方法,用于添加会被追加到该 component 末尾的新兄弟 component,以及设置文本的样式。构造或修改 component 时,都应当以 MutableComponent 为准。

文本格式

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

备注

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

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 中注册该提供器。