Skip to content

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

字数
2116 字
阅读时间
9 分钟

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

国际化(I18n,Internationalization 的缩写)是指设计程序以支持多种语言的方式。本地化(L10n,Localization 的缩写)是将文本翻译成用户语言的过程。Minecraft 使用 组件(Components) 来实现这些功能。


组件(Components)

组件 是一段带有元数据的文本,元数据包括文本格式等内容。可以通过以下方式创建组件(以下所有方法都是 Component​ 接口中的静态方法):

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

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

每个组件都可以通过 #getString()​ 解析。解析通常是惰性的,这意味着服务器可以指定一个组件,将其发送给客户端,客户端会自行解析组件(不同的语言可能导致不同的文本)。Minecraft 中的许多地方也会直接接受组件并为你处理解析。

注意 永远不要让服务器翻译组件。始终将组件发送到客户端并在客户端解析。


可变组件(MutableComponent)

所有构造的组件通常都是 MutableComponent​。MutableComponent​ 提供了添加新组件兄弟节点(附加到当前组件末尾)和设置文本样式的方法。构造或修改组件时,应始终使用 MutableComponent​。


文本格式化

组件可以使用 样式(Styles) 进行格式化。样式是不可变的,修改时会创建一个新的 Style​ 对象,因此可以创建一次并重复使用。

注意 样式只能通过 MutableComponent​ 上的方法设置,因此请确保组件是 MutableComponent​。

Style.EMPTY​ 通常可以作为基础样式使用。多个样式可以通过 Style#applyTo(Style other)​ 合并,该方法返回一个新样式,优先使用调用 applyTo()​ 的样式中的设置,如果该设置不存在,则使用参数中的样式设置。样式可以像这样应用到组件上:

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

// 创建一个新样式。
Style blue = Style.EMPTY.withColor(0x0000FF);
// 样式使用类似构建器的模式。
Style blueItalic = Style.EMPTY.withColor(0x0000FF).withItalic(true);
// 除了斜体,我们还可以使样式加粗、下划线、删除线或模糊。
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));

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

java
// 点击事件有 6 种选项,悬停事件有 3 种选项。
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!");

// 悬停时显示给定组件。可以格式化。
// 请注意,点击或悬停事件在悬停工具提示中显然不起作用。
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Hello World!"));
// 悬停时显示给定物品堆叠的完整工具提示。
// 参见 ItemStackInfo 的可能构造函数。
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(...));
// 悬停时显示给定实体的完整工具提示。
// 参见 EntityTooltipInfo 的可能构造函数。
hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ENTITY, new HoverEvent.EntityTooltipInfo(...));

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

语言文件

语言文件是包含从翻译键(见下文)到实际名称的映射的 JSON 文件。它们位于 assets/<modid>/lang/language_name.json​。例如,模组 ID 为 examplemod​ 的美国英语翻译文件位于 assets/examplemod/lang/en_us.json​。Minecraft 支持的语言的完整列表可以在这里找到。

语言文件通常如下所示:

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

翻译键(Translation Keys)

翻译键是用于翻译的键。在许多情况下,它们的格式为 registry.modid.name​。例如,ID 为 examplemod​ 的模组提供了一个名为 example_block​ 的方块,可能会希望为键 block.examplemod.example_block​ 提供翻译。然而,你基本上可以使用任何字符串作为翻译键。

如果所选语言中没有与翻译键关联的翻译,游戏将回退到美国英语(en_us​),如果所选语言不是美国英语。如果美国英语也没有翻译,则翻译将静默失败,并显示原始翻译键。

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

提示 翻译键的唯一目的是本地化。不要将它们用于游戏逻辑,那是注册名称的用途。


翻译模组元数据

从 NeoForge 20.4.179 开始,翻译文件可以使用以下键覆盖模组信息的某些部分(其中 modid​ 应替换为实际的模组 ID):

翻译键覆盖内容
fml.menu.mods.info.description.modid覆盖模组描述。可以在 [[mods]]​ 部分中放置名为 description​ 的字段来代替。

数据生成(Datagen)

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

java
public class MyLanguageProvider extends LanguageProvider {
    public MyLanguageProvider(PackOutput output) {
        super(
            // 由 GatherDataEvent 提供。
            output,
            // 你的模组 ID。
            "examplemod",
            // 使用的语言环境。你可以为不同的语言环境使用多个语言提供者。
            "en_us"
        );
    }
    
    @Override
    protected void addTranslations() {
        // 添加具有给定键和值的翻译。
        add("translation.key.1", "Translation 1");
        
        // 为各种常见对象类型提供了辅助方法。每个辅助方法有两个变体:一个用于对象本身的 `add()` 变体,
        // 以及一个接受对象供应商的 `addTypeHere()` 变体。
        // 由于泛型类型擦除,供应商变体需要不同的名称。
        // 以下所有示例假设存在所需类型的供应商。

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

然后,像在 GatherDataEvent​ 中注册其他提供者一样注册该提供者。

贡献者

The avatar of contributor named as 小飘 小飘

文件历史