Skip to main content
Version: 1.21.4

国际化(I18n)与本地化(L10n)

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

Component(组件)​

Component(组件)是带有元数据的一段文本,元数据可以包含文本格式等信息。你可以通过以下方式之一来创建 Component(所有方法均为 Component 接口的静态方法):

方法描述
empty创建一个空组件。
literal创建一个包含指定文本的组件,直接显示该文本,不进行翻译。
nullToEmpty如果参数为 null,则创建一个空组件,否则创建一个文本组件。
translatable创建一个可翻译组件。传入的字符串会被当作翻译键(见下文)进行解析。
keybind创建一个包含指定按键绑定(keybind)显示名称(已翻译)的组件。
nbt创建一个表示指定路径下 NBT 的组件。
score创建一个包含记分板目标值的组件。
selector创建一个包含给定 实体 选择器(entity selector)所选实体名称列表的组件。

Component.translatable() 方法还带有一个可变参数,用于插入字符串。这类似于 Java 的 String#format,但始终使用 %s 而不是 %i、%d、%f 以及其他格式化符号,并在需要时调用 #toString()。

每个 Component 都可以通过 #getString() 进行解析。解析通常是惰性的,这意味着服务器可以指定一个 Component,将其发送给客户端,然后客户端各自解析该 Component(不同语言的客户端解析结果可能不同)。Minecraft 的许多地方也会直接接受 Component 并自动为你处理解析。

caution

切勿让服务器端翻译 Component。应始终将 Component 发送到客户端,并在客户端进行解析。

MutableComponent(可变组件)​

所有构造出的组件通常都是 MutableComponent(可变组件)。MutableComponent 提供了添加新的组件子节点(这些子节点会被追加到当前组件末尾)以及设置文本 样式 的方法。构建或修改组件时应始终使用 MutableComponent。

文本格式化​

Component(组件)可以通过 Style(样式)进行格式化。Style 是不可变对象,每次修改都会创建一个新的 Style 实例,因此可以只创建一次并在需要时复用。

note

只能通过 MutableComponent(可变组件)的方法来设置样式,因此请确保你的 Component 是一个 MutableComponent。

通常可以使用 Style.EMPTY 作为基础来构建样式。多个 Style 可以通过 Style#applyTo(Style other) 方法合并,该方法会返回一个新的 Style。新样式会优先采用调用 applyTo() 方法的 Style 的设置,如果该设置不存在,则采用作为参数传入的 Style 的对应设置。你可以像下面这样将 Style 应用于组件:

MutableComponent text = Component.literal("Hello World!");

// 创建一个新的样式,设置为蓝色。
Style blue = Style.EMPTY.withColor(0x0000FF);
// 样式采用类似构建器的模式。
Style blueItalic = Style.EMPTY.withColor(0x0000FF).withItalic(true);
// 除了斜体(italic),我们还可以让样式变为粗体(bold)、带下划线(underlined)、删除线(strikethrough)或乱码(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);
// 让我们合并几个样式试试!
Style merged = blueItalic.applyTo(bold).applyTo(strikethrough);

// 为组件设置样式。
text.setStyle(merged);
// 合并一个新的样式。
text.withStyle(Style.EMPTY.withColor(0xFF0000));

另一种更丰富的格式化方式是使用点击和悬停事件:

// 我们一共有 6 种点击事件(click event)选项,以及 3 种悬停事件(hover event)选项。
ClickEvent clickEvent;
HoverEvent hoverEvent;

// 点击时在默认浏览器中打开指定 URL。
clickEvent = new ClickEvent(ClickEvent.Action.OPEN_URL, "http://example.com/");
// 点击时打开指定文件。出于安全原因,该操作无法由服务器端发送。
clickEvent = new ClickEvent(ClickEvent.Action.OPEN_FILE, "C:/example.txt");
// 点击时执行指定命令。
clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/gamemode creative");
// 点击时在聊天栏中建议(填充)指定命令。
clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/gamemode creative");
// 点击时切换书页。仅在书本界面中有意义。
clickEvent = new ClickEvent(ClickEvent.Action.CHANGE_PAGE, "1");
// 点击时将指定文本复制到剪贴板。
clickEvent = new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "Hello World!");

// 悬停时显示指定组件。也可以进行格式化。
// 注意:在悬浮提示框(hover tooltip)中,点击或悬停事件不会生效,这是显而易见的。
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Hello World!"));
// 悬停时显示指定物品堆(item stack)的完整提示信息。
// 参见 ItemStackInfo 的可能构造方法。
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(...));
// 悬停时显示指定实体(entity)的完整提示信息。
// 参见 EntityTooltipInfo 的可能构造方法。
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ENTITY, new HoverEvent.EntityTooltipInfo(...));

// 将事件应用到样式(style)上。
Style clickable = Style.EMPTY.withClickEvent(clickEvent);
Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent);

语言文件(Language Files)​

语言文件是 JSON 文件,用于将翻译键(translation key,见下文)映射到实际名称。它们位于 assets/<modid>/lang/language_name.json 路径下。例如,mod id 为 examplemod 的模组的美式英语(US English)翻译文件路径为 assets/examplemod/lang/en_us.json。Minecraft 支持的所有语言列表可在 这里 查询。

一个典型的语言文件格式如下:

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

翻译键(Translation Keys)​

翻译键(translation key)是用于翻译的键值。在许多情况下,它们遵循 registry.modid.name 的格式。例如,id 为 examplemod 的模组提供了一个名为 example_block 的方块(block),那么通常需要为 block.examplemod.example_block 这个键提供翻译。当然,你也可以使用几乎任何字符串作为翻译键。 如果某个翻译键(translation key)在当前选择的语言中没有对应的翻译,游戏会回退到美式英语(en_us),前提是当前语言不是美式英语。如果美式英语中也没有该翻译,则会静默失败,界面上将直接显示原始的翻译键。

Minecraft 的某些部分为你提供了获取翻译键的辅助方法。例如,方块(block)和物品(item)都提供了 #getDescriptionId 方法。对于物品,这些方法不仅可以用于查询,还可以在需要时通过 Item$Properties#overrideDescription 进行修改。如果物品的名称会根据其底层 数据组件 不同而变化,可以通过在 ItemStack 上设置带有期望可翻译组件的 CUSTOM_NAME 数据组件来覆盖名称。此外,Item#getName 还有一个变体方法,接受一个 ItemStack 参数,用于设置物品的默认组件。另一方面,BlockItem 会通过调用 Item$Properties#useBlockDescriptionPrefix 来设置描述 id。

tip

翻译键(translation key)唯一的用途就是用于本地化(localization)。不要将它们用于游戏逻辑,这类用途应该使用 注册表名称(registry name)。

翻译 Mod 元数据(Translating Mod Metadata)​

从 NeoForge 20.4.179 开始,翻译文件可以使用以下键覆盖部分 mod 信息(其中 modid 需要替换为实际的 mod id):

翻译键(Translation Key)覆盖方式(Overriding)
描述(Description)fml.menu.mods.info.description.modid也可以在 [[mods]] 区块中直接添加一个名为 description 的字段。

数据生成(Datagen)​

语言文件可以通过 数据生成(datagenned) 的方式创建。为此,你需要继承 LanguageProvider 类,并在 addTranslations() 方法中添加你的翻译内容:

public class MyLanguageProvider extends LanguageProvider {
public MyLanguageProvider(PackOutput output) {
super(
// 由 `GatherDataEvent.Client` 提供。
output,
// 你的模组 id。
"examplemod",
// 要使用的语言区域。你可以为不同的区域使用多个语言提供器。
"en_us"
);
}

@Override
protected void addTranslations() {
// 使用给定的键和值添加一条翻译。
this.add("translation.key.1", "Translation 1");

// 对于各种常见的对象类型,提供了辅助方法。每个辅助方法都有两种变体:add() 变体
// 用于对象本身,addTypeHere() 变体则接受一个对象的供应器(supplier)。
// 由于泛型类型擦除,不同的供应器变体需要不同的名称。
// 下列所有示例都假设已经存在所需类型的供应器变量。

// 添加一个方块(block)的翻译。
this.add(MyBlocks.EXAMPLE_BLOCK.get(), "Example Block");
this.addBlock(MyBlocks.EXAMPLE_BLOCK, "Example Block");
// 添加一个物品(item)的翻译。
this.add(MyItems.EXAMPLE_ITEM.get(), "Example Item");
this.addItem(MyItems.EXAMPLE_ITEM, "Example Item");
// 添加一个物品堆(item stack)的翻译。主要用于具有特定 NBT 名称的物品。
this.add(MyItems.EXAMPLE_ITEM_STACK.get(), "Example Item");
this.addItemStack(MyItems.EXAMPLE_ITEM_STACK, "Example Item");
// 添加一个实体类型(entity type)的翻译。
this.add(MyEntityTypes.EXAMPLE_ENTITY_TYPE.get(), "Example Entity");
this.addEntityType(MyEntityTypes.EXAMPLE_ENTITY_TYPE, "Example Entity");
// 添加一个附魔(enchantment)的翻译。
this.add(MyEnchantments.EXAMPLE_ENCHANTMENT.get(), "Example Enchantment");
this.addEnchantment(MyEnchantments.EXAMPLE_ENCHANTMENT, "Example Enchantment");
// 添加一个生物效果(mob effect)的翻译。
this.add(MyMobEffects.EXAMPLE_MOB_EFFECT.get(), "Example Effect");
this.addEffect(MyMobEffects.EXAMPLE_MOB_EFFECT, "Example Effect");
}
}

然后,像注册其他数据提供器一样,在 GatherDataEvent.Client 中注册该语言提供器。