国际化与本地化
I18n 是 internationalization(国际化)的缩写,指让程序支持多种语言的设计方式。L10n 是 localization(本地化)的缩写,指将文字翻译为用户语言的过程。Minecraft 通过 Component 实现它们。
Component
Component 是包含格式等元数据的一段文本,可以通过 Component 接口的以下静态方法创建:
| 方法 | 说明 |
|---|---|
empty |
创建空组件。 |
literal |
直接显示给定文字,不进行翻译。 |
nullToEmpty |
参数为 null 时创建空组件,否则创建字面文本组件。 |
translatable |
创建可翻译组件,将给定字符串作为翻译键解析,见下文。 |
keybind |
显示指定按键绑定的本地化名称。 |
nbt |
显示给定路径的 NBT。 |
score |
显示记分板目标值。 |
selector |
显示实体选择器选中的实体名称列表。 |
object |
显示图集精灵等任意字形内容。 |
Component.translatable() 还接受可变参数,用于字符串插值。它与 Java 的 String#format 类似,但无论是整数、小数还是其他格式,一律使用 %s,需要时调用 #toString()。
每个组件都可以通过 #getString() 解析。解析通常是惰性的:服务端指定组件后发给客户端,各客户端按自己的语言解析,因此可能显示不同文字。Minecraft 的许多位置也直接接受组件并负责解析。
注意 不要让服务端翻译组件。始终将组件发送到客户端,再在客户端解析。
MutableComponent
创建出的组件通常都是 MutableComponent。它提供追加同级组件(放在当前组件末尾)及设置文本样式的方法。构建或修改组件时应使用 MutableComponent。
文本格式
组件通过 Style 设置格式。样式不可变,修改会创建新对象,因此可以创建一次并按需复用。
提示 只有
MutableComponent的方法可以设置样式,所以应先确认组件的类型。
通常以 Style.EMPTY 为基础。Style#applyTo(Style other) 可以合并多个样式并返回新样式:优先采用调用方中已设置的值,仅在某项缺失时使用参数样式的设置。应用方式如下:
MutableComponent text = Component.literal("Hello World!");
// Create a new style.
Style blue = Style.EMPTY.withColor(0x0000FF);
Style greenShadow = Style.EMPTY.withShadowColor(0xFF00FF00);
// 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 8 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.OpenUrl(URI.create("http://example.com/"));
// Opens the given file when clicked. For security reasons, this cannot be sent from a server.
clickEvent = new ClickEvent.OpenFile("C:/example.txt");
// Runs the given command when clicked.
clickEvent = new ClickEvent.RunCommand("/gamemode creative");
// Suggests the given command in the chat when clicked.
clickEvent = new ClickEvent.SuggestCommand("/gamemode creative");
// Changes a book page when clicked. Irrelevant outside of a book screen context.
clickEvent = new ClickEvent.ChangePage("1");
// Copies the given text to the clipboard.
clickEvent = new ClickEvent.CopyToClipboard("Hello World!");
// Opens a dialog screen (assume we have some Holder<Dialog> dialog)
clickEvent = new ClickEvent.ShowDialog(dialog);
// Sends an identifier and payload to the server (by default, logs the action and payload sent)
clickEvent = new ClickEvent.Custom(Identifier.fromNamespaceAndPath("examplemod", "custom"), Optional.empty());
// 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.
hoverEvent = new HoverEvent.ShowText(Component.literal("Hello World!"));
// Shows a complete tooltip of the given stack template when hovered.
hoverEvent = new HoverEvent.ShowItem(new ItemStackTemplate(...));
// Shows a complete tooltip of the given entity when hovered.
// See the possible constructors of EntityTooltipInfo.
hoverEvent = new HoverEvent.ShowEntity(new HoverEvent.EntityTooltipInfo(...));
// Apply the events to a style.
Style clickable = Style.EMPTY.withClickEvent(clickEvent);
Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent);
也可以指定组件所用的字体:
// Set the font to be used by a style.
// Must match an available FontDescription.Resource (found in `assets/<namespace>/font/<path>.json`)
// Points to `assets/minecraft/font/illageralt.json`
Identifier fontLocation = Identifier.withDefaultNamespace("illageralt");
// Apply the font to a style
Style customFont = Style.EMPTY.withFont(new FontDescription.Resource(fontLocation));
警告 在样式中使用
FontDescription.Resource以外的字体描述类型会抛出异常。若需要其他或自定义FontDescription,请创建ObjectInfo,提供给带ObjectContents的组件。
语言文件
语言文件是将翻译键映射到实际文本的 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 格式。例如 examplemod 添加名为 example_block 的方块时,通常提供 block.examplemod.example_block 的翻译。不过,基本上任意字符串都可作为翻译键。
如果当前语言没有对应翻译,游戏会回退到美式英语 en_us,除非当前已选用该语言。若美式英语也没有翻译,游戏会静默失败,直接显示原始翻译键。
Minecraft 在一些位置提供获取翻译键的便捷方法。例如方块和物品都有 #getDescriptionId。物品的键还可以通过 Item.Properties#overrideDescription 修改。若名称依赖底层数据组件,可在 ItemStack 的 CUSTOM_NAME 组件中设置所需的可翻译组件。Item#getName 还有接受 ItemStack 参数的变体,用于设置物品的默认组件。BlockItem 则通过 Item.Properties#useBlockDescriptionPrefix 设置描述 ID。Identifier 也提供 toLanguageKey,接受分类或注册表名以及可选后缀,构造翻译键。
建议 翻译键仅用于本地化,不应参与游戏逻辑。游戏逻辑应使用注册名。
翻译 Mod 元数据
语言文件可以通过以下键覆盖部分 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.Client`.
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.
this.add("translation.key.1", "Translation 1");
// Helpers are available for various common object types. Most helpers have 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.
this.add(MyBlocks.EXAMPLE_BLOCK.get(), "Example Block");
this.addBlock(MyBlocks.EXAMPLE_BLOCK, "Example Block");
// Adds an item translation.
this.add(MyItems.EXAMPLE_ITEM.get(), "Example Item");
this.addItem(MyItems.EXAMPLE_ITEM, "Example Item");
// Adds an item stack translation. This is mainly for items that have NBT-specific names.
this.add(MyItems.EXAMPLE_ITEM_STACK.get(), "Example Item");
this.addItemStack(MyItems.EXAMPLE_ITEM_STACK, "Example Item");
// Adds an entity type translation.
this.add(MyEntityTypes.EXAMPLE_ENTITY_TYPE.get(), "Example Entity");
this.addEntityType(MyEntityTypes.EXAMPLE_ENTITY_TYPE, "Example Entity");
// Adds a mob effect translation.
this.add(MyMobEffects.EXAMPLE_MOB_EFFECT.get(), "Example Effect");
this.addEffect(MyMobEffects.EXAMPLE_MOB_EFFECT, "Example Effect");
// Adds a tag key translation.
this.add(MyTags.EXAMPLE_TAG, "Example Tag");
// Adds a dimension translation.
this.addDimension(MyDimensions.EXAMPLE_DIMENSION_KEY, "Example Dimension");
// Adds a biome translation.
this.addBiome(MyBiomes.EXAMPLE_BIOME_KEY, "Example Biome");
}
}
然后像其他提供器一样,在 GatherDataEvent.Client 中注册:
@SubscribeEvent // on the mod event bus
public static void onGatherData(GatherDataEvent.Client event) {
// Call event.createDatapackRegistryObjects(...) first if adding datapack objects
event.createProvider(MyLanguageProvider::new);
}
来源: NeoForged/Documentation(26.1)。中文翻译依据提交
4a680cdd4d15。