初始化项目

This commit is contained in:
yhy
2026-06-01 07:06:35 +08:00
commit 93a583c781
16 changed files with 1376 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@@ -0,0 +1,57 @@
<<<<<<< HEAD
target/
out/
lib/
libs/
.vscode/
.codex/
.idea/
*.iml
*.class
*.log
logs/
*.db
*.sqlite
*.sqlite3
.DS_Store
Thumbs.db
=======
# ---> YHPlugin
# Maven
target/
# Build
out/
# IDE
.idea/
*.iml
.vscode/
# AI
.codex/
# Local libraries
lib/
libs/
# Tests
src/test/
# Logs
logs/
*.log
# Database
*.db
*.sqlite
*.sqlite3
# Java
*.class
# OS
.DS_Store
Thumbs.db
>>>>>>> 12c7b3f43c6113b170b9e83dedf75650d51b9040

459
README.md Normal file
View File

@@ -0,0 +1,459 @@
<<<<<<< HEAD
# 1.LangUtil 是什么?
LangUtil 是一个 Minecraft 1.12.2 Bukkit / Spigot 多语言管理插件。
它的作用不是替代业务插件,而是给其他插件提供统一的语言 API
- 每个插件维护自己的语言文件
- 玩家可以切换自己的语言
- 不同玩家可以看到不同语言
- 修改语言文件后可以不重启服务器直接重载
- 支持物品名称、物品 lore、普通消息、占位符和颜色代码
简单理解:
```yml
LangUtil = 公共语言管理器
其他插件 = 自己提供 lang 文件
玩家 = 自己选择 zh_CN / en_US 等语言
```
------
# 2.目录结构
## LangUtil 自己的数据
玩家语言选择会保存到:
```yml
plugins/LangUtil/player-language.yml
```
格式示例:
```yml
player-language:
550e8400-e29b-41d4-a716-446655440000: zh_CN
```
## 其他插件的语言文件
其他插件必须把语言文件放在自己的插件目录下:
```yml
plugins/OtherPlugin/lang/zh_CN.yml
plugins/OtherPlugin/lang/en_US.yml
```
注意:
```yml
语言文件不是放在 LangUtil 的 lang 目录
语言文件是放在调用 register 的插件自己的 lang 目录
```
------
# 3.其他插件如何注册?
其他插件需要依赖 LangUtil。
## plugin.yml
```yml
depend:
- LangUtil
```
## 插件入口
```java
import com.io.yaohun.langutil.LangUtil;
import org.bukkit.plugin.java.JavaPlugin;
public final class OtherPlugin extends JavaPlugin {
@Override
public void onEnable() {
LangUtil.register(this);
}
}
```
当执行:
```java
LangUtil.register(this);
```
LangUtil 会读取:
```yml
plugins/OtherPlugin/lang/*.yml
```
------
# 4.语言文件怎么写?
## zh_CN.yml
```yml
message:
reload: "&a语言文件已重载"
no-permission: "&c你没有权限使用该命令。"
item:
flame-sword:
name: "&c烈焰长剑"
lore:
- "&7伤害: &c{damage}"
- "&7品质: &6{quality}"
- ""
- "&e右键释放火焰"
```
## en_US.yml
```yml
message:
reload: "&aLanguage files reloaded"
no-permission: "&cYou do not have permission."
item:
flame-sword:
name: "&cFlame Sword"
lore:
- "&7Damage: &c{damage}"
- "&7Quality: &6{quality}"
- ""
- "&eRight-click to release fire"
```
文件名就是语言 ID
```yml
zh_CN.yml → zh_CN
en_US.yml → en_US
```
------
# 5.玩家命令
## 查看可用语言
```yml
/lang
```
玩家会看到当前已经加载的语言 ID。
## 切换语言
```yml
/lang zh_CN
/lang en_US
```
切换后会按玩家 UUID 保存。
玩家退出服务器再进入,仍然保持上次选择。
## 重载语言文件
```yml
/lang reload
```
修改语言文件后,不需要重启服务器,执行该命令即可重新读取所有已注册插件的语言文件。
控制台也可以执行:
```yml
lang reload
```
------
# 6.权限节点
```yml
langutil.use # 允许玩家使用 /lang 查看和切换语言
langutil.reload # 允许使用 /lang reload
```
推荐配置:
```yml
普通玩家: langutil.use
管理员: langutil.use + langutil.reload
```
------
# 7.API 调用
## 获取普通消息
```java
String message = LangUtil.get(player, this, "message.reload");
```
## 发送普通消息
```java
LangUtil.send(player, this, "message.reload");
```
## 发送消息并播放音效
```java
LangUtil.send(player, this, "message.reload", Sound.LEVEL_UP);
```
## 使用占位符
```java
Map<String, String> placeholders = new HashMap<String, String>();
placeholders.put("damage", "12");
placeholders.put("quality", "史诗");
String name = LangUtil.get(player, this, "item.flame-sword.name", placeholders);
```
语言文件:
```yml
item:
flame-sword:
name: "&c烈焰长剑"
```
结果:
```yml
烈焰长剑
```
------
# 8.物品 name 和 lore 怎么做?
物品 name 是字符串,可以用:
```java
LangUtil.get(player, this, "item.flame-sword.name", placeholders);
```
物品 lore 是 List可以用
```java
LangUtil.getList(player, this, "item.flame-sword.lore", placeholders);
```
完整示例:
```java
Map<String, String> placeholders = new HashMap<String, String>();
placeholders.put("damage", "12");
placeholders.put("quality", "史诗");
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(LangUtil.get(player, this, "item.flame-sword.name", placeholders));
meta.setLore(LangUtil.getList(player, this, "item.flame-sword.lore", placeholders));
item.setItemMeta(meta);
```
------
# 9.推荐封装方式
为了让业务代码更短,其他插件可以自己封装一个 `Lang` 工具类。
```java
public final class Lang {
private static JavaPlugin plugin;
private Lang() {
}
public static void init(JavaPlugin javaPlugin) {
plugin = javaPlugin;
LangUtil.register(javaPlugin);
}
public static void send(CommandSender sender, String path) {
LangUtil.send(sender, plugin, path);
}
public static String get(CommandSender sender, String path) {
return LangUtil.get(sender, plugin, path);
}
public static List<String> getList(CommandSender sender, String path) {
return LangUtil.getList(sender, plugin, path);
}
}
```
插件启动时:
```java
Lang.init(this);
```
业务代码:
```java
Lang.send(player, "message.reload");
```
------
# 10.重载机制说明
LangUtil 不会每次发送消息都读取本地 YAML。
语言文件读取时机:
```yml
插件启动 register 时
/lang reload 时
再次调用 register 时
```
平时调用:
```java
LangUtil.get(...)
LangUtil.getList(...)
LangUtil.send(...)
```
都是从内存缓存读取。
这样可以避免每次发消息都读磁盘,降低主线程压力。
------
# 11.异常与回退规则
## 玩家没有选择语言
默认使用:
```yml
zh_CN
```
## 玩家选择的语言文件不存在
回退:
```yml
zh_CN
```
## zh_CN 也不存在
返回 path 原文:
```java
LangUtil.get(player, this, "message.not-found");
```
返回:
```yml
message.not-found
```
## lore 不存在
返回单行 List
```yml
- item.xxx.lore
```
## YAML 格式错误
控制台会输出:
```yml
插件名
语言文件名
错误原因
```
不会因为单个语言文件错误导致服务器关闭。
------
# 12.实战使用流程
## 第一步:安装 LangUtil
把插件放入服务器:
```yml
plugins/LangUtil-1.2.0.jar
```
启动服务器。
## 第二步:业务插件依赖 LangUtil
```yml
depend:
- LangUtil
```
## 第三步:业务插件注册语言文件
```java
LangUtil.register(this);
```
## 第四步:创建语言文件
```yml
plugins/OtherPlugin/lang/zh_CN.yml
plugins/OtherPlugin/lang/en_US.yml
```
## 第五步:玩家切换语言
```yml
/lang zh_CN
/lang en_US
```
## 第六步:业务插件发送消息
```java
LangUtil.send(player, this, "message.reload");
```
## 第七步:修改语言后重载
```yml
/lang reload
```
------
# 13.注意事项
- 推荐始终使用带 `JavaPlugin plugin` 参数的 API。
- 不推荐使用 `LangUtil.get(String path)`,因为它无法稳定判断读取哪个插件的语言。
- 其他插件必须先调用 `LangUtil.register(this)`,否则读取不到自己的语言文件。
- 语言文件必须放在调用注册方法的插件自己的 `lang` 目录。
- 物品 lore 必须用 YAML List 格式编写。
- 修改语言文件后必须执行 `/lang reload` 才会进入缓存。
=======
# LangUtil
>>>>>>> 12c7b3f43c6113b170b9e83dedf75650d51b9040

57
pom.xml Normal file
View File

@@ -0,0 +1,57 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.douhun</groupId>
<artifactId>LangUtil</artifactId>
<version>1.2.1</version>
<packaging>jar</packaging>
<name>LangUtil</name>
<description>Minecraft 1.12.2 多插件语言管理插件</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>LangUtil-${project.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,129 @@
package com.io.yaohun.langutil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
final class LangCommand implements CommandExecutor, TabCompleter {
private static final String PERMISSION_USE = "langutil.use";
private static final String PERMISSION_RELOAD = "langutil.reload";
private final LanguageRegistry languageRegistry;
private final PlayerLanguageStore playerLanguageStore;
LangCommand(LanguageRegistry languageRegistry, PlayerLanguageStore playerLanguageStore) {
this.languageRegistry = languageRegistry;
this.playerLanguageStore = playerLanguageStore;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 1 && "reload".equalsIgnoreCase(args[0])) {
return handleReload(sender);
}
if (args.length == 0) {
return handleList(sender);
}
if (args.length == 1) {
return handleSwitch(sender, args[0]);
}
LangUtil.sendMessage(sender, "&c用法/lang [语言ID|reload]");
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length != 1) {
return Collections.emptyList();
}
List<String> suggestions = new ArrayList<String>();
String prefix = args[0].toLowerCase();
if ("reload".startsWith(prefix) && sender.hasPermission(PERMISSION_RELOAD)) {
suggestions.add("reload");
}
if (sender.hasPermission(PERMISSION_USE)) {
for (String languageId : this.languageRegistry.getAvailableLanguageIds()) {
if (languageId.toLowerCase().startsWith(prefix)) {
suggestions.add(languageId);
}
}
}
return suggestions;
}
private boolean handleList(CommandSender sender) {
if (!sender.hasPermission(PERMISSION_USE)) {
LangUtil.sendMessage(sender, "&c你没有权限使用该命令。");
return true;
}
List<String> languageIds = this.languageRegistry.getAvailableLanguageIds();
if (languageIds.isEmpty()) {
LangUtil.sendMessage(sender, "&e当前没有已加载的语言文件。");
return true;
}
LangUtil.sendMessage(sender, "&a当前可用语言&f" + join(languageIds));
return true;
}
private boolean handleSwitch(CommandSender sender, String languageId) {
if (!(sender instanceof Player)) {
LangUtil.sendMessage(sender, "&c该命令仅玩家可用。");
return true;
}
if (!sender.hasPermission(PERMISSION_USE)) {
LangUtil.sendMessage(sender, "&c你没有权限使用该命令。");
return true;
}
if (!this.languageRegistry.isLanguageAvailable(languageId)) {
LangUtil.sendMessage(sender, "&c该语言不存在。");
return true;
}
Player player = (Player) sender;
this.playerLanguageStore.setLanguage(player, languageId);
LangUtil.sendMessage(sender, "&a你的语言已切换为 " + languageId);
return true;
}
private boolean handleReload(CommandSender sender) {
if (!sender.hasPermission(PERMISSION_RELOAD)) {
LangUtil.sendMessage(sender, "&c你没有权限使用该命令。");
return true;
}
ReloadSummary summary = this.languageRegistry.reloadAll();
if (summary.hasFailures()) {
LangUtil.sendMessage(sender, "&c语言文件重载完成但存在失败项");
for (LanguageLoadFailure failure : summary.getFailures()) {
LangUtil.sendMessage(sender, "&c- " + failure.getPluginName() + "/" + failure.getFileName()
+ "" + failure.getReason());
}
return true;
}
LangUtil.sendMessage(sender, "&a语言文件已重载。已加载" + join(summary.getLoadedMessages()));
return true;
}
private String join(List<String> values) {
StringBuilder builder = new StringBuilder();
for (int index = 0; index < values.size(); index++) {
if (index > 0) {
builder.append(", ");
}
builder.append(values.get(index));
}
return builder.toString();
}
}

View File

@@ -0,0 +1,37 @@
package com.io.yaohun.langutil;
import java.util.List;
import java.util.Map;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;
final class LangService {
private final LanguageRegistry languageRegistry;
private final PlayerLanguageStore playerLanguageStore;
LangService(LanguageRegistry languageRegistry, PlayerLanguageStore playerLanguageStore) {
this.languageRegistry = languageRegistry;
this.playerLanguageStore = playerLanguageStore;
}
void register(JavaPlugin plugin) {
this.languageRegistry.register(plugin);
}
String getDefault(String path) {
return MessageFormatter.color(this.languageRegistry.getDefaultMessage(path));
}
String get(CommandSender sender, JavaPlugin plugin, String path, Map<String, String> placeholders) {
String languageId = this.playerLanguageStore.getLanguage(sender);
String rawMessage = this.languageRegistry.getMessage(plugin, languageId, path);
String replacedMessage = MessageFormatter.replace(rawMessage, placeholders);
return MessageFormatter.color(replacedMessage);
}
List<String> getList(CommandSender sender, JavaPlugin plugin, String path, Map<String, String> placeholders) {
String languageId = this.playerLanguageStore.getLanguage(sender);
List<String> rawMessages = this.languageRegistry.getMessageList(plugin, languageId, path);
return MessageFormatter.formatList(rawMessages, placeholders);
}
}

View File

@@ -0,0 +1,102 @@
package com.io.yaohun.langutil;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
public final class LangUtil {
private static volatile LangService service;
private LangUtil() {
}
static void initialize(LangService langService) {
service = langService;
}
static void shutdown() {
service = null;
}
public static void register(JavaPlugin plugin) {
LangService current = service;
if (current == null) {
throw new IllegalStateException("LangUtil 尚未加载,无法注册语言文件。");
}
current.register(plugin);
}
/**
* 不推荐使用:该方法缺少调用方插件上下文,无法稳定判断应读取哪个插件的语言文件。
*/
public static String get(String path) {
LangService current = service;
return current == null ? fallbackPath(path) : current.getDefault(path);
}
public static String get(CommandSender sender, JavaPlugin plugin, String path) {
return get(sender, plugin, path, Collections.<String, String>emptyMap());
}
public static String get(CommandSender sender, JavaPlugin plugin, String path, Map<String, String> placeholders) {
LangService current = service;
return current == null ? fallbackPath(path) : current.get(sender, plugin, path, placeholders);
}
public static List<String> getList(CommandSender sender, JavaPlugin plugin, String path) {
return getList(sender, plugin, path, Collections.<String, String>emptyMap());
}
public static List<String> getList(CommandSender sender, JavaPlugin plugin, String path, Map<String, String> placeholders) {
LangService current = service;
return current == null ? Collections.singletonList(fallbackPath(path)) : current.getList(sender, plugin, path, placeholders);
}
public static void sendMessage(CommandSender sender, String message) {
if (sender == null || message == null) {
return;
}
sender.sendMessage(MessageFormatter.color(message));
}
public static void sendMessage(CommandSender sender, String message, Sound sound) {
sendMessage(sender, message);
playSound(sender, sound);
}
public static void send(CommandSender sender, JavaPlugin plugin, String path) {
send(sender, plugin, path, Collections.<String, String>emptyMap());
}
public static void send(CommandSender sender, JavaPlugin plugin, String path, Sound sound) {
send(sender, plugin, path);
playSound(sender, sound);
}
public static void send(CommandSender sender, JavaPlugin plugin, String path, Map<String, String> placeholders) {
if (sender == null) {
return;
}
sender.sendMessage(get(sender, plugin, path, placeholders));
}
public static void send(CommandSender sender, JavaPlugin plugin, String path, Map<String, String> placeholders, Sound sound) {
send(sender, plugin, path, placeholders);
playSound(sender, sound);
}
private static void playSound(CommandSender sender, Sound sound) {
if (sender instanceof Player && sound != null) {
Player player = (Player) sender;
player.playSound(player.getLocation(), sound, 1.0F, 1.0F);
}
}
private static String fallbackPath(String path) {
return path == null ? "" : path;
}
}

View File

@@ -0,0 +1,42 @@
package com.io.yaohun.langutil;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
public final class LangUtilPlugin extends JavaPlugin {
private LanguageRegistry languageRegistry;
private PlayerLanguageStore playerLanguageStore;
private LangService langService;
@Override
public void onEnable() {
this.playerLanguageStore = new PlayerLanguageStore(this);
this.playerLanguageStore.load();
this.languageRegistry = new LanguageRegistry(this);
this.langService = new LangService(this.languageRegistry, this.playerLanguageStore);
LangUtil.initialize(this.langService);
LangCommand langCommand = new LangCommand(this.languageRegistry, this.playerLanguageStore);
PluginCommand command = getCommand("lang");
if (command != null) {
command.setExecutor(langCommand);
command.setTabCompleter(langCommand);
}
LangUtil.register(this);
getLogger().info("LangUtil 已加载。");
}
@Override
public void onDisable() {
if (this.playerLanguageStore != null) {
this.playerLanguageStore.save();
}
if (this.languageRegistry != null) {
this.languageRegistry.clear();
}
LangUtil.shutdown();
getLogger().info("LangUtil 已卸载。");
}
}

View File

@@ -0,0 +1,25 @@
package com.io.yaohun.langutil;
final class LanguageLoadFailure {
private final String pluginName;
private final String fileName;
private final String reason;
LanguageLoadFailure(String pluginName, String fileName, String reason) {
this.pluginName = pluginName;
this.fileName = fileName;
this.reason = reason;
}
String getPluginName() {
return this.pluginName;
}
String getFileName() {
return this.fileName;
}
String getReason() {
return this.reason;
}
}

View File

@@ -0,0 +1,28 @@
package com.io.yaohun.langutil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.bukkit.configuration.file.YamlConfiguration;
final class LanguageLoadResult {
private final Map<String, YamlConfiguration> languages;
private final List<LanguageLoadFailure> failures = new ArrayList<LanguageLoadFailure>();
LanguageLoadResult(Map<String, YamlConfiguration> languages) {
this.languages = languages;
}
Map<String, YamlConfiguration> getLanguages() {
return this.languages;
}
void addFailure(LanguageLoadFailure failure) {
this.failures.add(failure);
}
List<LanguageLoadFailure> getFailures() {
return Collections.unmodifiableList(this.failures);
}
}

View File

@@ -0,0 +1,70 @@
package com.io.yaohun.langutil;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
final class LanguageLoader {
private final JavaPlugin owner;
LanguageLoader(JavaPlugin owner) {
this.owner = owner;
}
LanguageLoadResult load(JavaPlugin plugin) {
Map<String, YamlConfiguration> languages = new LinkedHashMap<String, YamlConfiguration>();
LanguageLoadResult result = new LanguageLoadResult(languages);
File langFolder = new File(plugin.getDataFolder(), "lang");
if (!langFolder.exists() || !langFolder.isDirectory()) {
this.owner.getLogger().info("插件 " + plugin.getName() + " 未提供 lang 目录,已跳过语言加载。");
return result;
}
File[] files = langFolder.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().toLowerCase().endsWith(".yml");
}
});
if (files == null || files.length == 0) {
return result;
}
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File left, File right) {
return left.getName().compareToIgnoreCase(right.getName());
}
});
for (File file : files) {
String languageId = file.getName().substring(0, file.getName().length() - ".yml".length());
YamlConfiguration configuration = new YamlConfiguration();
try {
configuration.load(file);
languages.put(languageId, configuration);
} catch (IOException exception) {
recordFailure(result, plugin, file, exception);
} catch (InvalidConfigurationException exception) {
recordFailure(result, plugin, file, exception);
}
}
return result;
}
private void recordFailure(LanguageLoadResult result, JavaPlugin plugin, File file, Exception exception) {
String reason = exception.getMessage() == null ? exception.getClass().getSimpleName() : exception.getMessage();
LanguageLoadFailure failure = new LanguageLoadFailure(plugin.getName(), file.getName(), reason);
result.addFailure(failure);
this.owner.getLogger().warning("语言文件加载失败,插件=" + failure.getPluginName()
+ ",文件=" + failure.getFileName() + ",原因=" + failure.getReason());
}
}

View File

@@ -0,0 +1,180 @@
package com.io.yaohun.langutil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
final class LanguageRegistry {
static final String DEFAULT_LANGUAGE = "zh_CN";
private final JavaPlugin owner;
private final LanguageLoader languageLoader;
private final Map<String, RegisteredLanguageBundle> bundles = new LinkedHashMap<String, RegisteredLanguageBundle>();
LanguageRegistry(JavaPlugin owner) {
this.owner = owner;
this.languageLoader = new LanguageLoader(owner);
}
synchronized void register(JavaPlugin plugin) {
if (plugin == null) {
throw new IllegalArgumentException("注册语言文件时 plugin 不能为空。");
}
LanguageLoadResult result = this.languageLoader.load(plugin);
this.bundles.put(plugin.getName(), new RegisteredLanguageBundle(plugin, result.getLanguages()));
}
synchronized ReloadSummary reloadAll() {
ReloadSummary summary = new ReloadSummary();
List<JavaPlugin> plugins = new ArrayList<JavaPlugin>();
for (RegisteredLanguageBundle bundle : this.bundles.values()) {
plugins.add(bundle.getPlugin());
}
for (JavaPlugin plugin : plugins) {
LanguageLoadResult result = this.languageLoader.load(plugin);
this.bundles.put(plugin.getName(), new RegisteredLanguageBundle(plugin, result.getLanguages()));
summary.addLoaded(plugin.getName(), result.getLanguages().size());
summary.addFailures(result.getFailures());
}
return summary;
}
synchronized String getMessage(JavaPlugin plugin, String languageId, String path) {
if (path == null || path.length() == 0) {
return "";
}
RegisteredLanguageBundle bundle = findBundle(plugin);
if (bundle == null) {
return path;
}
String selectedMessage = bundle.getMessage(languageId, path);
if (selectedMessage != null) {
return selectedMessage;
}
String defaultMessage = bundle.getMessage(DEFAULT_LANGUAGE, path);
return defaultMessage == null ? path : defaultMessage;
}
synchronized List<String> getMessageList(JavaPlugin plugin, String languageId, String path) {
if (path == null || path.length() == 0) {
return Collections.emptyList();
}
RegisteredLanguageBundle bundle = findBundle(plugin);
if (bundle == null) {
return Collections.singletonList(path);
}
List<String> selectedMessages = bundle.getMessageList(languageId, path);
if (selectedMessages != null) {
return selectedMessages;
}
List<String> defaultMessages = bundle.getMessageList(DEFAULT_LANGUAGE, path);
return defaultMessages == null ? Collections.singletonList(path) : defaultMessages;
}
synchronized String getDefaultMessage(String path) {
if (path == null || path.length() == 0) {
return "";
}
RegisteredLanguageBundle ownerBundle = this.bundles.get(this.owner.getName());
if (ownerBundle != null) {
String message = ownerBundle.getMessage(DEFAULT_LANGUAGE, path);
if (message != null) {
return message;
}
}
for (RegisteredLanguageBundle bundle : this.bundles.values()) {
String message = bundle.getMessage(DEFAULT_LANGUAGE, path);
if (message != null) {
return message;
}
}
return path;
}
synchronized boolean isLanguageAvailable(String languageId) {
if (languageId == null || languageId.length() == 0) {
return false;
}
for (RegisteredLanguageBundle bundle : this.bundles.values()) {
if (bundle.hasLanguage(languageId)) {
return true;
}
}
return false;
}
synchronized List<String> getAvailableLanguageIds() {
Set<String> languageIds = new LinkedHashSet<String>();
for (RegisteredLanguageBundle bundle : this.bundles.values()) {
languageIds.addAll(bundle.getLanguageIds());
}
List<String> result = new ArrayList<String>(languageIds);
Collections.sort(result);
return result;
}
synchronized void clear() {
this.bundles.clear();
}
private RegisteredLanguageBundle findBundle(JavaPlugin plugin) {
if (plugin != null) {
RegisteredLanguageBundle bundle = this.bundles.get(plugin.getName());
if (bundle != null) {
return bundle;
}
}
return this.bundles.get(this.owner.getName());
}
private static final class RegisteredLanguageBundle {
private final JavaPlugin plugin;
private final Map<String, YamlConfiguration> languages;
private RegisteredLanguageBundle(JavaPlugin plugin, Map<String, YamlConfiguration> languages) {
this.plugin = plugin;
this.languages = new LinkedHashMap<String, YamlConfiguration>(languages);
}
private JavaPlugin getPlugin() {
return this.plugin;
}
private boolean hasLanguage(String languageId) {
return this.languages.containsKey(languageId);
}
private Set<String> getLanguageIds() {
return this.languages.keySet();
}
private String getMessage(String languageId, String path) {
YamlConfiguration configuration = this.languages.get(languageId);
return configuration == null ? null : configuration.getString(path);
}
private List<String> getMessageList(String languageId, String path) {
YamlConfiguration configuration = this.languages.get(languageId);
if (configuration == null || !configuration.contains(path)) {
return null;
}
if (configuration.isList(path)) {
return configuration.getStringList(path);
}
String singleLine = configuration.getString(path);
return singleLine == null ? null : Collections.singletonList(singleLine);
}
}
}

View File

@@ -0,0 +1,48 @@
package com.io.yaohun.langutil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bukkit.ChatColor;
final class MessageFormatter {
private MessageFormatter() {
}
static String replace(String message, Map<String, String> placeholders) {
if (message == null) {
return "";
}
if (placeholders == null || placeholders.isEmpty()) {
return message;
}
String result = message;
for (Map.Entry<String, String> entry : placeholders.entrySet()) {
if (entry.getKey() == null) {
continue;
}
String value = entry.getValue() == null ? "" : entry.getValue();
result = result.replace("{" + entry.getKey() + "}", value);
}
return result;
}
static String color(String message) {
if (message == null) {
return "";
}
return ChatColor.translateAlternateColorCodes('&', message);
}
static List<String> formatList(List<String> messages, Map<String, String> placeholders) {
List<String> result = new ArrayList<String>();
if (messages == null) {
return result;
}
for (String message : messages) {
result.add(color(replace(message, placeholders)));
}
return result;
}
}

View File

@@ -0,0 +1,82 @@
package com.io.yaohun.langutil;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
final class PlayerLanguageStore {
private static final String ROOT_PATH = "player-language";
private final JavaPlugin plugin;
private final File file;
private final Map<UUID, String> languages = new LinkedHashMap<UUID, String>();
PlayerLanguageStore(JavaPlugin plugin) {
this.plugin = plugin;
this.file = new File(plugin.getDataFolder(), "player-language.yml");
}
synchronized void load() {
this.languages.clear();
if (!this.file.exists()) {
return;
}
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(this.file);
ConfigurationSection section = configuration.getConfigurationSection(ROOT_PATH);
if (section == null) {
return;
}
for (String key : section.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
String languageId = section.getString(key);
if (languageId != null && languageId.length() > 0) {
this.languages.put(uuid, languageId);
}
} catch (IllegalArgumentException exception) {
this.plugin.getLogger().warning("玩家语言数据包含无效 UUID" + key);
}
}
}
synchronized void save() {
if (!this.plugin.getDataFolder().exists() && !this.plugin.getDataFolder().mkdirs()) {
this.plugin.getLogger().warning("无法创建插件数据目录:" + this.plugin.getDataFolder().getAbsolutePath());
return;
}
YamlConfiguration configuration = new YamlConfiguration();
for (Map.Entry<UUID, String> entry : this.languages.entrySet()) {
configuration.set(ROOT_PATH + "." + entry.getKey().toString(), entry.getValue());
}
try {
configuration.save(this.file);
} catch (IOException exception) {
this.plugin.getLogger().warning("保存玩家语言数据失败:" + exception.getMessage());
}
}
synchronized String getLanguage(CommandSender sender) {
if (sender instanceof Player) {
Player player = (Player) sender;
String languageId = this.languages.get(player.getUniqueId());
return languageId == null ? LanguageRegistry.DEFAULT_LANGUAGE : languageId;
}
return LanguageRegistry.DEFAULT_LANGUAGE;
}
synchronized void setLanguage(Player player, String languageId) {
this.languages.put(player.getUniqueId(), languageId);
save();
}
}

View File

@@ -0,0 +1,30 @@
package com.io.yaohun.langutil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
final class ReloadSummary {
private final List<String> loadedMessages = new ArrayList<String>();
private final List<LanguageLoadFailure> failures = new ArrayList<LanguageLoadFailure>();
void addLoaded(String pluginName, int languageCount) {
this.loadedMessages.add(pluginName + ": " + languageCount);
}
void addFailures(List<LanguageLoadFailure> newFailures) {
this.failures.addAll(newFailures);
}
List<String> getLoadedMessages() {
return Collections.unmodifiableList(this.loadedMessages);
}
List<LanguageLoadFailure> getFailures() {
return Collections.unmodifiableList(this.failures);
}
boolean hasFailures() {
return !this.failures.isEmpty();
}
}

View File

@@ -0,0 +1,14 @@
message:
reload: "&a语言文件已重载"
no-permission: "&c你没有权限使用该命令。"
command:
lang:
changed: "&a你的语言已切换为 {lang}"
item:
flame-sword:
name: "&c烈焰长剑"
lore:
- "&7伤害: &c{damage}"
- "&7品质: &6{quality}"
- ""
- "&e右键释放火焰"

View File

@@ -0,0 +1,16 @@
name: LangUtil
main: com.io.yaohun.langutil.LangUtilPlugin
version: ${project.version}
author: Douhun
description: Minecraft 1.12.2 多插件语言管理插件
commands:
lang:
description: 查看、切换或重载语言文件
usage: /lang [语言ID|reload]
permissions:
langutil.use:
description: 允许玩家查看和切换个人语言
default: true
langutil.reload:
description: 允许重载所有已注册插件的语言文件
default: op