正在载入

等待时间过长时请刷新页面

编写于

最近更新

语言文件

语言文件服务 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 的主要参数为:

  1. filePath:相对于语言根目录的子目录,默认为 lang
  2. formatFileType.YAMLFileType.JSON,默认为 YAML。
  3. emit:是否允许生成和补齐磁盘文件,默认为 true
  4. defaultLocale:当前全局语言不可用时,该语言包使用的回退语言。
  5. normalizeLocale:是否把 en-GBenGB 等名称归一化为 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.ymlen_GB.yml。默认情况下,服务会把 enGBen-GB 等可归一化名称整理为 en_GB;设置 normalizeLocale = false 后,扫描、读取和保存都会保留原始名称。要了解语言代码归一化,请见: 多语言 ⇱

语言文件的注释应直接写在 resources 中的内建 YAML 文件里。首次复制资源时,Linlang 会保留这些注释;保存已有 YAML 时也会尽量保留原注释。不需要使用专门的多语言注释注解。

绑定语言

通过当前 Facade 的语言服务绑定对象:

LangService langService = lin.linFile().language();
MainLanguage lang = langService.bind(MainLanguage.class);

绑定时,服务会:

  1. 读取 @LangPack 的目录和格式。
  2. 按当前全局语言选择首选 locale,并准备语言包声明的默认 locale 作为回退。
  3. 优先读取首选 locale 的磁盘或内建资源;不存在时读取默认 locale。
  4. 按语言对象默认值补齐缺失键。
  5. 把结果写入活动语言对象。
  6. 在允许写回时复制内建资源或生成磁盘文件。

同一个 LangService 重复绑定同一个语言类时,会返回同一个活动对象,并按当前语言重新填充它。

读取文本

一般情况下,您可以使用 String 字段:

public String reloaded = "重载成功";
...
sender.sendMessage(lang.message.reloaded);

如果组件需要保存语言字段,并在语言重载后读取新文本,可以使用 LangText

public LangText commandDescription = LangText.of("执行重载命令");
...
String description = lang.message.commandDescription.resolve();

String 与普通集合字段表示当前值快照;LangTextLangListLangMap 表示稳定的语言字段引用。普通重载会更新引用背后的语言快照,因此保存过的引用仍然可以解析到最新内容。

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();

切换语言后,原来的 langServicelang 和字段引用继续有效,不必重新绑定。

普通语言重载按语言包处理:先解析、校验,再提交字段和缓存。失败的包保留旧值,其他包继续加载,最后通过 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["仅在内存中使用"]

讨论

请登录账号