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 小飘 小飘

文件历史

布局切换

调整 VitePress 的布局样式,以适配不同的阅读习惯和屏幕环境。

全部展开
使侧边栏和内容区域占据整个屏幕的全部宽度。
全部展开,但侧边栏宽度可调
侧边栏宽度可调,但内容区域宽度不变,调整后的侧边栏将可以占据整个屏幕的最大宽度。
全部展开,且侧边栏和内容区域宽度均可调
侧边栏宽度可调,但内容区域宽度不变,调整后的侧边栏将可以占据整个屏幕的最大宽度。
原始宽度
原始的 VitePress 默认布局宽度

页面最大宽度

调整 VitePress 布局中页面的宽度,以适配不同的阅读习惯和屏幕环境。

调整页面最大宽度
一个可调整的滑块,用于选择和自定义页面最大宽度。

内容最大宽度

调整 VitePress 布局中内容区域的宽度,以适配不同的阅读习惯和屏幕环境。

调整内容最大宽度
一个可调整的滑块,用于选择和自定义内容最大宽度。

聚光灯

支持在正文中高亮当前鼠标悬停的行和元素,以优化阅读和专注困难的用户的阅读体验。

ON开启
开启聚光灯。
OFF关闭
关闭聚光灯。

聚光灯样式

调整聚光灯的样式。

置于底部
在当前鼠标悬停的元素下方添加一个纯色背景以突出显示当前鼠标悬停的位置。
置于侧边
在当前鼠标悬停的元素旁边添加一条固定的纯色线以突出显示当前鼠标悬停的位置。