语言文件与文本组件
Language 模块是 TabooLib 提供的国际化(I18n)解决方案,支持多语言文本管理、富文本交互和动态文本组件。
核心概念解析
Language 模块由三个核心功能组成:
关键概念:
- 语言文件:YAML 格式的多语言文本配置
- 语言节点:语言文件中的键(key),用于引用文本
- 文本类型:定义文本的展现方式(普通/富文本/标题等)
- 文本组件:通过代码动态构建富文本
语言文件基础
文件结构
语言文件采用扁平化的 YAML 结构,存放在 lang/ 目录下:
插件目录/
└── lang/
├── zh_CN.yml # 简体中文
├── en_US.yml # 英文
└── zh_TW.yml # 繁体中文
语言代码标准:
zh_CN:简体中文zh_TW:繁体中文en_US:英文ja_JP:日文ko_KR:韩文
基础语法
单行文本
welcome: '欢迎来到服务器!'
goodbye: '再见,{0}!'
level-up: '恭喜你升级到 {0} 级!'
代码说明:
- 键值对格式:
节点名: '文本内容' - 使用
{0}、{1}等占位符表示参数 - 文本内容需要用单引号包裹
多行文本
server-info:
- '服务器名称:TabooLib 测试服'
- '在线人数:{0}/{1}'
- '运行时间:{2} 天'
rules:
- '1. 不要破坏公共建筑'
- '2. 不要恶意 PVP'
- '3. 尊重其他玩家'
代码说明:
- 使用列表格式表示多行文本
- 每行前添加
-符号 - 发送时自动换行
注意事项: 语言文件必须使用扁平化结构,不允许嵌套对象:
# ✅ 正确 - 扁平化结构
ui-title: '商店'
ui-apple-name: '苹果'
ui-apple-lore: '一个新鲜的苹果'
# ❌ 错误 - 嵌套结构(不支持)
ui:
title: '商店'
apple:
name: '苹果'
lore: '一个新鲜的苹果'
参数替换
语言文本支持使用占位符传递动态参数:
player-join: '玩家 {0} 加入了游戏'
player-money: '你的金币:{0}'
transfer-success: '你向 {0} 转账了 {1} 金币'
使用方式:
// 单个参数
player.sendLang("player-join", player.name)
// 多个参数
player.sendLang("transfer-success", targetPlayer.name, 100)
执行效果:
玩家 Steve 加入了游戏
你向 Alex 转账了 100 金币
颜色代码
语言文件支持 Minecraft 颜色代码:
colored-text: '&a绿色文本 &b蓝色文本 &c红色文本'
hex-color-text: '&x&f&f&0&0&0&0Hex颜色文本'
hex-color-text-2: '&{#ff0000}Hex颜色文本'
formatted-text: '&l&n加粗下划线'
支持的颜色代码:
&0-&9、&a-&f:基础颜色&l:加粗&m:删除线&n:下划线&o:斜体&k:乱码&r:重置格式&{#Hex-code}: 十六进制颜色 (minecraft 1.16+)
基础使用示例
定义语言文件
创建 lang/zh_CN.yml:
# 基础消息
welcome-message: '&a欢迎 {0} 来到服务器!'
goodbye-message: '&c{0} 离开了服务器'
# 错误消息
error-no-permission: '&c你没有权限执行此操作'
error-player-not-found: '&c玩家 {0} 不在线'
# 成功消息
success-teleport: '&a传送成功!'
success-money-add: '&a你获得了 {0} 金币'
# 多行消息
help-message:
- '&8[&3帮助&8] &7可用命令列表'
- '&7/shop open - 打开商店'
- '&7/shop buy <物品> - 购买物品'
- '&7/shop sell <物品> - 出售物品'
代码中使用
// 向玩家发送语言消息
@EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
val player = event.player
// 发送单行消息(带参数)
player.sendLang("welcome-message", player.name)
// 发送多行消息
player.sendLang("help-message")
}
// 在命令中使用
@CommandBody
val shop = subCommand {
execute<Player> { sender, _, _ ->
if (!sender.hasPermission("shop.use")) {
sender.sendLang("error-no-permission")
return@execute
}
// 打开商店逻辑...
sender.sendLang("success-teleport")
}
}
获取语言文本
除了直接发送,还可以获取语言文本用于其他用途:
// 获取单行文本
val title = player.asLangText("ui-title")
val itemName = player.asLangText("item-apple-name", "苹果")
// 获取多行文本
val lore = player.asLangTextList("item-apple-lore")
// 在菜单中使用
player.openMenu<Chest>(player.asLangText("shop-title")) {
set('A', buildItem(XMaterial.APPLE) {
name = player.asLangText("shop-apple-name")
lore.addAll(player.asLangTextList("shop-apple-lore"))
colored()
})
}
常用方法:
sendLang(节点, 参数...):发送语言消息asLangText(节点, 参数...):获取单行文本asLangTextOrNull(节点, 参数...):获取文本(可能为 null)asLangTextList(节点, 参数...):获取多行文本列表
设置语言
设置玩家语言
通过事件监听器自定义玩家使用的语言:
@SubscribeEvent
fun onSelectLocale(event: PlayerSelectLocaleEvent) {
// 从配置文件读取
event.locale = config.getString("Lang", "zh_CN")!!
// 或根据玩家客户端语言
// event.locale 默认会自动读取玩家的客户端语言设置
}
设置系统语言
控制台和系统消息使用的语言:
@SubscribeEvent
fun onSelectLocale(event: SystemSelectLocaleEvent) {
event.locale = config.getString("Lang", "zh_CN")!!
}
适用场景:
- 强制所有玩家使用指定语言
- 根据玩家数据库设置个性化语言
- 提供语言切换命令
在配置文件中使用多语言
除了可以通过 sendLang 发送语言信息外, 还可以在配置文件中引用语言节点:
配置文件设定
nb_sword:
item:
material: DIAMOND_SWORD
# @lang:nb_sword_name
name: '牛逼的剑' # 带有 @lang 注释的键会被解析为对应语言的文本, 并且定义了默认值
# @lang:nb_sword_lore
lore: |-
'这把剑削铁如泥,'
'非常的牛逼!'
语言文件设定
这里提供 zh_CN 和 en_US 两种语言的示例:
zh_CN.yml
nb_sword_name: '牛逼的剑'
nb_sword_lore:
- '这把剑削铁如泥,'
- '非常的牛逼!'
en_US.yml
nb_sword_name: 'Awesome Sword'
nb_sword_lore:
- 'This sword cuts through iron,'
- 'It's really awesome!'
代码中使用
val display = section.getTranslatedString("item.name")?.get(player.locale) // 获取 player 对应的语言文本
val displayWithDefault = section.getTranslatedString("item.name", "牛逼的剑").get(player.locale) // 获取 player 对应的语言文本, 如果没有找到语言节点则使用默认值
val lore = section.getTranslatedStringList("item.lore").get(player.locale) // 获取 player 对应的语言文本列表
富文本与交互
复合文本模式
复合文本允许一个语言节点包含多种展现形式(文本、标题、音效、JSON 等)。
文本类型 (Text)
最基础的文本展现:
simple-message:
- type: text
text: '这是一条简单的文本消息'
代码说明:
type: text:指定类型为普通文本text:文本内容
标题类型 (Title)
在屏幕中央显示标题和副标题:
welcome-title:
- type: title
title: '&a欢迎来到服务器'
subtitle: '&7祝你玩得开心'
fadein: 10 # 淡入时间(tick)
stay: 40 # 停留时间(tick)
fadeout: 10 # 淡出时间(tick)
代码说明:
title:主标题文本subtitle:副标题文本fadein、stay、fadeout:动画时间(1 秒 = 20 tick)
使用示例:
@EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) {
val player = event.player
// 发送标题
player.sendLang("welcome-title", player.name)
}
动作栏类型 (ActionBar)
在物品栏上方显示文本:
actionbar-message:
- type: actionbar
text: '&e当前金币:{0}'
使用示例:
// 定时显示动作栏
submit(period = 20) {
onlinePlayers.forEach { player ->
val money = PlayerDataManager.getMoney(player.uniqueId.toString())
player.sendLang("actionbar-message", money)
}
}
音效类型 (Sound)
播放音效:
level-up-sound:
- type: sound
sound: entity.player.levelup # 音效 ID
volume: 1.0 # 音量
pitch: 1.0 # 音调
常用音效:
entity.player.levelup:升级音效entity.experience_orb.pickup:经验球拾取block.note_block.pling:叮当声ui.button.click:按钮点击
命令类型 (Command)
执行命令:
execute-command:
- type: command
command: 'give {0} diamond 1'
注意事项: 命令以控制台身份执行,不需要添加 / 前缀
JSON 类型 (JSON/Raw)
支持点击、悬浮等交互的富文本:
clickable-message:
- type: json
text:
- '[点击查看规则]'
args:
- hover: '&7点击打开规则说明'
command: '/rules'
代码说明:
text:文本内容,支持数组格式args:每个文本块对应的交互参数hover:鼠标悬停提示command:点击执行的命令
组合使用
一个节点可以包含多种类型,按顺序执行:
complete-notification:
# 播放音效
- type: sound
sound: entity.player.levelup
volume: 1.0
pitch: 1.2
# 显示标题
- type: title
title: '&6任务完成'
subtitle: '&7获得 100 金币'
fadein: 10
stay: 40
fadeout: 10
# 发送可点击消息
- type: json
text: '&a恭喜完成任务'
使用示例:
fun completeQuest(player: Player, questId: String) {
// 一次调用,多种效果
player.sendLang("complete-notification", questId)
}
SimpleComponent(行内文本)
SimpleComponent 是类似 MiniMessage 的行内富文本语法,允许在单行中定义交互效果。
启用 SimpleComponent
在插件加载时启用该功能:
object ExamplePlugin : Plugin() {
override fun onEnable() {
// 启用 SimpleComponent
Language.enableSimpleComponent = true
}
}
基础语法
使用 [] 框住需要操作的文本,在后面加上 () 配置参数:
[文本内容](参数1;参数2;参数3)
参数分隔: 多个参数使用 ; 分隔
文本装饰
styled-text: 'TabooLib 真[牛逼](b;u)啊'
colored-text: '[红色文本](c=#ff0000)和[蓝色文本](c=#0000ff)'
formatted-text: '[加粗](b) [斜体](i) [删除线](s) [下划线](u)'
装饰参数:
b/bold:加粗i/italic:斜体s:删除线u:下划线o/obf:模糊效果r/reset:重置所有格式
渲染效果:
TabooLib 真牛逼啊
──
(加粗且下划线)
交互功能
点击命令
clickable-text: '[点我](command=/stop;hover=关闭服务器)关服'
admin-panel: '[打开面板](cmd=/admin panel;h=点击打开管理面板)'
参数说明:
command/cmd:点击执行命令hover/h:悬停提示文本
悬浮提示
hover-text: '[悬浮查看](hover=这是悬浮信息<br>支持多行<br>使用 <br> 换行)'
multiline-hover: '[查看详情](h=第一行||第二行||第三行)'
换行方式:
- 使用
<br>换行 - 使用
||换行
建议命令
点击后在聊天框填入命令,但不执行:
suggest-text: '[建议](suggest=喵喵喵!)输入内容'
command-hint: '[传送](suggest=/tp {0};hover=点击填入传送命令)到玩家'
复制文本
点击后复制到剪贴板:
copy-text: '[点击复制](copy=这段文本会被复制)'
server-ip: '服务器地址:[mc.example.com](copy=mc.example.com;hover=点击复制)'
打开链接
url-text: '[\[点击\]](url=https://tabooproject.org/)访问官网'
wiki-link: '[查看Wiki](url=https://github.com/TabooLib/taboolib/wiki)'
注意事项: 如果文本中包含 [] 符号,需要使用 \ 转义
颜色与渐变
单色
colored: '[我是粉色的](c=#e44b8d)'
hex-color: '[十六进制颜色](color=#00ff00)'
渐变色
gradient: '[||||||||||||||||||||](gradient=#f6d365,#fda085)'
rainbow: '[彩虹文本](g=#ff0000,#ff7f00,#ffff00,#00ff00,#0000ff,#8b00ff)'
参数说明:
gradient/g:渐变色- 支持多个颜色值,使用
,分隔
高级功能
换行
multiline: '坏黑,[](nl)我爱你!'
newline: '第一行[](br)第二行'
参数:
nl/br/newline:换行
字体
custom-font: '[TabooLib](font=uniform)'
ascii-art: '[ASCII](f=minecraft:alt)'
键位绑定
显示玩家当前的键位设置:
jump-hint: '按下 [key.jump](key) 以跳跃'
sneak-hint: '使用 [key.sneak](keybind) 潜行'
翻译文本
根据玩家语言显示译名:
block-name: '我叫 [block.minecraft.diamond_block](trans)'
item-name: '这是 [item.minecraft.diamond_sword](translate)'
Shift 插入
按住 Shift 点击后插入文本:
secret: '[&6按住 Shift 点我](insert=坏黑爱我)告诉你一个秘密'
转义字符
需要显示 []() 等特殊字符时,使用 \ 转义:
escaped: '[\[点我\]](command=/stop)关服'
brackets: '这是 \[方括号\] 和 \(圆括号\)'
支持转义的字符: [ ] ( ) ; = \
完整示例
# 复杂的交互消息
shop-welcome: '欢迎来到[商店](c=#ffd700;b) 或 [购买物品](suggest=/shop buy ;h=输入物品名称)'
# 带渐变和多重交互
announcement: '[||||](g=#ff0000,#00ff00) [重要通知](b;u;c=#ffff00) [||||](g=#00ff00,#0000ff)<br>[点击查看详情](url=https://example.com;hover=打开网页)'
# 多功能消息
papi-install: '&7你必须安装 PAPI 拓展。[点击安装](cmd=/papi ecloud download {0};hover=&7点击下载拓展) 或 [手动下载](url=https://example.com;copy=https://example.com;hover=点击打开网页<br>或复制链接)'
ComponentText(代码构建)
ComponentText 是 TabooLib 提供的富文本构建 DSL,允许你在代码中以链式调用的方式构建复杂的富文本消息。
核心接口
ComponentText 提供了完整的 Minecraft 富文本功能支持:
创建 ComponentText
有三种方式创建 ComponentText 实例:
// 1. 创建空白组件
val component = ComponentText.empty()
// 2. 创建带文本的组件
val component = ComponentText.of("欢迎来到服务器")
// 3. 从 JSON 原始数据解析
val component = ComponentText.raw("""{"text":"Hello","color":"red"}""")
// 4. 使用 Components 工具类(推荐)
val component = Components.text("欢迎来到服务器")
添加文本内容
普通文本:
val component = Components.empty()
.append("欢迎来到 ") // 支持颜色代码
.append("&c服务器", true) // 第二个参数控制是否解析颜色
// 使用 += 操作符
component += "&a新玩家"
component += "&b已上线"
翻译文本:
显示 Minecraft 内置翻译键,根据玩家客户端语言显示:
// 显示方块/物品译名
component.appendTranslation("block.minecraft.diamond_block")
component.appendTranslation("item.minecraft.diamond_sword")
// 带参数的翻译
component.appendTranslation("commands.give.success", "钻石剑", "坏黑", 1)
按键文本:
显示玩家当前的按键绑定:
component.appendKeybind("key.jump") // 显示跳跃键
component.appendKeybind("key.inventory") // 显示背包键
其他特殊文本:
// 分数文本(显示计分板分数)
component.appendScore("player_name", "objective_name")
// 选择器文本(显示选择器匹配的实体名称)
component.appendSelector("@p") // 最近的玩家
component.appendSelector("@a") // 所有玩家
文本装饰
基础装饰:
val component = Components.text("重要消息")
.bold() // 加粗
.underline() // 下划线
.italic() // 斜体
.strikethrough() // 删除线
.obfuscated() // 混淆(随机跳动字符)
移除装饰:
component
.unbold() // 移除加粗
.ununderline() // 移除下划线
.unitalic() // 移除斜体
.unstrikethrough() // 移除删除线
.unobfuscated() // 移除混淆
.undecoration() // 移除所有装饰
自定义字体:
component.font("minecraft:uniform") // 等宽字体
component.font("minecraft:alt") // 替代字体
component.unfont() // 移除字体设置
颜色控制
预定义颜色:
import taboolib.module.chat.StandardColors
component.color(StandardColors.RED) // 红色
component.color(StandardColors.GOLD) // 金色
component.color(StandardColors.AQUA) // 青色
component.uncolor() // 移除颜色
自定义 RGB 颜色:
import java.awt.Color
component.color(Color(255, 0, 0)) // RGB 红色
component.color(Color(0xFF, 0xD7, 0x00)) // 金色
悬浮提示(Hover)
显示文本:
// 单行文本
component.hoverText("这是提示信息")
// 多行文本
component.hoverText(listOf(
"&e提示标题",
"&7这是第一行",
"&7这是第二行"
))
// 显示另一个 ComponentText
val hoverContent = Components.text("&c警告").bold()
component.hoverText(hoverContent)
显示物品:
悬浮时显示物品详情:
// 显示钻石剑
component.hoverItem("minecraft:diamond_sword")
// 带 NBT 数据的物品
component.hoverItem(
"minecraft:diamond_sword",
"""{"Enchantments":[{"id":"sharpness","lvl":5}]}"""
)
显示实体:
悬浮时显示实体信息:
// 基础实体
component.hoverEntity(
id = "uuid-here",
type = "minecraft:zombie",
name = "僵尸王"
)
// 使用 ComponentText 作为实体名称
val entityName = Components.text("&c精英怪物").bold()
component.hoverEntity("uuid-here", "minecraft:zombie", entityName)
点击交互(Click)
执行命令:
// 点击后执行命令(玩家执行)
component.clickRunCommand("/spawn")
// 点击后执行控制台命令
component.clickRunCommand("/say 玩家点击了消息")
建议命令:
点击后将命令填入聊天框但不执行:
component.clickSuggestCommand("/tell 坏黑 ")
打开 URL:
component.clickOpenURL("https://taboolib.org")
复制文本:
点击后复制到剪贴板:
component.clickCopyToClipboard("这段文本会被复制")
插入文本:
按住 Shift 点击后插入文本到聊天框:
component.clickInsertText("坏黑爱我")
其他点击行为:
// 打开文件(仅单人)
component.clickOpenFile("path/to/file.txt")
// 切换书本页码
component.clickChangePage(2)
// 使用通用方法
import taboolib.module.chat.ClickAction
component.click(ClickAction.RUN_COMMAND, "/spawn")
多行文本构建
换行:
val component = Components.text("第一行")
.newLine()
.append("第二行")
.newLine()
.append("第三行")
组合多个 ComponentText:
val header = Components.text("===== 标题 =====").bold().color(StandardColors.GOLD)
val content = Components.text("这是内容")
val footer = Components.text("===== 结束 =====").bold().color(StandardColors.GOLD)
// 方法一:使用 append
val message = Components.empty()
.append(header)
.newLine()
.append(content)
.newLine()
.append(footer)
// 方法二:使用 += 操作符
message += header
message.newLine()
message += content
完整示例
带悬浮和点击的欢迎消息:
val welcome = Components.text("欢迎来到 ")
.append("&6TabooLib", true)
.bold()
.append(" 服务器!", true)
.unbold()
.newLine()
.append("&7点击下方按钮了解更多:", true)
.newLine()
.append("[官网]", true)
.color(StandardColors.GREEN)
.clickOpenURL("https://taboolib.org")
.hoverText(listOf("&a点击访问官网", "&7https://taboolib.org"))
.append(" ", true)
.append("[文档]", true)
.color(StandardColors.AQUA)
.clickOpenURL("https://docs.taboolib.org")
.hoverText("&b查看开发文档")
player.sendMessage(welcome)
物品提示消息:
val itemTip = Components.text("你获得了 ")
.append("&e[钻石剑]", true)
.bold()
.hoverItem(
"minecraft:diamond_sword",
"""{"Enchantments":[{"id":"sharpness","lvl":5}]}"""
)
.clickSuggestCommand("/give @s diamond_sword")
.append("!", true)
.unbold()
player.sendMessage(itemTip)
交互式商店列表:
val shop = Components.text("===== 商店 =====").bold().color(StandardColors.GOLD)
.newLine()
val items = listOf(
"钻石剑" to 100,
"钻石镐" to 80,
"附魔书" to 50
)
for ((itemName, price) in items) {
shop.append("&f$itemName", true)
.append(" - ", true)
.append("&e$price 金币", true)
.append(" ", true)
.append("[购买]", true)
.color(StandardColors.GREEN)
.bold()
.clickRunCommand("/shop buy $itemName")
.hoverText(listOf(
"&a点击购买 $itemName",
"&7价格: &e$price 金币"
))
.undecoration()
.newLine()
}
player.sendMessage(shop)
与语言文件结合:
// 语言文件中定义基础文本
// zh_CN.yml:
// shop-welcome: "欢迎来到商店"
// shop-item-click: "点击购买"
val langText = player.asLangText("shop-welcome")
val component = Components.text(langText)
.color(StandardColors.GOLD)
.bold()
.newLine()
.append(player.asLangText("shop-item-click"), true)
.clickRunCommand("/shop")
.hoverText("&a打开商店界面")
player.sendMessage(component)
转换为其他格式
转换为原始 JSON:
val component = Components.text("&cHello").bold()
val json = component.toRawMessage() // 转为 Minecraft JSON 格式
发送给玩家:
import taboolib.platform.util.sendMessage
// 直接发送(TabooLib 会自动转换)
player.sendMessage(component)
// 或使用 BukkitProxyPlayer
(player as BukkitProxyPlayer).sendMessage(component)
SimpleComponent vs ComponentText
SimpleComponent(行内语法):
- ✅ 适合在 YAML 配置文件中使用
- ✅ 语法简洁,易于非开发者编辑
- ✅ 支持参数替换和颜色代码
- ❌ 功能受限于预定义语法
ComponentText(代码构建):
- ✅ 完整的 Minecraft 富文本 API
- ✅ 支持复杂的动态文本生成
- ✅ 类型安全,IDE 自动补全
- ❌ 需要在代码中编写
推荐使用场景:
// 场景 1: 静态消息 → 使用 SimpleComponent
// lang/zh_CN.yml
welcome: '欢迎 &6{0}&r查看帮助'
// 场景 2: 动态生成 → 使用 ComponentText
val ranks = getRankedPlayers() // 获取排行榜数据
val leaderboard = Components.text("===== 排行榜 =====").bold()
for ((index, playerData) in ranks.withIndex()) {
leaderboard.newLine()
.append("${index + 1}. ${playerData.name}", true)
.append(" - ${playerData.score} 分", true)
.hoverItem(playerData.headItem) // 动态物品
.clickRunCommand("/profile ${playerData.name}")
}
// 场景 3: 混合使用
val baseText = player.asLangText("shop-header") // 从语言文件获取
val component = Components.text(baseText)
.newLine()
.append(generateDynamicItems()) // 动态生成商品列表
最佳实践与示例
项目结构建议
推荐的语言文件组织方式:
src/main/resources/
└── lang/
├── zh_CN.yml # 简体中文(主要语言)
├── zh_TW.yml # 繁体中文
├── en_US.yml # 英文
└── ja_JP.yml # 日文
初始化语言模块:
@Awake(LifeCycle.ENABLE)
object LanguageLoader {
fun init() {
// 1. 设置默认语言
Language.default = "zh_CN"
// 2. 注册语言代码
registerLanguage("zh_CN", "zh_TW", "en_US", "ja_JP")
// 3. 启用 SimpleComponent(如果需要)
Language.enableSimpleComponent = true
// 4. 添加自定义文本转换器(可选)
Language.textTransfer.add(MyCustomTransfer)
}
}
完整的多语言系统示例
1. 语言文件定义
zh_CN.yml:
# 通用消息
prefix: '&8[&6MyPlugin&8] &r'
no-permission: '&c你没有权限执行此操作!'
player-only: '&c此命令只能由玩家执行!'
unknown-command: '&c未知命令!输入 &e/help &c查看帮助。'
# 欢迎消息(复合类型)
welcome:
# @lang:welcome_title
title: |-
&6欢迎来到服务器
&7祝你游戏愉快
# @lang:welcome_message
message: |-
text:欢迎 &6{0} &r加入服务器!
text:这是你第 &a{1} &r次进入服务器。
text:[点击查看规则](command=/rules;hover=&7查看服务器规则)
# @lang:welcome_sound
sound: entity.player.levelup-1-1
# 商店系统
shop-title: '&6===== 商店 ====='
shop-item-format: '&f{0} &7- &e{1} 金币 [购买](cmd=/shop buy {0};hover=点击购买 {0})'
shop-buy-success: '&a成功购买 &e{0}&a!'
shop-buy-failed: '&c购买失败:{0}'
shop-insufficient-money: '金币不足'
# GUI 消息
gui-back-button: '[返回](hover=&7返回上一页)'
gui-next-page: '[下一页](hover=&7前往第 {0} 页)'
gui-prev-page: '[上一页](hover=&7返回第 {0} 页)'
en_US.yml:
prefix: '&8[&6MyPlugin&8] &r'
no-permission: '&cYou don''t have permission to do this!'
player-only: '&cThis command can only be executed by players!'
unknown-command: '&cUnknown command! Type &e/help &cfor help.'
welcome:
# @lang:welcome_title
title: |-
&6Welcome to the Server
&7Have a great time
# @lang:welcome_message
message: |-
text:Welcome &6{0}&r to the server!
text:This is your &a{1}&r time joining.
text:[View Rules](command=/rules;hover=&7View server rules)
# @lang:welcome_sound
sound: entity.player.levelup-1-1
shop-title: '&6===== Shop ====='
shop-item-format: '&f{0} &7- &e{1} coins [Buy](cmd=/shop buy {0};hover=Click to buy {0})'
shop-buy-success: '&aSuccessfully purchased &e{0}&a!'
shop-buy-failed: '&cPurchase failed: {0}'
shop-insufficient-money: 'Insufficient coins'
gui-back-button: '[Back](hover=&7Go back)'
gui-next-page: '[Next](hover=&7Go to page {0})'
gui-prev-page: '[Previous](hover=&7Go to page {0})'
2. 扩展函数封装
创建通用的消息发送工具:
package com.example.plugin.util
import taboolib.common.platform.ProxyCommandSender
import taboolib.common.platform.ProxyPlayer
import taboolib.module.lang.sendLang
import taboolib.module.lang.asLangText
import taboolib.module.chat.Components
import taboolib.platform.util.sendMessage
/**
* 发送带前缀的消息
*/
fun ProxyCommandSender.sendPrefixedLang(node: String, vararg args: Any) {
val prefix = asLangText("prefix")
val message = asLangText(node, *args)
sendMessage("$prefix$message")
}
/**
* 发送错误消息
*/
fun ProxyCommandSender.sendError(message: String) {
val prefix = asLangText("prefix")
sendMessage("$prefix&c$message")
}
/**
* 发送成功消息
*/
fun ProxyCommandSender.sendSuccess(message: String) {
val prefix = asLangText("prefix")
sendMessage("$prefix&a$message")
}
/**
* 检查权限并发送消息
*/
fun ProxyCommandSender.requirePermission(permission: String): Boolean {
if (!hasPermission(permission)) {
sendPrefixedLang("no-permission")
return false
}
return true
}
/**
* 检查是否为玩家
*/
fun ProxyCommandSender.requirePlayer(action: (ProxyPlayer) -> Unit) {
if (this is ProxyPlayer) {
action(this)
} else {
sendPrefixedLang("player-only")
}
}
3. 实战示例:商店系统
商店命令:
package com.example.plugin.command
import taboolib.common.platform.command.*
import taboolib.common.platform.function.adaptCommandSender
import taboolib.module.chat.Components
import taboolib.module.chat.StandardColors
import taboolib.module.lang.asLangText
import com.example.plugin.util.*
@CommandHeader("shop", ["商店"])
object ShopCommand {
@CommandBody
val main = mainCommand {
execute<ProxyPlayer> { sender, _, _ ->
sender.openShopGUI()
}
}
@CommandBody
val list = subCommand {
execute<ProxyCommandSender> { sender, _, _ ->
sender.showShopList()
}
}
@CommandBody
val buy = subCommand {
dynamic("item") {
suggestion<ProxyCommandSender> { _, _ ->
ShopManager.items.map { it.name }
}
execute<ProxyPlayer> { sender, ctx, _ ->
val itemName = ctx.argument(-1)
sender.buyItem(itemName)
}
}
}
}
// 显示商店列表
private fun ProxyCommandSender.showShopList() {
val title = asLangText("shop-title")
sendMessage(title)
val items = Components.empty()
for (item in ShopManager.items) {
val itemLine = asLangText("shop-item-format", item.displayName, item.price)
items.append(itemLine, true).newLine()
}
sendMessage(items)
}
// 购买物品
private fun ProxyPlayer.buyItem(itemName: String) {
val item = ShopManager.findItem(itemName)
if (item == null) {
sendPrefixedLang("shop-buy-failed", asLangText("unknown-item"))
return
}
val money = EconomyManager.getMoney(this)
if (money < item.price) {
sendPrefixedLang("shop-buy-failed", asLangText("shop-insufficient-money"))
return
}
// 扣钱并给予物品
EconomyManager.takeMoney(this, item.price)
giveItem(item.itemStack)
sendPrefixedLang("shop-buy-success", item.displayName)
}
欢迎消息监听器:
package com.example.plugin.listener
import taboolib.common.platform.event.SubscribeEvent
import taboolib.platform.BukkitPlugin
import org.bukkit.event.player.PlayerJoinEvent
import taboolib.module.lang.sendLang
import taboolib.module.configuration.Config
import taboolib.module.configuration.Configuration
object PlayerListener {
@Config("player_data.yml")
lateinit var playerData: Configuration
@SubscribeEvent
fun onJoin(event: PlayerJoinEvent) {
val player = event.player.adaptCommandSender()
val playerName = event.player.name
// 获取登录次数
val joinCount = playerData.getInt("players.$playerName.join-count", 0) + 1
playerData.set("players.$playerName.join-count", joinCount)
// 延迟发送欢迎消息
BukkitPlugin.getInstance().server.scheduler.runTaskLater(
BukkitPlugin.getInstance(),
Runnable {
player.sendLang("welcome", playerName, joinCount)
},
20L // 1 秒后
)
}
}
4. 实战示例:排行榜系统
package com.example.plugin.feature
import taboolib.common.platform.ProxyPlayer
import taboolib.module.chat.Components
import taboolib.module.chat.StandardColors
import taboolib.module.lang.asLangText
import taboolib.platform.util.sendMessage
import java.awt.Color
object LeaderboardSystem {
data class RankData(
val rank: Int,
val playerName: String,
val score: Int,
val uuid: String
)
/**
* 显示排行榜
*/
fun ProxyPlayer.showLeaderboard(type: String) {
val ranks = getRankData(type)
// 构建标题
val title = Components.text("===== 排行榜 =====")
.bold()
.color(StandardColors.GOLD)
// 构建排行榜内容
val content = Components.empty()
for (data in ranks.take(10)) {
content.newLine()
// 排名颜色
val rankColor = when (data.rank) {
1 -> Color(255, 215, 0) // 金色
2 -> Color(192, 192, 192) // 银色
3 -> Color(205, 127, 50) // 铜色
else -> Color(255, 255, 255) // 白色
}
// 构建排行条目
content.append("${data.rank}. ", true)
.color(rankColor)
.append(data.playerName, false)
.bold()
.append(" - ${data.score} 分", false)
.unbold()
.hoverText(listOf(
"&e玩家: ${data.playerName}",
"&7分数: ${data.score}",
"&7排名: ${data.rank}",
"",
"&a点击查看详情"
))
.clickRunCommand("/profile ${data.playerName}")
}
// 发送消息
sendMessage(title)
sendMessage(content)
}
/**
* 获取排行数据
*/
private fun getRankData(type: String): List<RankData> {
// 实现从数据库或其他存储获取排行数据
return emptyList()
}
}
5. 实战示例:交互式 GUI 文本
package com.example.plugin.gui
import taboolib.common.platform.ProxyPlayer
import taboolib.module.chat.Components
import taboolib.module.chat.StandardColors
import taboolib.module.lang.asLangText
import taboolib.platform.util.sendMessage
object TextBasedGUI {
/**
* 显示分页文本 GUI
*/
fun ProxyPlayer.showPagedGUI(items: List<String>, page: Int = 1) {
val itemsPerPage = 5
val totalPages = (items.size + itemsPerPage - 1) / itemsPerPage
val startIndex = (page - 1) * itemsPerPage
val endIndex = minOf(startIndex + itemsPerPage, items.size)
// 构建标题
val header = Components.text("===== 列表 (${page}/${totalPages}) =====")
.bold()
.color(StandardColors.GOLD)
// 构建内容
val content = Components.empty()
for (i in startIndex until endIndex) {
content.newLine()
.append("${i + 1}. ${items[i]}", true)
.hoverText("&7点击选择")
.clickRunCommand("/select ${items[i]}")
}
// 构建翻页按钮
val navigation = Components.empty().newLine()
// 上一页按钮
if (page > 1) {
val prevText = asLangText("gui-prev-page", page - 1)
navigation.append(prevText, true)
.color(StandardColors.GREEN)
.clickRunCommand("/list ${page - 1}")
} else {
navigation.append("&8[上一页]", true)
}
navigation.append(" ", false)
// 下一页按钮
if (page < totalPages) {
val nextText = asLangText("gui-next-page", page + 1)
navigation.append(nextText, true)
.color(StandardColors.GREEN)
.clickRunCommand("/list ${page + 1}")
} else {
navigation.append("&8[下一页]", true)
}
navigation.append(" ", false)
// 返回按钮
val backText = asLangText("gui-back-button")
navigation.append(backText, true)
.color(StandardColors.RED)
.clickRunCommand("/menu")
// 发送所有内容
sendMessage(header)
sendMessage(content)
sendMessage(navigation)
}
}
性能优化建议
1. 缓存语言文本
对于频繁使用的文本,建议缓存:
object MessageCache {
private val cache = mutableMapOf<String, String>()
fun ProxyCommandSender.getCachedLang(node: String, vararg args: Any): String {
val locale = getLocale()
val key = "$locale:$node:${args.joinToString(",")}"
return cache.getOrPut(key) {
asLangText(node, *args)
}
}
fun clearCache() {
cache.clear()
}
}
2. 避免频繁构建
不要在循环中重复构建相同的 ComponentText:
// ❌ 错误示例
for (player in players) {
val message = Components.text("欢迎 $player").bold()
player.sendMessage(message)
}
// ✅ 正确示例
val baseMessage = Components.text("欢迎 ")
for (player in players) {
val message = Components.empty()
.append(baseMessage)
.append(player, false)
.bold()
player.sendMessage(message)
}
3. 使用 SimpleComponent 处理静态文本
静态消息优先使用 SimpleComponent(在 YAML 中定义),动态内容才使用 ComponentText:
# 静态消息 → 使用 SimpleComponent
static-welcome: '欢迎来到服务器'
// 动态消息 → 使用 ComponentText
val dynamicMessage = Components.text("排行榜")
for (rank in ranks) {
dynamicMessage.newLine().append("${rank.name} - ${rank.score}", true)
}
常见问题
Q1: 语言文件没有加载?
解决方案:
// 1. 检查语言代码是否注册
registerLanguage("zh_CN", "en_US")
// 2. 检查文件路径是否正确(默认为 lang/ 目录)
Language.path = "lang"
// 3. 手动重载语言文件
Language.reload()
// 4. 检查是否在正确的生命周期加载
@Awake(LifeCycle.ENABLE)
fun init() {
registerLanguage("zh_CN")
}
Q2: SimpleComponent 语法无效?
解决方案:
// 确保启用了 SimpleComponent
Language.enableSimpleComponent = true
Q3: 颜色代码不生效?
解决方案:
// 1. 检查是否启用颜色转换
val component = Components.text("&cRed", color = true) // 第二个参数必须为 true
// 2. 检查 ColorTransfer 是否已加载
if (!Language.textTransfer.contains(ColorTransfer)) {
Language.textTransfer += ColorTransfer
}
Q4: 如何支持 PlaceholderAPI?
解决方案:
import me.clip.placeholderapi.PlaceholderAPI
import taboolib.common.platform.ProxyPlayer
import taboolib.module.lang.asLangText
fun ProxyPlayer.asLangTextWithPAPI(node: String, vararg args: Any): String {
val rawText = asLangText(node, *args)
return if (hasPAPI()) {
PlaceholderAPI.setPlaceholders(getBukkitPlayer(), rawText)
} else {
rawText
}
}
Q5: 如何处理超长文本?
解决方案:
// 使用换行分段显示
val longText = Components.text("很长的文本内容...")
.newLine()
.append("第二段内容...")
.newLine()
.append("第三段内容...")
// 或使用多行 hover 提示
val summary = Components.text("查看详情")
.hoverText(listOf(
"详细信息第一行",
"详细信息第二行",
"详细信息第三行"
))
Q6: 配置文件中使用语言节点, 无法获取到对应语言节点?
解决方案:
# 请确保注释格式正确
# @lang:welcome
welcome: "欢迎!"
# 错误的格式(注意空格)
# @lang: welcome
welcome: "欢迎!"
调试技巧
1. 打印语言节点
// 查看已加载的语言文件
Language.languageFile.forEach { (code, file) ->
println("Language: $code")
file.nodes.keys.forEach { node ->
println(" - $node")
}
}
2. 测试 ComponentText 输出
val component = Components.text("&cTest").bold()
println(component.toRawMessage()) // 查看生成的 JSON
3. 验证玩家语言
println("Player locale: ${player.getLocale()}")
println("Locale file: ${player.getLocaleFile()?.code}")
总结
TabooLib 的语言文件系统提供了从简单到复杂的完整解决方案:
- 基础语言文件:使用 YAML 配置多语言支持
- 复合文本模式:支持 Title、ActionBar、Sound 等多种消息类型
- SimpleComponent:轻量级行内富文本语法,适合配置文件
- ComponentText:完整的代码构建 API,适合动态富文本生成
通过合理组合这些工具,你可以构建出专业的多语言插件系统。