本文介绍语言对象中的 LangText、LangList 和 LangMap。配置中的引用写法见在文件中引用 ⇱,配置对象中的文本类型见配置文本 ⇱。
LangText 引用语言文件中的一个文本字段,保存字段路径,并在使用时向语言服务查询当前值。与直接保存 String 不同,保存 LangText 后仍能读取重载后的文本。列表和映射也有对应的引用类型:LangList 和 LangMap。
如果命令、GUI 等组件需要长期保存语言字段,并随语言重载更新文本,建议使用语言引用。
将字段类型改为 LangText,并通过 LangText.of(...) 提供默认值:
import api.linlang.file.file.LangText;
import api.linlang.file.file.annotations.LangPack;
@LangPack(filePath = "main")
public class MainLanguage {
public Command command = new Command();
public static class Command {
public AddLore addLore = new AddLore();
}
public static class AddLore {
// 引用字段需要通过 of 声明默认值。
public LangText description = LangText.of("添加新的行");
public LangText line = LangText.of("添加的描述");
}
}
语言资源和生成文件的格式不变:
command:
add-lore:
description: "添加新的行"
line: "添加的描述"
绑定完成后,这两个字段的路径分别为 command.add-lore.description 和 command.add-lore.line。路径遵循语言对象已有的 @Key 与 @NamingStyle 规则,不需要在 LangText.of(...) 中重复填写。
注意,LangText.of(...) 返回的是尚未绑定的声明对象。语言服务绑定语言类时,会把字段替换为受管理引用。因此,应先通过 LangService.bind(...) 取得语言对象,不要直接构造语言类并把其中的引用交给其他模块。
读取当前全局语言:
String description = lang.command.addLore.description.resolve();
显式读取指定 locale:
String description = lang.command.addLore.description.resolve("en_GB");
查找指定 locale 时,语言服务仍然应用语言包的归一化、默认语言和字段默认值规则。没有可用文件值时,返回 LangText.of(...) 声明的文本。
不要依赖字符串拼接隐式调用 toString()。需要进行替换、格式化或交给只接受 String 的 API 时,应先显式调用 resolve(),使取值时机在代码中保持清楚。
普通字段在绑定和重载时由语言服务直接写入:
public String reloaded = "重载完成";
读取后得到的是当时的字符串快照:
String message = lang.reloaded;
即使语言对象中的 reloaded 字段后来被刷新,局部变量 message 也不会变化。
引用字段则声明为:
public LangText reloaded = LangText.of("重载完成");
绑定后取得的引用可以被长期保存:
LangText message = lang.reloaded;
同一个语言服务执行 reload() 或切换活动 locale 后,message.resolve() 会从新的语言快照中读取文本。语言服务不会在普通重载时替换已经安装的 LangText 对象。
LinFile 还提供了 List 和 Map 的引用版本,您可以使用 LangList 与 LangMap:
import api.linlang.file.file.LangList;
import api.linlang.file.file.LangMap;
@LangPack(filePath = "main")
public class MainLanguage {
public LangList tips = LangList.of("默认提示");
public LangMap buttons = LangMap.of();
}
对应语言文件仍然使用原生列表和映射结构:
tips:
- "第一条提示"
- "第二条提示"
buttons:
confirm: "确认"
cancel: "取消"
解析当前语言下的不可修改集合快照:
List<String> tips = lang.tips.resolve();
Map<String, String> buttons = lang.buttons.resolve();
也可以显式指定 locale:
List<String> englishTips = lang.tips.resolve("en_GB");
String confirm = lang.buttons.resolve("en_GB").get("confirm");
应长期保存 LangList 或 LangMap 引用,而不是保存某次 resolve() 返回的集合。每次解析得到的列表和映射都是不可修改的快照,调用者不能通过 add、put 等方法修改语言服务中的数据。
LangList tipsRef = lang.tips;
langService.reload();
List<String> latestTips = tipsRef.resolve();
LangMap 的值必须是字符串、数字、布尔值等可以转换为字符串的标量。嵌套 Map 或嵌套 List 不属于 LangMap 的数据模型;字段结构不符合要求时,解析结果回退到代码声明的默认值。
非空的 LangMap.of(...) 默认映射会参与缺失键补齐,适合键集合固定的场景:
public LangMap buttons = LangMap.of(
"confirm", "确认",
"cancel", "取消"
);
如果 Map 的键由语言文件或用户自由定义,应使用空默认映射,避免默认键被自动补入:
public LangMap customMessages = LangMap.of();
LangList 和 LangMap 表示集合整体的引用。请不要声明 List<LangText>、Map<String, LangText> 或包含引用对象的嵌套集合;这些结构不支持上述集合引用的重载行为。
三种引用字段都可以放在带有 @LangPack 的语言对象及其嵌套对象中。资源目录、文件格式、默认语言、归一化和写回规则仍由语言对象类上的 @LangPack 统一决定。
命令服务会长期保存命令描述和参数标签,适合直接传入语言引用:
registry.register(
"re + <line:string{.+}>",
ctx -> {
Player player = (Player) ctx.sender();
String lore = normalize(ctx.get("line"));
itemEditor.addLore(player, lore);
},
CommandOptions.options()
.permission("rainbowedit.lore")
.player()
.i18n(
lang.command.addLore.description,
"line", lang.command.addLore.line
)
);
命令规范中的 line 仍然是参数解析和 ctx.get("line") 使用的稳定名称。语言引用只改变帮助与用法信息中的显示文本,不会令不同语言使用不同的命令参数名。
使用 CommandOptions 后不需要在参数后添加 @i18n。命令服务发现与参数名匹配的标签引用时会优先使用引用;引用不存在或解析为空时,再使用内联说明,最后回退到参数名。
原有的 Desc、Labels、registerLazy 和 @i18n 写法仍然可用。
LangText 同时实现了高级字符串使用的 TextSource 接口,可以直接交给 Messenger,不需要先调用 resolve():
messenger.send(
player,
lang.command.success,
"item", itemName
);
Messenger 会在每次发送时解析语言引用,并把语言文件中的内容作为高级字符串处理。普通语言重载后,使用同一个引用即可发送新的文本。
作为变量传入时,LangText 和 LinText 会被视为可渲染的高级文本;String 与其他普通对象会被转义后作为普通文本插入。这可以避免玩家名称、命令参数等外部输入意外生成点击行为。
旧版 sendKey() 通过路径字符串查找语言的方式已经弃用。新代码应保存并传递语言对象中的字段引用,使语言结构重命名能够在 Java 编译阶段被发现。
消息发送、动作栏、标题和完整投递策略请见:消息服务 ⇱。描述符格式请见:高级字符串 ⇱。
flowchart TD
Define["LangText、LangList 或 LangMap"] --> Bind["bind(LanguageClass)"]
Bind --> Path["按字段结构确定路径"]
Path --> Reference["安装受管理引用"]
Bind --> Snapshot["读取并校验语言文件"]
Reload["reload 或 locale 变化"] --> Snapshot
Snapshot --> Swap["替换语言包文本快照"]
Reference --> Resolve["resolve"]
Swap --> Resolve
语言文件会先被完整读取为新的文本快照,再交给引用解析。已经保存的引用不需要逐个接收更新,也不会持有注册命令时的旧字符串。
LangService.reload() 属于同一语言服务内的普通重载。此时语言对象和已经取得的语言引用都可以继续使用。
lin.settings().apply() 原地切换语言,不替换服务,已有语言引用和命令注册仍然有效。关闭 Facade 后,应停止使用它的服务及引用;重新初始化插件时再绑定新对象。
语言引用字段也不应被业务代码重新赋值。重新赋值只会改变语言对象中的字段,不会改变已经交给命令或 GUI 的旧引用。需要修改翻译时,应修改语言文件后调用 reload()。
讨论