语言文件服务 LangService 可以把 YAML 或 JSON 中的文本绑定为 Java 语言对象。字段结构由代码声明,具体翻译内容来自插件资源和插件数据目录中的语言文件。
语言文件服务使用当前 Linlang Facade 的全局语言。全局语言的设置与生命周期请先阅读:多语言 ⇱。
语言对象需要 @LangPack 注解声明资源目录和格式,同时满足文件服务中的对象约束 文件服务 ⇱:
import api.linlang.file.file.FileType;
import api.linlang.file.file.LangList;
import api.linlang.file.file.LangMap;
import api.linlang.file.file.LangText;
import api.linlang.file.file.annotations.LangPack;
@LangPack(filePath = "lang", format = FileType.YAML)
public class MainLanguage {
public Message message = new Message();
public static class Message {
public String prefix = "";
public String reloaded = "";
public LangText commandDescription = LangText.of("执行重载命令");
public LangList tips = LangList.of("默认提示");
public LangMap buttons = LangMap.of();
}
}
语言对象遵循与配置对象相同的文件对象约束:类和嵌套类需要可被无参构造,参与映射的字段为 public,字段初始化值提供缺失键的默认值。
构造方法和字段初始化器会在一次新绑定中执行一次,但不得包含文件访问、服务注册、线程创建或其他副作用。语言对象中的普通方法不会被 Linlang 自动调用。
@LangPack 的主要参数为:
filePath:相对于语言根目录的子目录,默认为 lang。format:FileType.YAML 或 FileType.JSON,默认为 YAML。emit:是否允许生成和补齐磁盘文件,默认为 true。defaultLocale:当前全局语言不可用时,该语言包使用的回退语言。normalizeLocale:是否把 en-GB、enGB 等名称归一化为 en_GB,默认为 true。全局语言仍决定首选 locale,但每个语言包可以声明自己的回退语言。启用 normalizeLocale 时,注解中的默认语言、全局语言、磁盘文件名和保存参数会使用同一套归一化规则;关闭后则保留开发者传入的名称,例如 fr-FR.yml。
同一个 filePath 在开发资源和服务器磁盘中分别对应:
src/main/resources/langservice/<filePath>/
plugins/<YourPlugin>/<filePath>/
上例的中文资源文件应位于:
src/main/resources/langservice/main/zh_CN.yml
运行后对应的磁盘文件为:
plugins/<YourPlugin>/main/zh_CN.yml
语言资源的结构需要与语言对象字段一致:
# 中文语言文件
message:
prefix: "§7[§dMyPlugin§7] "
reloaded: "配置文件重新加载成功"
command-description: "执行重载命令"
tips:
- "第一条提示"
- "第二条提示"
buttons:
confirm: "确认"
cancel: "取消"
英文资源可以放在同一目录:
# English language file
message:
prefix: "§7[§dMyPlugin§7] "
reloaded: "Configuration reloaded"
command-description: "Run the reload command"
tips:
- "First tip"
- "Second tip"
buttons:
confirm: "Confirm"
cancel: "Cancel"
文件名就是 locale,例如 zh_CN.yml、en_GB.yml。默认情况下,服务会把 enGB、en-GB 等可归一化名称整理为 en_GB;设置 normalizeLocale = false 后,扫描、读取和保存都会保留原始名称。要了解语言代码归一化,请见: 多语言 ⇱。
语言文件的注释应直接写在 resources 中的内建 YAML 文件里。首次复制资源时,Linlang 会保留这些注释;保存已有 YAML 时也会尽量保留原注释。不需要使用专门的多语言注释注解。
通过当前 Facade 的语言服务绑定对象:
LangService langService = lin.linFile().language();
MainLanguage lang = langService.bind(MainLanguage.class);
绑定时,服务会:
@LangPack 的目录和格式。同一个 LangService 重复绑定同一个语言类时,会返回同一个活动对象,并按当前语言重新填充它。
一般情况下,您可以使用 String 字段:
public String reloaded = "重载成功";
...
sender.sendMessage(lang.message.reloaded);
如果组件需要保存语言字段,并在语言重载后读取新文本,可以使用 LangText:
public LangText commandDescription = LangText.of("执行重载命令");
...
String description = lang.message.commandDescription.resolve();
String 与普通集合字段表示当前值快照;LangText、LangList 与 LangMap 表示稳定的语言字段引用。普通重载会更新引用背后的语言快照,因此保存过的引用仍然可以解析到最新内容。
List<String> tips = lang.message.tips.resolve();
Map<String, String> buttons = lang.message.buttons.resolve();
集合引用返回不可修改的集合快照。完整的定义方式、Map 默认键补齐、命令注册和生命周期规则请见:语言引用 ⇱。
语言对象只暴露代码中声明的结构。需要允许用户定义开放键集合时,应显式声明普通 Map<String, ?> 快照字段,或声明使用空默认值的 LangMap 引用字段,而不是依赖任意字符串路径访问。
语言服务也提供路径翻译入口:
String text = langService.tr("message.reloaded");
tr 会先查找当前 locale,再按已绑定语言包各自声明的 defaultLocale 查找,最后回显键名。它支持 {name} 形式的成对名称参数,也支持 MessageFormat 的位置参数:
String named = langService.tr("message.welcome", "player", player.getName());
String indexed = langService.tr("message.count", 3);
LangService 本身不会解析 PlaceholderAPI 占位符。需要 Bukkit 颜色、消息通道或其他占位符处理时,应把取得的文本交给对应的消息服务。
外部修改当前语言文件后,可以调用:
langService.reload();
该方法会重新扫描并读取当前 locale 的磁盘文件,原地刷新语言对象中的 String 字段,并替换 LangText 使用的语言快照。因此,普通语言文件重载后可以继续使用 lang 和已经保存的语言引用。
全局语言切换使用:
lin.settings()
.totalLocale("en_GB")
.apply();
切换语言后,原来的 langService、lang 和字段引用继续有效,不必重新绑定。
普通语言重载按语言包处理:先解析、校验,再提交字段和缓存。失败的包保留旧值,其他包继续加载,最后通过 ReloadException.failures() 汇总问题。失败包暂停保存,修正并成功加载后恢复。
切换全局语言时,则先检查全部已绑定语言包;任一包加载失败时,不切换全局语言。若数据已提交而显示监听器失败,会报告显示更新失败,不回滚已成功加载的文本。
可以修改当前活动语言对象,再保存当前全局语言:
lang.message.reloaded = "新的提示文本";
langService.saveAll();
saveAll() 会把所有已绑定语言对象保存到当前全局语言对应的文件,不会同时修改其他 locale。
LangText 是只读引用,不提供修改文本的方法。需要修改引用字段对应的翻译时,应修改语言文件并调用 reload(),不应把字段重新赋值为另一个 LangText。
公开 API 还提供 save(Class<T>, String locale):
langService.save(MainLanguage.class, "zh_CN");
该方法会把当前活动对象的字段写入指定 locale 文件。活动对象只代表当前全局语言,因此不应把它直接保存到另一个 locale,否则会用当前语言文本覆盖目标语言。通常应使用 saveAll(),并只在明确知道活动对象内容与目标 locale 一致时调用指定保存。
将符合目录和格式约定的 locale 文件放入插件数据目录后,可以通过以下方法查看已发现语言:
Set<String> locales = langService.availableLocales();
availableLocales() 扫描磁盘中已经存在的文件,不会仅因为 Jar 内含某个资源就自动把它列入结果。
补齐指定语言对象在所有已发现 locale 中的缺失键:
langService.ensure(MainLanguage.class);
补齐所有已经绑定的语言对象:
langService.ensureAllLocales();
这些方法只在该语言包允许写回时生效。bind(..., false)、@LangPack(emit = false) 或 @NoEmit 都会禁止生成与补齐文件。
flowchart TD
Locale["Facade 全局语言"] --> Bind["bind(LanguageClass)"]
Bind --> Disk{"首选 locale 存在?"}
Disk -->|是| ReadDisk["读取磁盘文件"]
Disk -->|否| Resource["读取语言包默认 locale"]
ReadDisk --> Merge["合并语言对象默认值"]
Resource --> Merge
Merge --> Holder["填充 String 并安装 LangText 引用"]
Merge --> Emit{"允许写回?"}
Emit -->|是| Save["复制、生成或补齐文件"]
Emit -->|否| Done["仅在内存中使用"]
讨论