国际化与本地化
I18n(internationalization,国际化)是让程序能够适用于多种语言的设计方式。L10n(localization,本地化)则是将文本翻译为用户语言的过程。Minecraft 使用 Component 实现这两项功能。
Component
Component 是一段带有元数据的文本,元数据包括文本格式等信息。可以通过下列方式创建组件(以下均为 Component 接口的静态方法):
| 方法 | 说明 |
|---|---|
empty |
创建空组件。 |
literal |
使用给定文本创建组件,直接显示文本而不翻译。 |
nullToEmpty |
参数为 null 时创建空组件,否则创建字面文本组件。 |
translatable |
创建可翻译组件,将给定字符串作为翻译键解析(见下文)。 |
keybind |
创建包含给定按键绑定的本地化显示名称的组件。 |
nbt |
创建表示给定路径处 NBT 的组件。 |
score |
创建包含记分板计分项数值的组件。 |
selector |
创建包含给定实体选择器所选实体名称列表的组件。 |
Component.translatable() 还接受一个可变参数,用于提供字符串插值元素。它类似于 Java 的 String#format,但始终使用 %s,而不是 %i、%d、%f 等其他格式说明符,并在需要时调用 #toString()。
每个 Component 都可通过 #getString() 解析。解析通常是惰性的:服务端可以指定一个组件并发送到客户端,再由各客户端自行解析(不同语言可能得到不同文本)。Minecraft 中许多位置也直接接受 Component,并自动处理解析。
注意 不要在服务端翻译
Component。应始终将组件发送到客户端,并在那里解析。
MutableComponent
创建的组件通常都是 MutableComponent。它提供方法,允许在当前组件末尾追加同级组件,并设置文本的样式。构造或修改组件时应使用 MutableComponent。
文本格式
可以使用 Style 设置组件格式。Style 是不可变对象,修改时会创建新的 Style,因此可以创建一次后按需复用。
提示 只能通过
MutableComponent上的方法设置样式,因此请先确认组件是MutableComponent。
通常可以使用 Style.EMPTY 作为起点。通过 Style#applyTo(Style other) 可以合并多个样式:返回的新样式优先采用调用 applyTo() 的样式设置;如果某项设置不存在,则采用作为参数传入的样式中的对应设置。随后即可把样式应用到组件:
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 中注册该提供器。
来源: NeoForged/Documentation(1.21.1)。中文翻译依据提交
4a680cdd4d15。