国际化与本地化

国际化与本地化

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。