Repository navigation
Lang
把玩家可见文本放进可重载的 YAML 语言文件,统一处理 MiniMessage / 旧式颜色、安全命名占位符,以及聊天栏、Action Bar、Title、Boss Bar 的发送。
| 模块名 | 制品 | 自动带入 |
|---|---|---|
lang |
me.kzheart.klib:klib-lang |
core、config
|
适合:
- 把玩家可见文本移出业务代码,让服主可以修改;
- 同时兼容 Java 8 / Spigot 1.12.2 与较新服务端的富文本发送;
- 统一处理前缀、命名占位符和可选的外部占位符;
- 让命令错误、帮助与业务消息共享同一套语言文件;
- 按消息选择聊天栏、Action Bar、Title 或 Boss Bar。
klib { modules { lang() } }依赖、直接引用制品和打包方式见 构建与打包。手动打包时需包含 Klib、Kotlin/协程、配置与 MiniMessage 运行时,并按构建页处理重定位。
在资源目录提供语言文件:
# src/main/resources/lang/zh_CN.yml
messages:
common:
prefix: "<gray>[<gold>MyPlugin</gold>]</gray> "
welcome: "{prefix}<green>欢迎 {player}!</green>"
reward: "actionbar: <yellow>获得 {amount} 枚金币</yellow>"
announcement:
- "<gold>服务器公告</gold>"
- "<gray>祝你游戏愉快</gray>"安装运行时并发送消息:
@Override
protected void setup() {
Scope root = context().scope();
LangRuntime lang = LangModule.install(
root,
getServer(),
getDataFolder().toPath(),
getClassLoader(),
"zh_CN",
null);
root.on(PlayerJoinEvent.class, event -> lang.send(
event.getPlayer(),
"welcome",
Placeholders.of("player", event.getPlayer().getName())));
}- 首次启动把资源文件提取到插件数据目录的
lang/zh_CN.yml。 - 之后通过
lang.configDocument().reload()或文件监听更新消息,无需重建LangRuntime。 - 最后一个参数是外部占位符适配器
PlaceholderApi,不需要时传null(见 占位符)。
install 还会把运行时注册为作用域能力,各处不必传递引用:
LangRuntime lang = root.requireCapability(LangRuntime.class);- 能力遵循作用域的常规查找规则,子作用域可以直接取到父作用域安装的运行时。
- 同一作用域安装多个地区实例时,能力指向首个安装的运行时,其余实例仍通过返回值使用。
业务消息放在根节点 messages 下,嵌套配置节展开为点分键:
messages:
arena:
joined: "<green>已加入竞技场</green>"对应键为 arena.joined。
| 情况 | 结果 |
|---|---|
缺少 messages 根节点 |
自定义消息目录为空,继续回退到内置消息 |
messages 存在但类型错误 |
加载失败 |
| 地区名写法不规范 | 自动规范化,如 zh-cn → zh_CN
|
| 请求的地区资源不存在 | 整个运行时回退到内置默认地区 zh_CN,只创建 lang/zh_CN.yml,不会把中文默认内容写进不存在的其他地区文件;通过 java.util.logging 记一条 WARNING,写明请求的地区和实际回退的地区 |
| 用户文件缺少 Klib 内置消息键 | 回退到内置目录 |
| 业务自定义键没有回退值 | 渲染为红色的 [missing:消息键],并对该键记录一次警告 |
地区回退的 WARNING 用来发现拼错或未打包的语言资源。
字符串消息支持 MiniMessage,并兼容常用 & 颜色和格式码:
messages:
modern: "<gradient:#ff0000:#0000ff><bold>渐变标题</bold></gradient>"
legacy: "&a旧式绿色 &l粗体"
clickable: "<click:run_command:/shop><hover:show_text:'打开商店'>点击打开商店</hover></click>"| 接收者 / 环境 | 表现 |
|---|---|
| 较旧服务端 | 无法表达的 RGB 色降级为最接近的传统颜色 |
| 玩家聊天、Action Bar | 优先尝试富组件发送,失败时回退到 legacy 文本 |
| 控制台和非玩家接收者 | 始终收到无颜色、无点击、无悬停元数据的纯文本 |
YAML 字符串列表以换行连接:
messages:
rules:
- "第一条规则"
- "第二条规则"用 YAML 字段描述单段富文本时,写一个含字符串 text 的配置节:
messages:
docs:
text: "查看项目文档"
color: gold
bold: true
hover: "点击打开"
click:
type: open_url
value: "https://example.com/docs"- 结构化条目适合单一文本片段;多段嵌套样式优先用 MiniMessage。
-
只要某个配置节有字符串
text子项,就会被当成结构化富文本,而不是普通嵌套命名空间。
命名占位符写作 {name}:
lang.send(
player,
"reward",
Placeholders.of(
"amount", Integer.valueOf(10),
"reason", "daily"));-
Placeholders.of接受成对的“键、值”,返回只读映射;也可以传普通Map<String, ?>。 -
{prefix}是保留占位符,只能来自语言目录中的common.prefix,调用方传入同名值不会覆盖它。
消息管线顺序固定:
- 查找消息键并提取路由前缀;
- 解析模板的 MiniMessage,再把
{prefix}替换为同样经 MiniMessage 解析的前缀片段; - 如果提供了
PlaceholderApi,对模板文本执行外部占位符展开; - 把
{name}形式的调用方值作为字面文本插入; - 路由到接收者。
安全边界:调用方的命名值不会再进入 MiniMessage、路由或外部占位符解析。玩家输入即使含 <click:...>、actionbar: 或 %some_placeholder%,也只显示为文字,无法注入样式和操作。
外部占位符系统:安装模块时传入自己的 PlaceholderApi 适配器,不需要时传 null。外部展开只处理语言文件模板,不处理之后插入的命名值。
在消息开头写路由前缀:
messages:
chat-message: "chat: 普通聊天消息"
progress: "actionbar: <green>进度 {current}/{total}</green>"
completed: "title: <gold>完成!</gold>|<gray>奖励已发放</gray>"
warning: "bossbar: <red>区域即将关闭</red>"| 前缀 | 发送到 |
|---|---|
chat: |
聊天栏,也是没有前缀时的默认路由 |
actionbar: |
Action Bar |
title: |
Title,第一个 | 之后作为副标题 |
bossbar: |
白色实心 Boss Bar,默认显示 100 tick |
- 前缀只在消息最开头识别,不区分大小写;前缀后的一个空格会被移除。
- 同一玩家收到新 Boss Bar 时替换旧实例;有 Core 调度能力时自动到期;作用域关闭会移除所有仍在显示的 Boss Bar。
在代码中覆盖目录声明的路由:
lang.send(
MessageRecipient.commandSender(player),
MessageRoute.ACTION_BAR,
"progress",
Placeholders.of("current", 3, "total", 10));适配其他输出系统时,只取解析结果:
RichText text = lang.pipeline().render(
MessageRecipient.commandSender(sender),
"welcome",
Placeholders.of("player", sender.getName()));
String plain = text.plainText();
String legacy = text.legacyText();RichText 与 RichTextSegment 不向业务 API 暴露 Adventure 类型,便于跨服务端版本使用。
安装 Command 时传入同一个消息管线,命令错误、帮助和内置子命令就从 messages.command.* 读取:
LangRuntime lang = LangModule.install(
root,
getServer(),
getDataFolder().toPath(),
getClassLoader(),
"zh_CN",
null);
CommandModule.install(
root,
BukkitCommandRegistrar.discover(this, "myplugin"),
lang.pipeline());缺少命令消息时仍回退到 Klib 内置中英文目录。
展开:用 BukkitAdventure 直接操作 Paper 原生组件
BukkitAdventure 处理 Paper 原生组件与插件内嵌组件之间的类加载隔离。
import me.kzheart.klib.lang.BukkitAdventure;
String plain = BukkitAdventure.plain(BukkitAdventure.parse("<gold>欢迎</gold>"));
BukkitAdventure.actionbar(player, "<green>完成</green>");
BukkitAdventure.book(player, "说明", "服务器", pages);| 方法 | 作用 |
|---|---|
parse |
返回宿主组件的 Object,不要强转成内嵌的 Adventure 类型 |
miniMessage、plain、legacy
|
把内容转成文本 |
actionbar、book
|
向玩家发送 Action Bar、打开书本 |
displayName、lore
|
读取物品的原生组件 |
inventory |
创建组件标题的物品栏,可作为 UI 的 MenuInventoryFactory
|
available() |
只检查宿主是否提供 Adventure;具体能力仍取决于 Paper API |
- 游戏对象操作要求主线程;纯文本序列化不访问服务器可变状态。
- 旧版 Bukkit 缺少宿主组件时会明确失败,不会尝试把重定位后的类型传给服务器。
- 反射同时处理泛型擦除后的序列化方法。
语言目录、占位符、富文本解析和 Bukkit 路由由 Kotlin 实现。Java 与 Kotlin 都通过 LangRuntime / MessagePipeline 提交消息键,不必分别维护配置回退、颜色转换或每种发送通道的宿主检测。
- 目录重载替换消息快照,已安装的管线继续使用新内容,不要求业务重建运行时。
- Paper 原生组件与插件私有 Adventure 的转换留在
BukkitAdventure边界,业务不直接交换不同加载器的组件对象。 - 消息目录、路由和发送 SPI 保留 Java 平台类型;
RichTextSegment的薄 Java 外壳保留颜色、悬停和点击等可选值的平台返回契约。实现语言不改变消息转义和线程规则。
- 语言配置文档、路由器、Boss Bar 和重载监听器都属于安装时传入的作用域。
- 作用域关闭后,
LangRuntime的目录、管线和发送入口不可再用。 - 在
KPlugin中,语言文件变更经 Core 调度到主线程后替换目录与前缀。 - Bukkit 的聊天、Action Bar、Title 和 Boss Bar API 应在主线程调用。不要从任意异步回调直接调用
lang.send,用thenSync或同步调度器切回主线程。 - 消息目录替换是完整快照替换;正在发送的消息使用本次解析得到的稳定结果。
- 有字符串
text子项的配置节会被当成结构化富文本,不能再当嵌套命名空间用。 - 对控制台发送 Title、Action Bar 或 Boss Bar 会降级为纯文本,不会创建对应的 Bukkit UI。
-
lang.configDocument().reload()的监听语义与 Config 模块一致;需要等待监听器完成时用reloadAsync()。 - 不要把用户输入直接拼进语言模板字符串。作为命名占位符值传入,才能保留字面插入的安全边界。
- 地区名拼错或语言资源未打包时会整体回退到
zh_CN,注意日志里的WARNING。