mirror of
https://github.com/octopusYan/dayz-mod-translator.git
synced 2024-11-21 19:56:42 +08:00
first commit
This commit is contained in:
commit
08c80f20ae
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
.mvn/
|
||||
mvnw
|
||||
mvnw.cmd
|
||||
log/
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# DayZ Mod Translator
|
||||
|
||||
使用 JavaFx 编写的 DayZ 游戏mod 汉化GUI工具
|
||||
|
||||
## 截图
|
||||
|
||||
![Main window start](doc/img/screenshot01.png 'Main application window start')
|
||||
![Main window open file](doc/img/screenshot02.png 'Main window open file')
|
BIN
doc/img/screenshot01.png
Normal file
BIN
doc/img/screenshot01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
doc/img/screenshot02.png
Normal file
BIN
doc/img/screenshot02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
194
pom.xml
Normal file
194
pom.xml
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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>cn.octopusyan</groupId>
|
||||
<artifactId>dayz-mod-translator</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<name>DayzModTranslator</name>
|
||||
|
||||
<organization>
|
||||
<name>octopus_yan</name>
|
||||
<url>octopus_yan@foxmail.com</url>
|
||||
</organization>
|
||||
<inceptionYear>2024</inceptionYear>
|
||||
<description>DayZ 模组汉化工具</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<java.version>17</java.version>
|
||||
|
||||
<junit.version>5.10.0</junit.version>
|
||||
<javafx.version>17.0.6</javafx.version>
|
||||
<slf4j.version>2.0.12</slf4j.version>
|
||||
<logback.version>1.4.14</logback.version>
|
||||
<fastjson.version>2.0.46</fastjson.version>
|
||||
<hutool.version>5.8.25</hutool.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- javafx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- slf4j -->
|
||||
<!--<dependency>-->
|
||||
<!-- <groupId>org.slf4j</groupId>-->
|
||||
<!-- <artifactId>slf4j-simple</artifactId>-->
|
||||
<!-- <version>${slf4j.version}</version>-->
|
||||
<!--</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- common -->
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.14.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.15.1</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>1.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON -->
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<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.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<nonFilteredFileExtensions>
|
||||
<nonFilteredFileExtension>exe</nonFilteredFileExtension>
|
||||
<nonFilteredFileExtension>dll</nonFilteredFileExtension>
|
||||
</nonFilteredFileExtensions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>cn.octopusyan.dayzmodtranslator/cn.octopusyan.dayzmodtranslator.AppLuncher
|
||||
</mainClass>
|
||||
<launcher>launcher</launcher>
|
||||
<jlinkZipName>app</jlinkZipName>
|
||||
<jlinkImageName>app</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
<noHeaderFiles>true</noHeaderFiles>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>io.github.fvarrui</groupId>
|
||||
<artifactId>javapackager</artifactId>
|
||||
<version>1.7.5</version>
|
||||
<configuration>
|
||||
<bundleJre>true</bundleJre>
|
||||
<mainClass>cn.octopusyan.dayzmodtranslator.AppLuncher</mainClass>
|
||||
<generateInstaller>false</generateInstaller>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundling-for-windows</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>package</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<platform>windows</platform>
|
||||
<createZipball>true</createZipball>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,19 @@
|
||||
package cn.octopusyan.dayzmodtranslator;
|
||||
|
||||
/**
|
||||
* 启动类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class AppLuncher {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// try {
|
||||
// Runtime.getRuntime().exec("Taskkill /IM " + FrpManager.FRPC_CLIENT_FILE_NAME + " /f");
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
Application.launch(Application.class, args);
|
||||
}
|
||||
}
|
105
src/main/java/cn/octopusyan/dayzmodtranslator/Application.java
Normal file
105
src/main/java/cn/octopusyan/dayzmodtranslator/Application.java
Normal file
@ -0,0 +1,105 @@
|
||||
package cn.octopusyan.dayzmodtranslator;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpConfig;
|
||||
import cn.octopusyan.dayzmodtranslator.controller.MainController;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.CfgConvertUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.PBOUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadPoolManager;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.FxmlUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpUtil;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ProxySelector;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Application extends javafx.application.Application {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Application.class);
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
logger.info("application init ...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException {
|
||||
|
||||
logger.info("application start ...");
|
||||
|
||||
// bin转换 工具初始化
|
||||
CfgConvertUtil.init();
|
||||
|
||||
// PBO 工具初始化
|
||||
PBOUtil.init(CfgConvertUtil.getInstance());
|
||||
|
||||
// 客户端配置初始化
|
||||
CustomConfig.init();
|
||||
|
||||
// 初始化弹窗工具
|
||||
AlertUtil.initOwner(stage);
|
||||
|
||||
// http请求工具初始化
|
||||
HttpConfig httpConfig = new HttpConfig();
|
||||
if (CustomConfig.hasProxy()) {
|
||||
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(CustomConfig.proxyHost(), CustomConfig.proxyPort());
|
||||
httpConfig.setProxySelector(ProxySelector.of(unresolved));
|
||||
}
|
||||
httpConfig.setConnectTimeout(10);
|
||||
HttpUtil.init(httpConfig);
|
||||
|
||||
// TODO 全局异常处理
|
||||
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
|
||||
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
|
||||
|
||||
// 启动主界面
|
||||
try {
|
||||
FXMLLoader fxmlLoader = FxmlUtil.load("main-view");
|
||||
VBox root = fxmlLoader.load();//底层面板
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
|
||||
stage.setScene(scene);
|
||||
stage.setMinHeight(330);
|
||||
stage.setMinWidth(430);
|
||||
stage.setMaxWidth(Double.MAX_VALUE);
|
||||
stage.setMaxHeight(Double.MAX_VALUE);
|
||||
stage.setTitle(AppConstant.APP_TITLE + " v" + AppConstant.APP_VERSION);
|
||||
stage.show();
|
||||
|
||||
MainController controller = fxmlLoader.getController();
|
||||
controller.setApplication(this);
|
||||
} catch (Throwable t) {
|
||||
showErrorDialog(Thread.currentThread(), t);
|
||||
}
|
||||
|
||||
logger.info("application start over ...");
|
||||
}
|
||||
|
||||
private void showErrorDialog(Thread t, Throwable e) {
|
||||
logger.error("", e);
|
||||
AlertUtil.exceptionAlert(new Exception(e)).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
logger.info("application stop ...");
|
||||
|
||||
// 清除翻译任务
|
||||
TranslateUtil.getInstance().clear();
|
||||
// 停止所有线程
|
||||
ThreadPoolManager.getInstance().shutdown();
|
||||
// 保存应用数据
|
||||
CustomConfig.store();
|
||||
// 清理缓存
|
||||
PBOUtil.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
package cn.octopusyan.dayzmodtranslator.base;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
||||
import cn.octopusyan.dayzmodtranslator.util.FxmlUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.Loading;
|
||||
import cn.octopusyan.dayzmodtranslator.util.TooltipUtil;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* 通用视图控制器基类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public abstract class BaseController<P extends Pane> implements Initializable {
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private Application application;
|
||||
|
||||
private double xOffSet = 0, yOffSet = 0;
|
||||
|
||||
private volatile Loading loading;
|
||||
|
||||
protected TooltipUtil tooltipUtil;
|
||||
|
||||
public void jumpTo(BaseController<P> controller) throws IOException {
|
||||
FXMLLoader fxmlLoader = FxmlUtil.load(controller.getRootFxml());
|
||||
|
||||
Scene scene = getRootPanel().getScene();
|
||||
double oldHeight = getRootPanel().getPrefHeight();
|
||||
double oldWidth = getRootPanel().getPrefWidth();
|
||||
|
||||
Pane root = fxmlLoader.load();
|
||||
Stage stage = (Stage) scene.getWindow();
|
||||
// 窗口大小
|
||||
double newWidth = root.getPrefWidth();
|
||||
double newHeight = root.getPrefHeight();
|
||||
// 窗口位置
|
||||
double newX = stage.getX() - (newWidth - oldWidth) / 2;
|
||||
double newY = stage.getY() - (newHeight - oldHeight) / 2;
|
||||
scene.setRoot(root);
|
||||
stage.setX(newX < 0 ? 0 : newX);
|
||||
stage.setY(newY < 0 ? 0 : newY);
|
||||
stage.setWidth(newWidth);
|
||||
stage.setHeight(newHeight);
|
||||
|
||||
controller = fxmlLoader.getController();
|
||||
controller.setApplication(getApplication());
|
||||
}
|
||||
|
||||
protected void open(Class<? extends BaseController<?>> clazz, String title) {
|
||||
try {
|
||||
FXMLLoader load = FxmlUtil.load(clazz.getDeclaredConstructor().newInstance().getRootFxml());
|
||||
Parent root = load.load();
|
||||
Scene scene = new Scene(root);
|
||||
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
|
||||
Stage stage = new Stage();
|
||||
stage.setScene(scene);
|
||||
stage.setTitle(title);
|
||||
stage.initOwner(getWindow());
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.show();
|
||||
load.getController();
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||
// 全局窗口拖拽
|
||||
if (dragWindow()) {
|
||||
// 窗口拖拽
|
||||
getRootPanel().setOnMousePressed(event -> {
|
||||
xOffSet = event.getSceneX();
|
||||
yOffSet = event.getSceneY();
|
||||
});
|
||||
getRootPanel().setOnMouseDragged(event -> {
|
||||
Stage stage = (Stage) getWindow();
|
||||
stage.setX(event.getScreenX() - xOffSet);
|
||||
stage.setY(event.getScreenY() - yOffSet);
|
||||
});
|
||||
}
|
||||
|
||||
// 窗口初始化完成监听
|
||||
getRootPanel().sceneProperty().addListener((observable, oldValue, newValue) -> {
|
||||
newValue.windowProperty().addListener(new ChangeListener<Window>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
|
||||
//关闭窗口监听
|
||||
getWindow().setOnCloseRequest(windowEvent -> onDestroy());
|
||||
|
||||
// app 版本信息
|
||||
if (getAppVersionLabel() != null) getAppVersionLabel().setText("v" + AppConstant.APP_VERSION);
|
||||
|
||||
// 初始化数据
|
||||
initData();
|
||||
|
||||
// 初始化视图样式
|
||||
initViewStyle();
|
||||
|
||||
// 初始化视图事件
|
||||
initViewAction();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void showLoading() {
|
||||
showLoading(null);
|
||||
}
|
||||
|
||||
public void showLoading(String message) {
|
||||
if (loading == null) loading = new Loading((Stage) getWindow());
|
||||
|
||||
if (StringUtils.isNotEmpty(message)) loading.showMessage(message);
|
||||
|
||||
loading.show();
|
||||
}
|
||||
|
||||
public void setApplication(Application application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
public Application getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
public boolean isLoadShowing() {
|
||||
return loading != null && loading.showing();
|
||||
}
|
||||
|
||||
public void stopLoading() {
|
||||
if (isLoadShowing())
|
||||
loading.closeStage();
|
||||
}
|
||||
|
||||
protected TooltipUtil getTooltipUtil() {
|
||||
if (tooltipUtil == null) tooltipUtil = TooltipUtil.getInstance(getRootPanel());
|
||||
return tooltipUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口拖拽设置
|
||||
*
|
||||
* @return 是否启用
|
||||
*/
|
||||
public abstract boolean dragWindow();
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
*
|
||||
* @return 根布局对象
|
||||
*/
|
||||
public abstract P getRootPanel();
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
* <p> 搭配 <code>FxmlUtil.load</code> 使用
|
||||
*
|
||||
* @return 根布局对象
|
||||
* @see cn.octopusyan.dayzmodtranslator.util.FxmlUtil#load(String)
|
||||
*/
|
||||
public abstract String getRootFxml();
|
||||
|
||||
protected Window getWindow() {
|
||||
return getRootPanel().getScene().getWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* App版本信息标签
|
||||
*/
|
||||
public Label getAppVersionLabel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
public abstract void initData();
|
||||
|
||||
/**
|
||||
* 视图样式
|
||||
*/
|
||||
public abstract void initViewStyle();
|
||||
|
||||
/**
|
||||
* 视图事件
|
||||
*/
|
||||
public abstract void initViewAction();
|
||||
|
||||
/**
|
||||
* 关闭窗口
|
||||
*/
|
||||
public void onDestroy() {
|
||||
Stage stage = (Stage) getWindow();
|
||||
stage.hide();
|
||||
stage.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package cn.octopusyan.dayzmodtranslator.config;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.util.PropertiesUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 应用信息
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class AppConstant {
|
||||
public static final String APP_TITLE = PropertiesUtils.getInstance().getProperty("app.title");
|
||||
public static final String APP_NAME = PropertiesUtils.getInstance().getProperty("app.name");
|
||||
public static final String APP_VERSION = PropertiesUtils.getInstance().getProperty("app.version");
|
||||
public static final String DATA_DIR_PATH = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + APP_NAME;
|
||||
public static final String TMP_DIR_PATH = FileUtils.getTempDirectoryPath() + APP_NAME;
|
||||
public static final String CUSTOM_CONFIG_PATH = DATA_DIR_PATH + File.separator + "config.properties";
|
||||
public static final String BAK_FILE_PATH = AppConstant.TMP_DIR_PATH + File.separator + "bak";
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package cn.octopusyan.dayzmodtranslator.config;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 客户端设置
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class CustomConfig {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomConfig.class);
|
||||
private static final Properties properties = new Properties();
|
||||
public static final String PROXY_HOST_KEY = "proxy.host";
|
||||
public static final String PROXY_PORT_KEY = "proxy.port";
|
||||
public static final String TRANSLATE_SOURCE_KEY = "translate.source";
|
||||
public static final String TRANSLATE_SOURCE_APPID_KEY = "translate.{}.appid";
|
||||
public static final String TRANSLATE_SOURCE_APIKEY_KEY = "translate.{}.apikey";
|
||||
public static final String TRANSLATE_SOURCE_QPS_KEY = "translate.{}.qps";
|
||||
|
||||
public static void init() {
|
||||
File customConfigFile = new File(AppConstant.CUSTOM_CONFIG_PATH);
|
||||
try {
|
||||
if (!customConfigFile.exists()) {
|
||||
// 初始配置
|
||||
properties.put(TRANSLATE_SOURCE_KEY, TranslateSource.FREE_GOOGLE.getName());
|
||||
// 保存配置文件
|
||||
store();
|
||||
} else {
|
||||
properties.load(new FileInputStream(customConfigFile));
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
logger.error("读取配置文件失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否配置代理
|
||||
*/
|
||||
public static boolean hasProxy() {
|
||||
String host = proxyHost();
|
||||
Integer port = proxyPort();
|
||||
|
||||
return StringUtils.isNoneBlank(host) && null != port;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理地址
|
||||
*/
|
||||
public static String proxyHost() {
|
||||
return properties.getProperty(PROXY_HOST_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理地址
|
||||
*/
|
||||
public static void proxyHost(String host) {
|
||||
properties.setProperty(PROXY_HOST_KEY, host);
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理端口
|
||||
*/
|
||||
public static Integer proxyPort() {
|
||||
try {
|
||||
return Integer.parseInt(properties.getProperty(PROXY_PORT_KEY));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理端口
|
||||
*/
|
||||
public static void proxyPort(int port) {
|
||||
properties.setProperty(PROXY_PORT_KEY, String.valueOf(port));
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译源
|
||||
*/
|
||||
public static TranslateSource translateSource() {
|
||||
String name = properties.getProperty(TRANSLATE_SOURCE_KEY, TranslateSource.FREE_GOOGLE.getName());
|
||||
return TranslateSource.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译源
|
||||
*/
|
||||
public static void translateSource(TranslateSource source) {
|
||||
properties.setProperty(TRANSLATE_SOURCE_KEY, source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否配置接口认证
|
||||
*
|
||||
* @param source 翻译源
|
||||
*/
|
||||
public static boolean hasTranslateApiKey(TranslateSource source) {
|
||||
return StringUtils.isNoneBlank(translateSourceAppid(source))
|
||||
&& StringUtils.isNoneBlank(translateSourceApikey(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置翻译源appid
|
||||
*
|
||||
* @param source 翻译源
|
||||
* @param appid appid
|
||||
*/
|
||||
public static void translateSourceAppid(TranslateSource source, String appid) {
|
||||
properties.setProperty(getTranslateSourceAppidKey(source), appid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取翻译源appid
|
||||
*
|
||||
* @param source 翻译源
|
||||
* @return appid
|
||||
*/
|
||||
public static String translateSourceAppid(TranslateSource source) {
|
||||
return properties.getProperty(getTranslateSourceAppidKey(source));
|
||||
}
|
||||
|
||||
public static void translateSourceApikey(TranslateSource source, String apikey) {
|
||||
properties.setProperty(getTranslateSourceApikeyKey(source), apikey);
|
||||
}
|
||||
|
||||
public static String translateSourceApikey(TranslateSource source) {
|
||||
return properties.getProperty(getTranslateSourceApikeyKey(source));
|
||||
}
|
||||
|
||||
public static Integer translateSourceQps(TranslateSource source) {
|
||||
String qpsStr = properties.getProperty(getTranslateSourceQpsKey(source));
|
||||
return qpsStr == null ? source.getDefaultQps() : Integer.parseInt(qpsStr);
|
||||
}
|
||||
|
||||
public static void translateSourceQps(TranslateSource source, int qps) {
|
||||
properties.setProperty(getTranslateSourceQpsKey(source), String.valueOf(qps));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
*/
|
||||
public static void store() {
|
||||
// 生成配置文件
|
||||
try {
|
||||
properties.store(new PrintStream(AppConstant.CUSTOM_CONFIG_PATH), String.valueOf(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
logger.error("保存客户端配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTranslateSourceAppidKey(TranslateSource source) {
|
||||
return StringUtils.replace(TRANSLATE_SOURCE_APPID_KEY, "{}", source.getName());
|
||||
}
|
||||
|
||||
private static String getTranslateSourceApikeyKey(TranslateSource source) {
|
||||
return StringUtils.replace(TRANSLATE_SOURCE_APIKEY_KEY, "{}", source.getName());
|
||||
}
|
||||
|
||||
private static String getTranslateSourceQpsKey(TranslateSource source) {
|
||||
return StringUtils.replace(TRANSLATE_SOURCE_QPS_KEY, "{}", source.getName());
|
||||
}
|
||||
}
|
@ -0,0 +1,515 @@
|
||||
package cn.octopusyan.dayzmodtranslator.controller;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.base.BaseController;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.PBOUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.file.FileTreeItem;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordCsvItem;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordItem;
|
||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.ClipUtil;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 主控制器
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class MainController extends BaseController<VBox> {
|
||||
|
||||
public VBox root;
|
||||
public MenuItem openFileSetupBtn;
|
||||
public MenuItem translateSetupBtn;
|
||||
public MenuItem proxySetupBtn;
|
||||
public Label filePath;
|
||||
public StackPane fileBox;
|
||||
public VBox openFileBox;
|
||||
public Button openFile;
|
||||
public VBox dragFileBox;
|
||||
public Label dragFileLabel;
|
||||
public VBox loadFileBox;
|
||||
public Label loadFileLabel;
|
||||
public ProgressBar loadFileProgressBar;
|
||||
public TreeView<String> treeFileBox;
|
||||
public StackPane wordBox;
|
||||
public TableView<WordItem> wordTableBox;
|
||||
public VBox wordMsgBox;
|
||||
public Label wordMsgLabel;
|
||||
public ProgressBar loadWordProgressBar;
|
||||
public Button translateWordBtn;
|
||||
public Button packBtn;
|
||||
private final PBOUtil pboUtil = PBOUtil.getInstance();
|
||||
private final TranslateUtil translateUtil = TranslateUtil.getInstance();
|
||||
|
||||
/**
|
||||
* 翻译标志 用于停止翻译
|
||||
*/
|
||||
private AtomicBoolean transTag;
|
||||
/**
|
||||
* 已翻译文本下标缓存
|
||||
*/
|
||||
private Set<Integer> transNum;
|
||||
|
||||
@Override
|
||||
public boolean dragWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VBox getRootPanel() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootFxml() {
|
||||
return "main-view";
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
@Override
|
||||
public void initData() {
|
||||
|
||||
// 解包监听
|
||||
pboUtil.setOnUnpackListener(new PBOUtil.OnUnpackListener() {
|
||||
@Override
|
||||
public void onStart() {
|
||||
refreshWordBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnpackSuccess(String unpackDirPath) {
|
||||
loadFileLabel.textProperty().setValue("加载完成,正在获取文件目录");
|
||||
// 展示解包文件内容
|
||||
logger.info("正在获取文件目录。。");
|
||||
showDirectory(new File(unpackDirPath));
|
||||
// 展示可翻译语句
|
||||
logger.info("正在查询待翻译文本目录。。");
|
||||
showTranslateWord();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnpackError(String msg) {
|
||||
loadFileLabel.textProperty().setValue("打开文件失败");
|
||||
logger.info("打开文件失败: \n" + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnpackOver() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// 打包监听
|
||||
pboUtil.setOnPackListener(new PBOUtil.OnPackListener() {
|
||||
@Override
|
||||
public void onStart() {
|
||||
showLoading("正在打包pbo文件");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long current, long all) {
|
||||
showLoading(String.format("正在打包pbo文件(%d / %d)", current, all));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackSuccess(File packFile) {
|
||||
// 选择文件保存地址
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PBO files (*.pbo)", "*.pbo");
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
File file = fileChooser.showSaveDialog(getWindow());
|
||||
if (file == null)
|
||||
return;
|
||||
if (file.exists()) {
|
||||
//文件已存在,则删除覆盖文件
|
||||
FileUtils.deleteQuietly(file);
|
||||
}
|
||||
String exportFilePath = file.getAbsolutePath();
|
||||
logger.info("导出文件的路径 =>" + exportFilePath);
|
||||
|
||||
try {
|
||||
FileUtils.copyFile(packFile, file);
|
||||
} catch (IOException e) {
|
||||
logger.error("保存文件失败!", e);
|
||||
|
||||
Platform.runLater(() -> AlertUtil.exception(e).content("保存文件失败!").show());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackError(String msg) {
|
||||
AlertUtil.error("保存文件失败!").show();
|
||||
logger.info("保存文件失败: \n" + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackOver() {
|
||||
stopLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// 获取待翻译文字
|
||||
pboUtil.setOnFindTransWordListener((words, isOver) -> {
|
||||
loadWordProgressBar.setVisible(false);
|
||||
|
||||
if (words == null || words.isEmpty()) {
|
||||
if (isOver) {
|
||||
wordMsgLabel.textProperty().set("未找到待翻译文本");
|
||||
}
|
||||
} else {
|
||||
// 展示翻译按钮
|
||||
translateWordBtn.setVisible(true);
|
||||
// 展示打包按钮
|
||||
packBtn.setVisible(true);
|
||||
|
||||
// 绑定TableView
|
||||
boolean isCsvItem = (words.get(0) instanceof WordCsvItem);
|
||||
bindWordTable(words, isCsvItem);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图样式
|
||||
*/
|
||||
@Override
|
||||
public void initViewStyle() {
|
||||
wordMsgLabel.textProperty().setValue("请打开PBO文件");
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图事件
|
||||
*/
|
||||
@Override
|
||||
public void initViewAction() {
|
||||
// 翻译设置
|
||||
translateSetupBtn.setOnAction(event -> open(SetupTranslateController.class, "翻译源设置"));
|
||||
// 代理设置
|
||||
proxySetupBtn.setOnAction(event -> open(SetupProxyController.class, "代理设置"));
|
||||
|
||||
// 选择pbo文件
|
||||
EventHandler<ActionEvent> selectPboFileAction = actionEvent -> {
|
||||
// 文件选择器
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PBO files (*.pbo)", "*.pbo");
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
selectFile(fileChooser.showOpenDialog(getWindow()));
|
||||
};
|
||||
openFileSetupBtn.setOnAction(selectPboFileAction);
|
||||
openFile.setOnAction(selectPboFileAction);
|
||||
|
||||
// 拖拽效果 start ---------------------
|
||||
fileBox.setOnDragEntered(dragEvent -> {
|
||||
Dragboard dragboard = dragEvent.getDragboard();
|
||||
if (dragboard.hasFiles() && isPboFile(dragboard.getFiles().get(0))) {
|
||||
disableBox();
|
||||
dragFileBox.setVisible(true);
|
||||
}
|
||||
});
|
||||
fileBox.setOnDragExited(dragEvent -> {
|
||||
if (!loadFileBox.isVisible()) {
|
||||
disableBox();
|
||||
openFileBox.setVisible(true);
|
||||
}
|
||||
});
|
||||
fileBox.setOnDragOver(dragEvent -> {
|
||||
Dragboard dragboard = dragEvent.getDragboard();
|
||||
if (dragEvent.getGestureSource() != fileBox && dragboard.hasFiles()) {
|
||||
/* allow for both copying and moving, whatever user chooses */
|
||||
dragEvent.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
}
|
||||
dragEvent.consume();
|
||||
});
|
||||
fileBox.setOnDragDropped(dragEvent -> {
|
||||
disableBox();
|
||||
openFileBox.setVisible(true);
|
||||
|
||||
Dragboard db = dragEvent.getDragboard();
|
||||
boolean success = false;
|
||||
File file = db.getFiles().get(0);
|
||||
if (db.hasFiles() && isPboFile(file)) {
|
||||
selectFile(file);
|
||||
success = true;
|
||||
}
|
||||
/* 让源知道字符串是否已成功传输和使用 */
|
||||
dragEvent.setDropCompleted(success);
|
||||
|
||||
dragEvent.consume();
|
||||
});
|
||||
// 拖拽效果 end ---------------------
|
||||
|
||||
// 翻译按钮
|
||||
translateWordBtn.setOnMouseClicked(mouseEvent -> {
|
||||
|
||||
// 是否初次翻译
|
||||
if (transTag == null) {
|
||||
transNum = new HashSet<>();
|
||||
transTag = new AtomicBoolean(true);
|
||||
// 开始翻译
|
||||
startTranslate();
|
||||
} else {
|
||||
// 获取翻译列表
|
||||
ObservableList<WordItem> items = wordTableBox.getItems();
|
||||
// 未获取到翻译列表 或 翻译完成 则不做处理
|
||||
if (items == null || items.isEmpty() || transNum.size() == items.size())
|
||||
return;
|
||||
|
||||
// 设置翻译标识
|
||||
transTag.set(!transTag.get());
|
||||
|
||||
if (Boolean.FALSE.equals(transTag.get())) {
|
||||
stopTranslate();
|
||||
} else {
|
||||
startTranslate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
// 打包按钮
|
||||
packBtn.setOnAction(event -> pboUtil.pack(wordTableBox.getItems()));
|
||||
|
||||
// 复制文本
|
||||
getRootPanel().getScene().getAccelerators()
|
||||
.put(new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TablePosition tablePosition = wordTableBox.getSelectionModel().getSelectedCells().get(0);
|
||||
Object cellData = tablePosition.getTableColumn().getCellData(tablePosition.getRow());
|
||||
ClipUtil.setClip(String.valueOf(cellData));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始翻译
|
||||
*/
|
||||
private void startTranslate() {
|
||||
// 获取翻译列表
|
||||
ObservableList<WordItem> items = wordTableBox.getItems();
|
||||
if (items == null || items.isEmpty()) return;
|
||||
|
||||
// 开始/继续 翻译
|
||||
String label = translateWordBtn.getText().replaceAll("已暂停|一键翻译", "正在翻译");
|
||||
translateWordBtn.textProperty().setValue(label);
|
||||
|
||||
// 禁用打包按钮
|
||||
packBtn.setDisable(true);
|
||||
|
||||
boolean isCsvItem = (items.get(0) instanceof WordCsvItem);
|
||||
// 循环提交翻译任务
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
// 跳过已翻译文本
|
||||
if (transNum.contains(i)) continue;
|
||||
|
||||
WordItem item = items.get(i);
|
||||
|
||||
// 提交翻译任务
|
||||
int finalI = i;
|
||||
translateUtil.translate(finalI, item.getOriginal(), new TranslateUtil.OnTranslateListener() {
|
||||
@Override
|
||||
public void onTranslate(String result) {
|
||||
// 防止多线程执行时停止不及时
|
||||
if (Boolean.FALSE.equals(transTag.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 含有中文则不翻译
|
||||
if (!containsChinese(item.getChinese()))
|
||||
item.setChinese(result);
|
||||
|
||||
// 设置简中文本
|
||||
if (isCsvItem) {
|
||||
WordCsvItem csvItem = ((WordCsvItem) item);
|
||||
if (!containsChinese(csvItem.getChineseSimp()))
|
||||
csvItem.setChineseSimp(result);
|
||||
}
|
||||
|
||||
// 设置翻译进度
|
||||
transNum.add(finalI);
|
||||
String label;
|
||||
if (transNum.size() >= items.size()) {
|
||||
label = "翻译完成(" + items.size() + ")";
|
||||
transTag.set(false);
|
||||
// 启用打包按钮
|
||||
packBtn.setDisable(false);
|
||||
} else {
|
||||
label = "正在翻译(" + transNum.size() + "/" + items.size() + ")";
|
||||
}
|
||||
translateWordBtn.textProperty().setValue(label);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止翻译
|
||||
*/
|
||||
private void stopTranslate() {
|
||||
// 清除未完成的翻译任务
|
||||
translateUtil.clear();
|
||||
// 设置翻译状态
|
||||
String label = translateWordBtn.getText().replace("正在翻译", "已暂停");
|
||||
translateWordBtn.textProperty().setValue(label);
|
||||
// 启用打包按钮
|
||||
packBtn.setDisable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择待汉化pbo文件
|
||||
* <p>TODO 多文件汉化
|
||||
*
|
||||
* @param file 待汉化文件
|
||||
*/
|
||||
private void selectFile(File file) {
|
||||
if (file == null || !file.exists()) return;
|
||||
|
||||
filePath.textProperty().set(file.getName());
|
||||
|
||||
// 重置文件界面
|
||||
disableBox();
|
||||
loadFileBox.setVisible(true);
|
||||
// 重置翻译文本状态
|
||||
wordBox.getChildren().remove(wordTableBox);
|
||||
wordTableBox = null;
|
||||
|
||||
loadFileLabel.textProperty().setValue("正在加载模组文件");
|
||||
pboUtil.unpack(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示文件夹内容
|
||||
*
|
||||
* @param file 根目录
|
||||
*/
|
||||
private void showDirectory(File file) {
|
||||
if (file == null || !file.exists() || !file.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
disableBox();
|
||||
treeFileBox.setVisible(true);
|
||||
|
||||
// 加载pbo文件目录
|
||||
FileTreeItem fileTreeItem = new FileTreeItem(file, File::listFiles);
|
||||
treeFileBox.setRoot(fileTreeItem);
|
||||
treeFileBox.setShowRoot(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示待翻译语句
|
||||
*/
|
||||
private void showTranslateWord() {
|
||||
wordMsgLabel.textProperty().setValue("正在获取可翻译文本");
|
||||
loadWordProgressBar.setVisible(true);
|
||||
|
||||
pboUtil.startFindWord();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定表格数据
|
||||
*
|
||||
* @param words 单词列表
|
||||
* @param isCsvItem 是否csv
|
||||
*/
|
||||
private void bindWordTable(List<WordItem> words, boolean isCsvItem) {
|
||||
if (wordTableBox == null) {
|
||||
wordTableBox = new TableView<>();
|
||||
wordBox.getChildren().add(wordTableBox);
|
||||
// 可编辑
|
||||
wordTableBox.setEditable(true);
|
||||
// 单元格选择模式而不是行选择
|
||||
wordTableBox.getSelectionModel().setCellSelectionEnabled(true);
|
||||
// 不允许选择多个单元格
|
||||
wordTableBox.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
// 鼠标事件清空
|
||||
wordTableBox.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if (event.isControlDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wordTableBox.getEditingCell() == null) {
|
||||
wordTableBox.getSelectionModel().clearSelection();
|
||||
}
|
||||
});
|
||||
|
||||
// 创建列
|
||||
wordTableBox.getColumns().add(createColumn("原始文本", WordItem::originalProperty));
|
||||
wordTableBox.getColumns().add(createColumn("中文", WordItem::chineseProperty));
|
||||
|
||||
if (isCsvItem) {
|
||||
wordTableBox.getColumns().add(createColumn("简体中文", WordCsvItem::chineseSimpProperty));
|
||||
}
|
||||
}
|
||||
|
||||
// 添加表数据
|
||||
wordTableBox.getItems().addAll(words);
|
||||
}
|
||||
|
||||
private <T extends WordItem> TableColumn<WordItem, String> createColumn(String colName, Function<T, StringProperty> colField) {
|
||||
TableColumn<WordItem, String> tableColumn = new TableColumn<>(colName);
|
||||
tableColumn.setCellValueFactory(features -> colField.apply((T) features.getValue()));
|
||||
tableColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
tableColumn.setPrefWidth(150);
|
||||
tableColumn.setSortable(false);
|
||||
tableColumn.setEditable(!"原始文本".equals(colName));
|
||||
return tableColumn;
|
||||
}
|
||||
|
||||
private void disableBox() {
|
||||
openFileBox.setVisible(false);
|
||||
dragFileBox.setVisible(false);
|
||||
loadFileBox.setVisible(false);
|
||||
treeFileBox.setVisible(false);
|
||||
}
|
||||
|
||||
private void refreshWordBox() {
|
||||
if (wordTableBox != null) {
|
||||
wordBox.getChildren().remove(wordTableBox);
|
||||
wordTableBox = null;
|
||||
}
|
||||
wordMsgLabel.textProperty().setValue("请打开pbo文件");
|
||||
loadWordProgressBar.setVisible(false);
|
||||
translateWordBtn.textProperty().setValue("一键翻译");
|
||||
translateWordBtn.setVisible(false);
|
||||
packBtn.setVisible(false);
|
||||
}
|
||||
|
||||
private boolean isPboFile(File file) {
|
||||
if (file == null) return false;
|
||||
return Pattern.compile(".*(.pbo)$").matcher(file.getName()).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否含有中文
|
||||
*
|
||||
* @param str 需要判断的字符串
|
||||
* @return 是否含有中文
|
||||
*/
|
||||
private boolean containsChinese(String str) {
|
||||
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package cn.octopusyan.dayzmodtranslator.controller;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.base.BaseController;
|
||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.FxmlUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpUtil;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* 应用配置
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class SetupProxyController extends BaseController<StackPane> {
|
||||
public StackPane root;
|
||||
public TextField hostField;
|
||||
public TextField portField;
|
||||
public TextField testPath;
|
||||
public static final String PROXY_ERROR = "ProxyError";
|
||||
|
||||
/**
|
||||
* 窗口拖拽设置
|
||||
*
|
||||
* @return 是否启用
|
||||
*/
|
||||
@Override
|
||||
public boolean dragWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
*
|
||||
* @return 根布局对象
|
||||
*/
|
||||
@Override
|
||||
public StackPane getRootPanel() {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
* <p> 搭配 <code>FxmlUtil.load</code> 使用
|
||||
*
|
||||
* @return 根布局对象
|
||||
* @see FxmlUtil#load(String)
|
||||
*/
|
||||
@Override
|
||||
public String getRootFxml() {
|
||||
return "proxy-view";
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
@Override
|
||||
public void initData() {
|
||||
// 是否已有代理配置
|
||||
if (CustomConfig.hasProxy()) {
|
||||
hostField.textProperty().setValue(CustomConfig.proxyHost());
|
||||
portField.textProperty().setValue(String.valueOf(CustomConfig.proxyPort()));
|
||||
}
|
||||
|
||||
// 默认测试地址
|
||||
testPath.textProperty().setValue("https://translate.googleapis.com");
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图样式
|
||||
*/
|
||||
@Override
|
||||
public void initViewStyle() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图事件
|
||||
*/
|
||||
@Override
|
||||
public void initViewAction() {
|
||||
|
||||
}
|
||||
|
||||
private String getHost() {
|
||||
String text = hostField.getText();
|
||||
if (StringUtils.isBlank(text)) {
|
||||
throw new RuntimeException(PROXY_ERROR);
|
||||
}
|
||||
try {
|
||||
URI.create(text);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(PROXY_ERROR);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private int getPort() {
|
||||
String text = portField.getText();
|
||||
if (StringUtils.isBlank(text)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
boolean creatable = NumberUtils.isCreatable(text);
|
||||
if (!creatable) {
|
||||
throw new RuntimeException(PROXY_ERROR);
|
||||
}
|
||||
|
||||
return Integer.parseInt(text);
|
||||
}
|
||||
|
||||
private String getTestPath() {
|
||||
String text = testPath.getText();
|
||||
if (StringUtils.isBlank(text)) {
|
||||
throw new RuntimeException(PROXY_ERROR);
|
||||
}
|
||||
return (text.startsWith("http") ? "" : "http://") + text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试代理有效性
|
||||
*/
|
||||
public void test() {
|
||||
HttpUtil.getInstance().clearProxy();
|
||||
try {
|
||||
String resp = HttpUtil.getInstance().proxy(getHost(), getPort())
|
||||
.get(getTestPath(), null, null);
|
||||
AlertUtil.info("成功").show();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.error("代理访问失败", e);
|
||||
AlertUtil.error("失败!").show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存代理配置
|
||||
*/
|
||||
public void save() {
|
||||
CustomConfig.proxyHost(getHost());
|
||||
CustomConfig.proxyPort(getPort());
|
||||
|
||||
CustomConfig.store();
|
||||
|
||||
onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
public void close() {
|
||||
HttpUtil.getInstance().clearProxy();
|
||||
|
||||
onDestroy();
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package cn.octopusyan.dayzmodtranslator.controller;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.base.BaseController;
|
||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 翻译设置控制器
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class SetupTranslateController extends BaseController<StackPane> {
|
||||
public StackPane root;
|
||||
public ComboBox<TranslateSource> translateSourceCombo;
|
||||
public TextField qps;
|
||||
public VBox appidBox;
|
||||
public TextField appid;
|
||||
public VBox apikeyBox;
|
||||
public TextField apikey;
|
||||
|
||||
@Override
|
||||
public boolean dragWindow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackPane getRootPanel() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRootFxml() {
|
||||
return "translate-view";
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
@Override
|
||||
public void initData() {
|
||||
// 翻译源
|
||||
for (TranslateSource value : TranslateSource.values()) {
|
||||
ObservableList<TranslateSource> items = translateSourceCombo.getItems();
|
||||
items.addAll(value);
|
||||
}
|
||||
translateSourceCombo.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(TranslateSource object) {
|
||||
if (object == null) return null;
|
||||
|
||||
return object.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TranslateSource fromString(String string) {
|
||||
return TranslateSource.getByLabel(string);
|
||||
}
|
||||
});
|
||||
translateSourceCombo.getSelectionModel()
|
||||
.selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
boolean needApiKey = newValue.needApiKey();
|
||||
appidBox.setVisible(needApiKey);
|
||||
apikeyBox.setVisible(needApiKey);
|
||||
if (needApiKey) {
|
||||
appid.textProperty().setValue(CustomConfig.translateSourceAppid(newValue));
|
||||
apikey.textProperty().setValue(CustomConfig.translateSourceApikey(newValue));
|
||||
|
||||
}
|
||||
|
||||
qps.textProperty().setValue(String.valueOf(CustomConfig.translateSourceQps(newValue)));
|
||||
});
|
||||
|
||||
// 当前翻译源
|
||||
translateSourceCombo.getSelectionModel().select(CustomConfig.translateSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图样式
|
||||
*/
|
||||
@Override
|
||||
public void initViewStyle() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图事件
|
||||
*/
|
||||
@Override
|
||||
public void initViewAction() {
|
||||
qps.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue.matches("\\d*")) {
|
||||
qps.setText(oldValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void save() {
|
||||
TranslateSource source = translateSourceCombo.getValue();
|
||||
String apikey = this.apikey.getText();
|
||||
String appid = this.appid.getText();
|
||||
int qps = Integer.parseInt(this.qps.getText());
|
||||
|
||||
CustomConfig.translateSource(source);
|
||||
if (source.needApiKey()) {
|
||||
if (StringUtils.isBlank(apikey) || StringUtils.isBlank(appid)) {
|
||||
AlertUtil.error("认证信息不能为空");
|
||||
}
|
||||
|
||||
CustomConfig.translateSourceApikey(source, apikey);
|
||||
CustomConfig.translateSourceAppid(source, appid);
|
||||
CustomConfig.translateSourceQps(source, qps);
|
||||
}
|
||||
// 保存到文件
|
||||
CustomConfig.store();
|
||||
// 退出
|
||||
onDestroy();
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
||||
import cn.octopusyan.dayzmodtranslator.util.FileUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.ProcessesUtil;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Cfg文件转换工具类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class CfgConvertUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CfgConvertUtil.class);
|
||||
private static CfgConvertUtil util;
|
||||
private static final String CfgConvert_DIR_PATH = AppConstant.DATA_DIR_PATH + File.separator + "CfgConvert";
|
||||
private static final File CfgConvert_DIR = new File(CfgConvert_DIR_PATH);
|
||||
private static final String CfgConvert_FILE_PATH = CfgConvert_DIR_PATH + File.separator + "CfgConvert.exe";
|
||||
private static final File CfgConvert_FILE = new File(CfgConvert_FILE_PATH);
|
||||
private static final String COMMAND = CfgConvert_FILE_PATH + " %s -dst %s %s";
|
||||
|
||||
private CfgConvertUtil() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (util == null) {
|
||||
util = new CfgConvertUtil();
|
||||
}
|
||||
// 检查pbo解析文件
|
||||
util.checkCfgConvert();
|
||||
}
|
||||
|
||||
public static synchronized CfgConvertUtil getInstance() {
|
||||
if (util == null)
|
||||
throw new RuntimeException("are you ready ?");
|
||||
return util;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Cfg转换文件
|
||||
*/
|
||||
private void checkCfgConvert() {
|
||||
if (!CfgConvert_FILE.exists()) initCfgConvert();
|
||||
}
|
||||
|
||||
private void initCfgConvert() {
|
||||
try {
|
||||
FileUtils.forceMkdir(CfgConvert_DIR);
|
||||
String cfgConvertFileName = "CfgConvert.exe";
|
||||
FileUtil.copyFile(Objects.requireNonNull(CfgConvertUtil.class.getResourceAsStream("/static/CfgConvert/" + cfgConvertFileName)), new File(CfgConvert_DIR_PATH + File.separator + cfgConvertFileName));
|
||||
} catch (IOException e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void toTxt(File binFile, String outPath, OnToTxtListener onToTxtListener) {
|
||||
String fileName = binFile.getAbsolutePath();
|
||||
ProcessesUtil.exec(toTxtCommand(binFile, outPath), new ProcessesUtil.OnExecuteListener() {
|
||||
@Override
|
||||
public void onExecute(String msg) {
|
||||
logger.info(fileName + " : " + msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteSuccess(int exitValue) {
|
||||
logger.info(fileName + " : to txt success");
|
||||
String outFilePath = outFilePath(binFile, outPath, ".cpp");
|
||||
if (onToTxtListener != null) {
|
||||
onToTxtListener.onToTxtSuccess(outFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteError(Exception e) {
|
||||
logger.error(fileName + " : to txt error", e);
|
||||
if (onToTxtListener != null) {
|
||||
onToTxtListener.onToTxtError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteOver() {
|
||||
logger.info(fileName + " : to txt end...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void toBin(File cppFile) {
|
||||
ProcessesUtil.exec(toBinCommand(cppFile, cppFile.getParentFile().getAbsolutePath()), new ProcessesUtil.OnExecuteListener() {
|
||||
@Override
|
||||
public void onExecute(String msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteSuccess(int exitValue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteError(Exception e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteOver() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String toBinCommand(File cppFile, String outPath) {
|
||||
String outFilePath = outFilePath(cppFile, outPath, ".bin");
|
||||
return String.format(COMMAND, "-bin", outFilePath, cppFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private String toTxtCommand(File binFile, String outPath) {
|
||||
String outFilePath = outFilePath(binFile, outPath, ".cpp");
|
||||
return String.format(COMMAND, "-txt", outFilePath, binFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private String outFilePath(File file, String outPath, String suffix) {
|
||||
return outPath + File.separator + FileUtil.mainName(file) + suffix;
|
||||
}
|
||||
|
||||
public interface OnToTxtListener {
|
||||
void onToTxtSuccess(String txtFilePath);
|
||||
|
||||
void onToTxtError(Exception e);
|
||||
}
|
||||
}
|
@ -0,0 +1,628 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadPoolManager;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordCsvItem;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordItem;
|
||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.FileUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.util.ProcessesUtil;
|
||||
import javafx.application.Platform;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.LineIterator;
|
||||
import org.apache.commons.io.filefilter.NameFileFilter;
|
||||
import org.apache.commons.io.filefilter.TrueFileFilter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* PBO 工具类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
* @see <a href="https://github.com/winseros/pboman3">https://github.com/winseros/pboman3</a>
|
||||
*/
|
||||
public class PBOUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PBOUtil.class);
|
||||
private static PBOUtil util;
|
||||
private static final String PBOC_DIR_PATH = AppConstant.DATA_DIR_PATH + File.separator + "pboman";
|
||||
private static final File PBOC_DIR = new File(PBOC_DIR_PATH);
|
||||
private static final String PBOC_FILE_PATH = PBOC_DIR_PATH + File.separator + "pboc.exe";
|
||||
private static final File PBOC_FILE = new File(PBOC_FILE_PATH);
|
||||
private static final String UNPACK_COMMAND = PBOC_FILE_PATH + " unpack -o " + AppConstant.TMP_DIR_PATH + " %s";
|
||||
private static final String PACK_COMMAND = PBOC_FILE_PATH + " pack -o %s %s";
|
||||
private OnPackListener onPackListener;
|
||||
private OnUnpackListener onUnpackListener;
|
||||
private OnFindTransWordListener onFindTransWordListener;
|
||||
private String unpackPath;
|
||||
private CfgConvertUtil cfgConvertUtil;
|
||||
private static final String FILE_NAME_STRING_TABLE = "stringtable.csv";
|
||||
private static final String FILE_NAME_CONFIG_BIN = "config.bin";
|
||||
private static final String FILE_NAME_CONFIG_CPP = "config.cpp";
|
||||
|
||||
private PBOUtil() {
|
||||
}
|
||||
|
||||
public static void init(CfgConvertUtil cfgConvertUtil) {
|
||||
if (util == null) {
|
||||
util = new PBOUtil();
|
||||
}
|
||||
// cfg转换工具
|
||||
util.cfgConvertUtil = cfgConvertUtil;
|
||||
// 检查pbo解析文件
|
||||
util.checkPboc();
|
||||
}
|
||||
|
||||
public static synchronized PBOUtil getInstance() {
|
||||
if (util == null)
|
||||
throw new RuntimeException("are you ready ?");
|
||||
return util;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置打包监听器
|
||||
*
|
||||
* @param onPackListener 打包监听器
|
||||
*/
|
||||
public void setOnPackListener(OnPackListener onPackListener) {
|
||||
this.onPackListener = onPackListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置解包监听器
|
||||
*
|
||||
* @param onUnpackListener 监听器
|
||||
*/
|
||||
public void setOnUnpackListener(OnUnpackListener onUnpackListener) {
|
||||
this.onUnpackListener = onUnpackListener;
|
||||
}
|
||||
|
||||
public void setOnFindTransWordListener(OnFindTransWordListener onFindTransWordListener) {
|
||||
this.onFindTransWordListener = onFindTransWordListener;
|
||||
}
|
||||
|
||||
private void checkPboc() {
|
||||
if (!PBOC_FILE.exists()) initPboc();
|
||||
}
|
||||
|
||||
private void initPboc() {
|
||||
try {
|
||||
FileUtils.forceMkdir(PBOC_DIR);
|
||||
String pbocFileName = "pboc.exe";
|
||||
String dllFileName = "Qt6Core.dll";
|
||||
FileUtil.copyFile(Objects.requireNonNull(PBOUtil.class.getResourceAsStream("/static/pboc/" + pbocFileName)), new File(PBOC_DIR_PATH + File.separator + pbocFileName));
|
||||
FileUtil.copyFile(Objects.requireNonNull(PBOUtil.class.getResourceAsStream("/static/pboc/" + dllFileName)), new File(PBOC_DIR_PATH + File.separator + dllFileName));
|
||||
} catch (IOException e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压PBO文件
|
||||
*
|
||||
* @param file PBO文件
|
||||
*/
|
||||
public void unpack(File file) {
|
||||
// 检查pbo解包程序
|
||||
checkPboc();
|
||||
// 清理缓存
|
||||
clear();
|
||||
|
||||
if (onUnpackListener != null) {
|
||||
onUnpackListener.onStart();
|
||||
}
|
||||
|
||||
String filePath = file.getAbsolutePath();
|
||||
if (filePath.contains(" ")) filePath = "\"" + filePath + "\"";
|
||||
|
||||
String command = String.format(UNPACK_COMMAND, filePath);
|
||||
logger.info(command);
|
||||
try {
|
||||
FileUtils.forceMkdir(new File(AppConstant.TMP_DIR_PATH));
|
||||
|
||||
// 执行命令
|
||||
ProcessesUtil.exec(command, new ProcessesUtil.OnExecuteListener() {
|
||||
@Override
|
||||
public void onExecute(String msg) {
|
||||
logger.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteSuccess(int exitValue) {
|
||||
if (exitValue != 0) {
|
||||
String msg = "打开PBO文件失败!";
|
||||
logger.error(msg);
|
||||
if (onUnpackListener != null) {
|
||||
Platform.runLater(() -> {
|
||||
onUnpackListener.onUnpackError(msg);
|
||||
onUnpackListener.onUnpackOver();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
logger.info("打开PBO文件成功!");
|
||||
unpackPath = AppConstant.TMP_DIR_PATH + File.separator + FileUtil.mainName(file);
|
||||
if (onUnpackListener != null) {
|
||||
Platform.runLater(() -> {
|
||||
onUnpackListener.onUnpackSuccess(unpackPath);
|
||||
onUnpackListener.onUnpackOver();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteError(Exception e) {
|
||||
logger.error("", e);
|
||||
if (onUnpackListener != null) {
|
||||
Platform.runLater(() -> {
|
||||
onUnpackListener.onUnpackError(e.getMessage());
|
||||
onUnpackListener.onUnpackOver();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteOver() {
|
||||
if (onUnpackListener != null) {
|
||||
Platform.runLater(() -> onUnpackListener.onUnpackOver());
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
if (onUnpackListener != null) {
|
||||
Platform.runLater(() -> {
|
||||
onUnpackListener.onUnpackError(e.getMessage());
|
||||
onUnpackListener.onUnpackOver();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待翻译单词列表
|
||||
*/
|
||||
public void startFindWord() {
|
||||
// 检查pbo解包文件
|
||||
if (unpackPath == null || StringUtils.isBlank(unpackPath))
|
||||
throw new RuntimeException("No PBO file was obtained !");
|
||||
|
||||
ThreadPoolManager.getInstance().execute(() -> {
|
||||
if (hasStringTable()) {
|
||||
List<WordItem> worlds = new ArrayList<>(readCsvFile());
|
||||
if (onFindTransWordListener != null) {
|
||||
Platform.runLater(() -> onFindTransWordListener.onFoundWords(worlds, true));
|
||||
}
|
||||
} else {
|
||||
findConfigWord();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取csv中 原文及 简中单词
|
||||
*
|
||||
* @return 待翻译语句列表
|
||||
*/
|
||||
private List<WordCsvItem> readCsvFile() {
|
||||
List<WordCsvItem> list = new ArrayList<>();
|
||||
AtomicInteger position = new AtomicInteger(0);
|
||||
File stringTable = new File(unpackPath + File.separator + FILE_NAME_STRING_TABLE);
|
||||
try (LineIterator it = FileUtils.lineIterator(stringTable, StandardCharsets.UTF_8.name())) {
|
||||
while (it.hasNext()) {
|
||||
String line = it.nextLine();
|
||||
|
||||
if (line.isEmpty() || line.startsWith("//") || line.startsWith("\"Language\"")) {
|
||||
position.addAndGet(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 原句
|
||||
int startIndex = line.indexOf(",\"") + 2;
|
||||
int endIndex = line.indexOf("\"", startIndex);
|
||||
String original = line.substring(startIndex, endIndex);
|
||||
|
||||
// 中文
|
||||
startIndex = StringUtils.ordinalIndexOf(line, ",\"", 11) + 2;
|
||||
endIndex = line.indexOf("\"", startIndex);
|
||||
int[] chinesePosition = new int[]{startIndex, endIndex};
|
||||
String chinese = line.substring(startIndex, endIndex);
|
||||
|
||||
// 简中
|
||||
startIndex = StringUtils.ordinalIndexOf(line, ",\"", 14) + 2;
|
||||
endIndex = line.indexOf("\"", startIndex);
|
||||
int[] chineseSimpPosition = new int[]{startIndex, endIndex};
|
||||
String chineseSimp = line.substring(startIndex, endIndex);
|
||||
|
||||
// 添加单词
|
||||
list.add(new WordCsvItem(stringTable, position.get(), original, chinese, chinesePosition, chineseSimp, chineseSimpPosition));
|
||||
|
||||
position.addAndGet(1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
list.sort(Comparator.comparingInt(WordItem::getLines));
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 config.bin 文件内 可翻译内容
|
||||
*/
|
||||
private void findConfigWord() {
|
||||
|
||||
// 搜索所有的 config.bin 文件,并按路径解包到bak文件夹
|
||||
List<File> files = new ArrayList<>(FileUtils.listFiles(new File(unpackPath), new NameFileFilter(FILE_NAME_CONFIG_BIN, FILE_NAME_CONFIG_CPP), TrueFileFilter.INSTANCE));
|
||||
|
||||
files.forEach(file -> {
|
||||
|
||||
// 转换bin文件为cpp可读取文件
|
||||
if (file.getName().endsWith("bin")) {
|
||||
cfgConvertUtil.toTxt(file, file.getParentFile().getAbsolutePath(), new CfgConvertUtil.OnToTxtListener() {
|
||||
@Override
|
||||
public void onToTxtSuccess(String txtFilePath) {
|
||||
// 读取 cpp 文件
|
||||
readCppFile(new File(txtFilePath), file.equals(files.get(files.size() - 1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToTxtError(Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
AlertUtil.exception(e).content(FILE_NAME_CONFIG_BIN + "文件转换失败").show();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 读取 cpp 文件
|
||||
readCppFile(file, file.equals(files.get(files.size() - 1)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static final Pattern pattern = Pattern.compile(".*((displayName|descriptionShort).?=.?\").*");
|
||||
|
||||
/**
|
||||
* 读取cpp文件,查询可翻译文本
|
||||
*
|
||||
* @param file cpp文件
|
||||
*/
|
||||
private void readCppFile(File file, boolean isEnd) {
|
||||
List<WordItem> list = new ArrayList<>();
|
||||
AtomicInteger lines = new AtomicInteger(0);
|
||||
try (LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name())) {
|
||||
while (it.hasNext()) {
|
||||
String line = it.nextLine();
|
||||
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (!line.contains("$") && matcher.find()) {
|
||||
|
||||
String name = matcher.group(1);
|
||||
|
||||
// 原始文本
|
||||
int startIndex = line.indexOf(name) + name.length();
|
||||
|
||||
int endIndex = line.indexOf("\"", startIndex);
|
||||
String original;
|
||||
try {
|
||||
original = line.substring(startIndex, endIndex);
|
||||
} catch (Exception e) {
|
||||
lines.addAndGet(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 添加单词
|
||||
if (!"".endsWith(original) && !containsChinese(original)) {
|
||||
list.add(new WordItem(file, lines.get(), original, "", new int[]{startIndex, endIndex}));
|
||||
}
|
||||
}
|
||||
|
||||
lines.addAndGet(1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
list.sort(Comparator.comparingInt(WordItem::getLines));
|
||||
|
||||
if (onFindTransWordListener != null) {
|
||||
Platform.runLater(() -> onFindTransWordListener.onFoundWords(list, isEnd));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否存在中文
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 是否存在中文
|
||||
*/
|
||||
private boolean containsChinese(String str) {
|
||||
// [\u4e00-\u9fa5]
|
||||
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打包PBO文件
|
||||
*/
|
||||
public void pack(List<WordItem> words) {
|
||||
if (onPackListener != null) {
|
||||
Platform.runLater(() -> onPackListener.onStart());
|
||||
}
|
||||
|
||||
ThreadPoolManager.getInstance().execute(() -> {
|
||||
File unpackDir;
|
||||
if (StringUtils.isBlank(unpackPath)
|
||||
|| !(unpackDir = new File(unpackPath)).exists()
|
||||
|| !unpackDir.isDirectory()
|
||||
) {
|
||||
AlertUtil.error("未获取到打开的pbo文件!").show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 写入翻译后文本
|
||||
try {
|
||||
writeWords(words);
|
||||
} catch (Exception e) {
|
||||
logger.error("writeWords error", e);
|
||||
if (onPackListener != null) {
|
||||
Platform.runLater(() -> {
|
||||
onPackListener.onPackOver();
|
||||
onPackListener.onPackError("writeWords error ==> " + e.getMessage());
|
||||
});
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// 打包文件临时保存路径
|
||||
String packFilePath = unpackPath + ".pbo";
|
||||
File packFile = new File(packFilePath);
|
||||
if (packFile.exists()) {
|
||||
// 如果存在则删除
|
||||
FileUtils.deleteQuietly(packFile);
|
||||
}
|
||||
|
||||
// 执行打包指令
|
||||
String command = String.format(PACK_COMMAND, AppConstant.TMP_DIR_PATH, unpackPath);
|
||||
logger.info(command);
|
||||
ProcessesUtil.exec(command, new ProcessesUtil.OnExecuteListener() {
|
||||
@Override
|
||||
public void onExecute(String msg) {
|
||||
logger.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteSuccess(int exitValue) {
|
||||
Platform.runLater(() -> {
|
||||
if (exitValue != 0) {
|
||||
logger.error("保存PBO文件失败!");
|
||||
if (onPackListener != null) {
|
||||
onPackListener.onPackOver();
|
||||
onPackListener.onPackError("保存PBO文件失败!");
|
||||
}
|
||||
} else {
|
||||
if (onPackListener != null) {
|
||||
onPackListener.onPackOver();
|
||||
onPackListener.onPackSuccess(packFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteError(Exception e) {
|
||||
logger.error("保存PBO文件失败!");
|
||||
if (onPackListener != null) {
|
||||
Platform.runLater(() -> {
|
||||
onPackListener.onPackOver();
|
||||
onPackListener.onPackError(e.getMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteOver() {
|
||||
if (onPackListener != null) {
|
||||
Platform.runLater(() -> onPackListener.onPackOver());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入翻译文本
|
||||
*
|
||||
* @param words 已经翻译好的文本对象
|
||||
*/
|
||||
private void writeWords(List<WordItem> words) throws Exception {
|
||||
Map<File, List<WordItem>> wordMap = words.stream()
|
||||
.collect(Collectors.groupingBy(WordItem::getFile, Collectors.toList()));
|
||||
|
||||
AtomicInteger progress = new AtomicInteger(0);
|
||||
|
||||
// 0 执行成功 大于0 执行失败
|
||||
List<Future<Integer>> result = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<File, List<WordItem>> entry : wordMap.entrySet()) {
|
||||
Future<Integer> submit = ThreadPoolManager.getInstance().submit(() -> {
|
||||
try {
|
||||
entry.getValue().sort(Comparator.comparingInt(WordItem::getLines));
|
||||
|
||||
File file = entry.getKey();
|
||||
// 创建备份文件
|
||||
File bakFile = getWordBakFile(file);
|
||||
// 判断重复打包时 备份文件处理
|
||||
if (!bakFile.exists()) {
|
||||
FileUtils.copyFile(file, bakFile);
|
||||
}
|
||||
|
||||
// 清空原始文件
|
||||
FileWriter fileWriter = new FileWriter(file);
|
||||
fileWriter.write("");
|
||||
fileWriter.flush();
|
||||
fileWriter.close();
|
||||
|
||||
// 遍历拼接翻译文本并写入
|
||||
long lines = 0;
|
||||
String line;
|
||||
LineIterator it = FileUtils.lineIterator(bakFile, StandardCharsets.UTF_8.name());
|
||||
while (it.hasNext() && !entry.getValue().isEmpty()) {
|
||||
line = it.nextLine();
|
||||
|
||||
WordItem item = entry.getValue().get(0);
|
||||
int[] ip = item.getPosition();
|
||||
|
||||
// 拼接翻译后的文本
|
||||
if (lines == item.getLines()) {
|
||||
if (item instanceof WordCsvItem csv) {
|
||||
int[] simp = csv.getPositionSimp();
|
||||
// 判断 ip 是否比 simp 靠前
|
||||
boolean tag = ip[0] < simp[0];
|
||||
line = line.substring(0, (tag ? ip : simp)[0])
|
||||
+ (tag ? csv.getChinese() : csv.getChineseSimp())
|
||||
+ line.substring((tag ? ip : simp)[1], (tag ? simp : ip)[0])
|
||||
+ (tag ? csv.getChineseSimp() : csv.getChinese())
|
||||
+ line.substring((tag ? simp : ip)[1]);
|
||||
} else {
|
||||
try {
|
||||
line = line.substring(0, ip[0])
|
||||
+ item.getChinese()
|
||||
+ line.substring(ip[1]);
|
||||
} catch (Exception e) {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
|
||||
entry.getValue().remove(item);
|
||||
if (onPackListener != null) {
|
||||
Platform.runLater(() -> onPackListener.onProgress(progress.addAndGet(1), words.size()));
|
||||
}
|
||||
}
|
||||
|
||||
// 写入原始文件
|
||||
FileUtils.writeStringToFile(file, line + System.lineSeparator(), StandardCharsets.UTF_8, true);
|
||||
|
||||
lines++;
|
||||
}
|
||||
|
||||
// 关闭流
|
||||
IOUtils.closeQuietly(it);
|
||||
|
||||
// cpp 文件需要 转换为 bin
|
||||
if (file.getName().endsWith("cpp")) {
|
||||
File binFile = new File(file.getParent() + File.separator + FileUtil.mainName(file) + ".bin");
|
||||
if (binFile.exists()) {
|
||||
// 转为bin文件
|
||||
cfgConvertUtil.toBin(file);
|
||||
// 删除cpp文件
|
||||
FileUtils.deleteQuietly(file);
|
||||
}
|
||||
// 同目录下不存在bin文件,说明原始文件为cpp 无需转换
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("写入翻译文本失败", e);
|
||||
// 执行失败
|
||||
return 1;
|
||||
}
|
||||
// 执行成功
|
||||
return 0;
|
||||
});
|
||||
|
||||
// 添加执行结果
|
||||
result.add(submit);
|
||||
}
|
||||
|
||||
for (Future<Integer> future : result) {
|
||||
if (future.get() > 0)
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private File getWordBakFile(File wordFile) {
|
||||
return new File(wordFile.getAbsolutePath().replace(unpackPath, AppConstant.BAK_FILE_PATH));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有国际化翻译文件
|
||||
*
|
||||
* @return 是否有 <code>stringtable.csv</code> 文件
|
||||
*/
|
||||
private boolean hasStringTable() {
|
||||
List<String> fileNames = FileUtil.listFileNames(unpackPath);
|
||||
return fileNames.stream().anyMatch(FILE_NAME_STRING_TABLE::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存文件
|
||||
*/
|
||||
public static void clear() {
|
||||
File tmpDest = new File(AppConstant.TMP_DIR_PATH);
|
||||
|
||||
if (tmpDest.exists())
|
||||
FileUtils.deleteQuietly(tmpDest);
|
||||
}
|
||||
|
||||
public interface OnUnpackListener {
|
||||
/**
|
||||
* 开始解包
|
||||
*/
|
||||
void onStart();
|
||||
|
||||
/**
|
||||
* 解包完成
|
||||
*
|
||||
* @param unpackDirPath 解包文件夹绝对路径
|
||||
*/
|
||||
void onUnpackSuccess(String unpackDirPath);
|
||||
|
||||
/**
|
||||
* 解包失败
|
||||
*
|
||||
* @param msg 失败信息
|
||||
*/
|
||||
void onUnpackError(String msg);
|
||||
|
||||
void onUnpackOver();
|
||||
}
|
||||
|
||||
public interface OnPackListener {
|
||||
/**
|
||||
* 开始解包
|
||||
*/
|
||||
void onStart();
|
||||
|
||||
void onProgress(long current, long all);
|
||||
|
||||
/**
|
||||
* 打包完成
|
||||
*/
|
||||
void onPackSuccess(File packFile);
|
||||
|
||||
/**
|
||||
* 打包失败
|
||||
*
|
||||
* @param msg 失败信息
|
||||
*/
|
||||
void onPackError(String msg);
|
||||
|
||||
void onPackOver();
|
||||
}
|
||||
|
||||
public interface OnFindTransWordListener {
|
||||
void onFoundWords(List<WordItem> worlds, boolean isOver);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.file;
|
||||
|
||||
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.image.PixelFormat;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritablePixelFormat;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.filechooser.FileSystemView;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.io.File;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
/**
|
||||
* 文件图标
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class FileIcon {
|
||||
//设置图标
|
||||
public static Canvas getFileIconToNode(File file) {
|
||||
//获取系统文件的图标
|
||||
Image image = ((ImageIcon) FileSystemView.getFileSystemView().getSystemIcon(file)).getImage();
|
||||
//构建图片缓冲区,设定图片缓冲区的大小和背景,背景为透明
|
||||
BufferedImage bi = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.BITMASK);
|
||||
//把图片画到图片缓冲区
|
||||
bi.getGraphics().drawImage(image, 0, 0, null);
|
||||
//将图片缓冲区的数据转换成int型数组
|
||||
int[] data = ((DataBufferInt) bi.getData().getDataBuffer()).getData();
|
||||
//获得写像素的格式模版
|
||||
WritablePixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbInstance();
|
||||
//新建javafx的画布
|
||||
Canvas canvas = new Canvas(bi.getWidth() + 2, bi.getHeight() + 2);
|
||||
//获取像素的写入器
|
||||
PixelWriter pixelWriter = canvas.getGraphicsContext2D().getPixelWriter();
|
||||
//根据写像素的格式模版把int型数组写到画布
|
||||
pixelWriter.setPixels(0, 0, bi.getWidth(), bi.getHeight(), pixelFormat, data, 0, bi.getWidth());
|
||||
//设置树节点的图标
|
||||
return canvas;
|
||||
|
||||
}
|
||||
|
||||
public static String getFileName(File file) {
|
||||
return FileSystemView.getFileSystemView().getSystemDisplayName(file);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.file;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TreeItem;
|
||||
|
||||
import javax.swing.filechooser.FileSystemView;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 文件目录
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class FileTreeItem extends TreeItem<String> {
|
||||
public static File ROOT_FILE = FileSystemView.getFileSystemView().getRoots()[0];
|
||||
|
||||
//判断树节点是否被初始化,没有初始化为真
|
||||
private boolean notInitialized = true;
|
||||
|
||||
private final File file;
|
||||
private final Function<File, File[]> supplier;
|
||||
|
||||
public FileTreeItem(File file) {
|
||||
super(FileIcon.getFileName(file), FileIcon.getFileIconToNode(file));
|
||||
this.file = file;
|
||||
supplier = (File f) -> {
|
||||
if (((FileTreeItem) this.getParent()).getFile() == ROOT_FILE) {
|
||||
String name = FileIcon.getFileName(f);
|
||||
if (name.equals("网络") || name.equals("家庭组")) {
|
||||
return new File[0];
|
||||
}
|
||||
}
|
||||
return f.listFiles();
|
||||
};
|
||||
}
|
||||
|
||||
public FileTreeItem(File file, Function<File, File[]> supplier) {
|
||||
super(FileIcon.getFileName(file), FileIcon.getFileIconToNode(file));
|
||||
this.file = file;
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
|
||||
//重写getchildren方法,让节点被展开时加载子目录
|
||||
@Override
|
||||
public ObservableList<TreeItem<String>> getChildren() {
|
||||
|
||||
ObservableList<TreeItem<String>> children = super.getChildren();
|
||||
//没有加载子目录时,则加载子目录作为树节点的孩子
|
||||
if (this.notInitialized && this.isExpanded()) {
|
||||
|
||||
this.notInitialized = false; //设置没有初始化为假
|
||||
|
||||
/*
|
||||
*判断树节点的文件是否是目录,
|
||||
*如果是目录,着把目录里面的所有的文件添加入树节点的孩子中。
|
||||
*/
|
||||
if (this.getFile().isDirectory()) {
|
||||
List<FileTreeItem> fileList = new ArrayList<>();
|
||||
for (File f : supplier.apply(this.getFile())) {
|
||||
if (f.isDirectory())
|
||||
children.add(new FileTreeItem(f));
|
||||
else
|
||||
fileList.add(new FileTreeItem(f));
|
||||
}
|
||||
children.addAll(fileList);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
//重写叶子方法,如果该文件不是目录,则返回真
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
|
||||
return !file.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file
|
||||
*/
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.http;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.net.Authenticator;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.http.HttpClient;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Http配置参数
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class HttpConfig {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
|
||||
/**
|
||||
* http版本
|
||||
*/
|
||||
private HttpClient.Version version = HttpClient.Version.HTTP_2;
|
||||
|
||||
/**
|
||||
* 转发策略
|
||||
*/
|
||||
private HttpClient.Redirect redirect = HttpClient.Redirect.NORMAL;
|
||||
|
||||
/**
|
||||
* 线程池
|
||||
*/
|
||||
private Executor executor;
|
||||
|
||||
/**
|
||||
* 认证
|
||||
*/
|
||||
private Authenticator authenticator;
|
||||
|
||||
/**
|
||||
* 代理
|
||||
*/
|
||||
private ProxySelector proxySelector;
|
||||
|
||||
/**
|
||||
* CookieHandler
|
||||
*/
|
||||
private CookieHandler cookieHandler;
|
||||
|
||||
/**
|
||||
* sslContext
|
||||
*/
|
||||
private SSLContext sslContext;
|
||||
|
||||
/**
|
||||
* sslParams
|
||||
*/
|
||||
private SSLParameters sslParameters;
|
||||
|
||||
/**
|
||||
* 连接超时时间毫秒
|
||||
*/
|
||||
private int connectTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 默认读取数据超时时间
|
||||
*/
|
||||
private int defaultReadTimeout = 1200000;
|
||||
|
||||
|
||||
public HttpConfig() {
|
||||
TrustManager[] trustAllCertificates = new X509TrustManager[]{new X509TrustManager() {
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0]; // Not relevant.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
}};
|
||||
sslParameters = new SSLParameters();
|
||||
sslParameters.setEndpointIdentificationAlgorithm("");
|
||||
|
||||
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLSv1.2");
|
||||
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
|
||||
sslContext.init(null, trustAllCertificates, new SecureRandom());
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public HttpClient.Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(HttpClient.Version version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public int getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(int connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
|
||||
public HttpClient.Redirect getRedirect() {
|
||||
return redirect;
|
||||
}
|
||||
|
||||
public void setRedirect(HttpClient.Redirect redirect) {
|
||||
this.redirect = redirect;
|
||||
}
|
||||
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
public void setExecutor(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public Authenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
public void setAuthenticator(Authenticator authenticator) {
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
public ProxySelector getProxySelector() {
|
||||
return proxySelector;
|
||||
}
|
||||
|
||||
public void setProxySelector(ProxySelector proxySelector) {
|
||||
this.proxySelector = proxySelector;
|
||||
}
|
||||
|
||||
public CookieHandler getCookieHandler() {
|
||||
return cookieHandler;
|
||||
}
|
||||
|
||||
public void setCookieHandler(CookieHandler cookieHandler) {
|
||||
this.cookieHandler = cookieHandler;
|
||||
}
|
||||
|
||||
public int getDefaultReadTimeout() {
|
||||
return defaultReadTimeout;
|
||||
}
|
||||
|
||||
public void setDefaultReadTimeout(int defaultReadTimeout) {
|
||||
this.defaultReadTimeout = defaultReadTimeout;
|
||||
}
|
||||
|
||||
public SSLContext getSslContext() {
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
public SSLParameters getSslParameters() {
|
||||
return sslParameters;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.http;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 网络请求封装
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class HttpUtil {
|
||||
private volatile static HttpUtil util;
|
||||
private volatile HttpClient httpClient;
|
||||
private final HttpConfig httpConfig;
|
||||
|
||||
private HttpUtil(HttpConfig httpConfig) {
|
||||
this.httpConfig = httpConfig;
|
||||
this.httpClient = createClient(httpConfig);
|
||||
}
|
||||
|
||||
public static HttpUtil getInstance() {
|
||||
if (util == null) {
|
||||
throw new RuntimeException("are you ready ?");
|
||||
}
|
||||
return util;
|
||||
}
|
||||
|
||||
public static void init(HttpConfig httpConfig) {
|
||||
synchronized (HttpUtil.class) {
|
||||
util = new HttpUtil(httpConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient createClient(HttpConfig httpConfig) {
|
||||
HttpClient.Builder builder = HttpClient.newBuilder()
|
||||
.version(httpConfig.getVersion())
|
||||
.connectTimeout(Duration.ofMillis(httpConfig.getConnectTimeout()))
|
||||
.sslContext(httpConfig.getSslContext())
|
||||
.sslParameters(httpConfig.getSslParameters())
|
||||
.followRedirects(httpConfig.getRedirect());
|
||||
Optional.ofNullable(httpConfig.getAuthenticator()).ifPresent(builder::authenticator);
|
||||
Optional.ofNullable(httpConfig.getCookieHandler()).ifPresent(builder::cookieHandler);
|
||||
Optional.ofNullable(httpConfig.getProxySelector()).ifPresent(builder::proxy);
|
||||
Optional.ofNullable(httpConfig.getExecutor()).ifPresent(builder::executor);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public HttpUtil proxy(String host, int port) {
|
||||
if (httpClient == null)
|
||||
throw new RuntimeException("are you ready ?");
|
||||
|
||||
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(host, port);
|
||||
ProxySelector other = ProxySelector.of(unresolved);
|
||||
this.httpConfig.setProxySelector(other);
|
||||
this.httpClient = createClient(httpConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void clearProxy() {
|
||||
if (httpClient == null)
|
||||
throw new RuntimeException("are you ready ?");
|
||||
|
||||
httpConfig.setProxySelector(HttpClient.Builder.NO_PROXY);
|
||||
httpClient = createClient(httpConfig);
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public String get(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
||||
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET();
|
||||
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
return response.body();
|
||||
}
|
||||
|
||||
public String post(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
||||
HttpRequest.Builder request = getRequest(uri, header)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(param.toJSONString()));
|
||||
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
return response.body();
|
||||
}
|
||||
|
||||
public String postForm(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
||||
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
|
||||
.POST(HttpRequest.BodyPublishers.noBody());
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
return response.body();
|
||||
}
|
||||
|
||||
private HttpRequest.Builder getRequest(String uri, JSONObject header) {
|
||||
HttpRequest.Builder request = HttpRequest.newBuilder();
|
||||
// 请求地址
|
||||
request.uri(URI.create(uri));
|
||||
// 请求头
|
||||
if (header != null && !header.isEmpty()) {
|
||||
for (String key : header.keySet()) {
|
||||
request.header(key, header.getString(key));
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private String createFormParams(JSONObject params) {
|
||||
StringBuilder formParams = new StringBuilder();
|
||||
if (params == null) {
|
||||
return formParams.toString();
|
||||
}
|
||||
for (String key : params.keySet()) {
|
||||
Object value = params.get(key);
|
||||
if (value instanceof String) {
|
||||
value = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8);
|
||||
formParams.append("&").append(key).append("=").append(value);
|
||||
} else if (value instanceof Number) {
|
||||
formParams.append("&").append(key).append("=").append(value);
|
||||
} else if (value instanceof List) {
|
||||
formParams.append("&").append(key).append("=").append(params.getJSONArray(key));
|
||||
} else {
|
||||
formParams.append("&").append(key).append("=").append(params.getJSONObject(key));
|
||||
}
|
||||
}
|
||||
if (!formParams.isEmpty()) {
|
||||
formParams = new StringBuilder("?" + formParams.substring(1));
|
||||
}
|
||||
|
||||
return formParams.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.thread;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 自定义线程工厂
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class ThreadFactory implements java.util.concurrent.ThreadFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ThreadFactory.class);
|
||||
|
||||
public static final String DEFAULT_THREAD_PREFIX = "thread-factory-pool";
|
||||
|
||||
private static final AtomicInteger poolNumber = new AtomicInteger(1);
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix;
|
||||
|
||||
public ThreadFactory() {
|
||||
this(DEFAULT_THREAD_PREFIX);
|
||||
}
|
||||
|
||||
public ThreadFactory(String prefix) {
|
||||
group = Thread.currentThread().getThreadGroup();
|
||||
namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable runnable) {
|
||||
|
||||
Thread t = new Thread(group, runnable,
|
||||
namePrefix + threadNumber.getAndIncrement(),
|
||||
0);
|
||||
|
||||
t.setUncaughtExceptionHandler((t1, e) -> {
|
||||
logger.error("thread : {}, error", t1.getName(), e);
|
||||
});
|
||||
|
||||
if (t.isDaemon())
|
||||
t.setDaemon(false);
|
||||
if (t.getPriority() != Thread.NORM_PRIORITY)
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.thread;
|
||||
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 线程池管理类
|
||||
*/
|
||||
public final class ThreadPoolManager extends ThreadPoolExecutor {
|
||||
|
||||
private static volatile ThreadPoolManager sInstance;
|
||||
|
||||
private static ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
private ThreadPoolManager() {
|
||||
super(32,
|
||||
200,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(200),
|
||||
new ThreadFactory(ThreadFactory.DEFAULT_THREAD_PREFIX),
|
||||
new ThreadPoolExecutor.DiscardPolicy());
|
||||
}
|
||||
|
||||
public static ThreadPoolManager getInstance() {
|
||||
if (sInstance == null) sInstance = new ThreadPoolManager();
|
||||
return sInstance;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate;
|
||||
|
||||
/**
|
||||
* API 密钥配置
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class ApiKey {
|
||||
private String appid;
|
||||
private String apiKey;
|
||||
|
||||
public ApiKey() {
|
||||
}
|
||||
|
||||
public ApiKey(String appid, String apiKey) {
|
||||
this.appid = appid;
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
public String getAppid() {
|
||||
return appid;
|
||||
}
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate;
|
||||
|
||||
/**
|
||||
* 翻译引擎类型
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public enum TranslateSource {
|
||||
FREE_GOOGLE("free_google", "谷歌(免费)", false, 50),
|
||||
BAIDU("baidu", "百度(需认证)", true),
|
||||
|
||||
;
|
||||
private final String name;
|
||||
private final String label;
|
||||
private final boolean needApiKey;
|
||||
private Integer defaultQps;
|
||||
|
||||
TranslateSource(String name, String label, boolean needApiKey) {
|
||||
// 设置接口默认qps=10
|
||||
this(name, label, needApiKey, 10);
|
||||
}
|
||||
|
||||
TranslateSource(String name, String label, boolean needApiKey, int defaultQps) {
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
this.needApiKey = needApiKey;
|
||||
this.defaultQps = defaultQps;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public boolean needApiKey() {
|
||||
return needApiKey;
|
||||
}
|
||||
|
||||
public Integer getDefaultQps() {
|
||||
return defaultQps;
|
||||
}
|
||||
|
||||
public String getDefaultQpsStr() {
|
||||
return String.valueOf(defaultQps);
|
||||
}
|
||||
|
||||
public static TranslateSource get(String type) {
|
||||
for (TranslateSource value : values()) {
|
||||
if (value.getName().equals(type))
|
||||
return value;
|
||||
}
|
||||
throw new RuntimeException("类型不存在");
|
||||
}
|
||||
|
||||
public static TranslateSource getByLabel(String label) {
|
||||
for (TranslateSource value : values()) {
|
||||
if (value.getLabel().equals(label))
|
||||
return value;
|
||||
}
|
||||
throw new RuntimeException("类型不存在");
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadFactory;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadPoolManager;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.factory.TranslateFactory;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.factory.TranslateFactoryImpl;
|
||||
import javafx.application.Platform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 翻译工具
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class TranslateUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TranslateUtil.class);
|
||||
private static TranslateUtil util;
|
||||
private static final DelayQueue<DelayWord> delayQueue = new DelayQueue<>();
|
||||
private final TranslateFactory factory;
|
||||
private static WordThread wordThread;
|
||||
private static ThreadPoolExecutor threadPoolExecutor;
|
||||
|
||||
private TranslateUtil(TranslateFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public static TranslateUtil getInstance() {
|
||||
if (util == null) {
|
||||
util = new TranslateUtil(TranslateFactoryImpl.getInstance());
|
||||
}
|
||||
return util;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交翻译任务
|
||||
*
|
||||
* @param index 序号
|
||||
* @param original 原始文本
|
||||
* @param listener 翻译结果回调 (主线程)
|
||||
*/
|
||||
public void translate(int index, String original, OnTranslateListener listener) {
|
||||
|
||||
// 设置延迟时间
|
||||
DelayWord word = factory.getDelayWord(CustomConfig.translateSource(), index, original, listener);
|
||||
// 添加到延迟队列
|
||||
delayQueue.add(word);
|
||||
|
||||
if (wordThread == null) {
|
||||
wordThread = new WordThread();
|
||||
wordThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除翻译任务
|
||||
*/
|
||||
public void clear() {
|
||||
// 尝试停止所有线程
|
||||
getThreadPoolExecutor().shutdownNow();
|
||||
// 清空队列
|
||||
delayQueue.clear();
|
||||
// 设置停止标记
|
||||
if (wordThread != null)
|
||||
wordThread.setStop(true);
|
||||
wordThread = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取翻译任务用线程池
|
||||
*/
|
||||
public static ThreadPoolExecutor getThreadPoolExecutor() {
|
||||
if (threadPoolExecutor == null || threadPoolExecutor.isShutdown()) {
|
||||
threadPoolExecutor = new ThreadPoolExecutor(32,
|
||||
200,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(200),
|
||||
new ThreadFactory(ThreadFactory.DEFAULT_THREAD_PREFIX),
|
||||
new ThreadPoolExecutor.DiscardPolicy());
|
||||
}
|
||||
return threadPoolExecutor;
|
||||
}
|
||||
|
||||
public interface OnTranslateListener {
|
||||
void onTranslate(String result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟翻译对象
|
||||
*/
|
||||
public static class DelayWord implements Delayed {
|
||||
private TranslateSource source;
|
||||
private final int index;
|
||||
private final String original;
|
||||
private final OnTranslateListener listener;
|
||||
private long time;
|
||||
|
||||
public DelayWord(int index, String original, OnTranslateListener listener) {
|
||||
this.index = index;
|
||||
this.original = original;
|
||||
this.listener = listener;
|
||||
|
||||
}
|
||||
|
||||
public void setSource(TranslateSource source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public void setTime(long time, TimeUnit timeUnit) {
|
||||
this.time = System.currentTimeMillis() + (time > 0 ? timeUnit.toMillis(time) : 0);
|
||||
}
|
||||
|
||||
public TranslateSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public String getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
public OnTranslateListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDelay(TimeUnit unit) {
|
||||
return time - System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Delayed o) {
|
||||
DelayWord word = (DelayWord) o;
|
||||
return Integer.compare(this.index, word.index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟队列处理线程
|
||||
*/
|
||||
private static class WordThread extends Thread {
|
||||
private boolean stop = false;
|
||||
|
||||
public void setStop(boolean stop) {
|
||||
this.stop = stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<DelayWord> tmp = new ArrayList<>();
|
||||
while (!delayQueue.isEmpty()) {
|
||||
// 停止处理
|
||||
if (stop) {
|
||||
this.interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 取出待翻译文本
|
||||
DelayWord take = delayQueue.take();
|
||||
tmp.add(take);
|
||||
|
||||
if (tmp.size() < CustomConfig.translateSourceQps(take.source))
|
||||
continue;
|
||||
|
||||
tmp.forEach(word -> {
|
||||
try {
|
||||
getThreadPoolExecutor().execute(() -> {
|
||||
// 翻译
|
||||
try {
|
||||
String translate = util.factory.translate(word.getSource(), word.getOriginal());
|
||||
// 回调监听器
|
||||
if (word.getListener() != null)
|
||||
// 主线程处理翻译结果
|
||||
Platform.runLater(() -> word.getListener().onTranslate(translate));
|
||||
} catch (InterruptedException ignored) {
|
||||
} catch (Exception e) {
|
||||
logger.error("翻译出错", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("翻译出错", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
tmp.clear();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余
|
||||
tmp.forEach(word -> {
|
||||
try {
|
||||
ThreadPoolManager.getInstance().execute(() -> {
|
||||
// 翻译
|
||||
try {
|
||||
String translate = util.factory.translate(word.getSource(), word.getOriginal());
|
||||
// 回调监听器
|
||||
if (word.getListener() != null)
|
||||
// 主线程处理翻译结果
|
||||
Platform.runLater(() -> word.getListener().onTranslate(translate));
|
||||
} catch (Exception e) {
|
||||
logger.error("翻译出错", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.error("翻译出错", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
|
||||
tmp.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate.factory;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
||||
|
||||
/**
|
||||
* 翻译器接口
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public interface TranslateFactory {
|
||||
/**
|
||||
* 翻译处理
|
||||
*
|
||||
* @param source 翻译源
|
||||
* @param sourceString 原始文本
|
||||
* @return 翻译结果
|
||||
* @throws Exception 翻译出错
|
||||
*/
|
||||
String translate(TranslateSource source, String sourceString) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取延迟翻译对象
|
||||
*
|
||||
* @param source 翻译源
|
||||
* @param index 序号
|
||||
* @param original 原始文本
|
||||
* @param listener 监听器
|
||||
* @return 延迟翻译对象
|
||||
*/
|
||||
TranslateUtil.DelayWord getDelayWord(TranslateSource source, int index, String original, TranslateUtil.OnTranslateListener listener);
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate.factory;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.processor.AbstractTranslateProcessor;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.processor.BaiduTranslateProcessor;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.processor.FreeGoogleTranslateProcessor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 翻译处理器
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class TranslateFactoryImpl implements TranslateFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TranslateFactoryImpl.class);
|
||||
private static TranslateFactoryImpl impl;
|
||||
private final Map<String, AbstractTranslateProcessor> processorMap = new HashMap<>();
|
||||
private final List<AbstractTranslateProcessor> processorList = new ArrayList<>();
|
||||
|
||||
private TranslateFactoryImpl() {
|
||||
}
|
||||
|
||||
public static synchronized TranslateFactoryImpl getInstance() {
|
||||
if (impl == null) {
|
||||
impl = new TranslateFactoryImpl();
|
||||
impl.initProcessor();
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
private void initProcessor() {
|
||||
processorList.addAll(Arrays.asList(
|
||||
new FreeGoogleTranslateProcessor(TranslateSource.FREE_GOOGLE),
|
||||
new BaiduTranslateProcessor(TranslateSource.BAIDU)
|
||||
));
|
||||
for (AbstractTranslateProcessor processor : processorList) {
|
||||
processorMap.put(processor.getSource(), processor);
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractTranslateProcessor getProcessor(TranslateSource source) {
|
||||
return processorMap.get(source.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取延迟翻译对象
|
||||
*
|
||||
* @param source 翻译源
|
||||
* @param index 序号
|
||||
* @param original 原始文本
|
||||
* @param listener 监听器
|
||||
* @return 延迟翻译对象
|
||||
*/
|
||||
@Override
|
||||
public TranslateUtil.DelayWord getDelayWord(TranslateSource source, int index, String original, TranslateUtil.OnTranslateListener listener) {
|
||||
// 生产翻译对象
|
||||
TranslateUtil.DelayWord word = new TranslateUtil.DelayWord(index, original, listener);
|
||||
// 设置延迟
|
||||
getProcessor(source).setDelayTime(word);
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译(英->中)
|
||||
* <p> TODO 切换语种
|
||||
*
|
||||
* @param source 翻译源
|
||||
* @param sourceString 原始文本
|
||||
* @return 翻译结果
|
||||
* @throws Exception 翻译出错
|
||||
*/
|
||||
@Override
|
||||
public String translate(TranslateSource source, String sourceString) throws Exception {
|
||||
return getProcessor(source).translate(sourceString);
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.ApiKey;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 翻译处理器抽象类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public abstract class AbstractTranslateProcessor implements TranslateProcessor {
|
||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
protected static final HttpUtil httpUtil = HttpUtil.getInstance();
|
||||
protected final TranslateSource translateSource;
|
||||
protected ApiKey apiKey;
|
||||
|
||||
public AbstractTranslateProcessor(TranslateSource translateSource) {
|
||||
this.translateSource = translateSource;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return translateSource.getName();
|
||||
}
|
||||
|
||||
public TranslateSource source() {
|
||||
return translateSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needApiKey() {
|
||||
return source().needApiKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configuredKey() {
|
||||
return CustomConfig.hasTranslateApiKey(source());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int qps() {
|
||||
return CustomConfig.translateSourceQps(source());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Api配置信息
|
||||
*/
|
||||
protected ApiKey getApiKey() {
|
||||
if (!configuredKey()) {
|
||||
String message = String.format("未配置【%s】翻译源认证信息!", source().getLabel());
|
||||
logger.error(message);
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
|
||||
String appid = CustomConfig.translateSourceAppid(source());
|
||||
String apikey = CustomConfig.translateSourceApikey(source());
|
||||
return new ApiKey(appid, apikey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translate(String source) throws Exception {
|
||||
|
||||
if (needApiKey() && !configuredKey()) {
|
||||
String message = String.format("未配置【%s】翻译源认证信息!", source().getLabel());
|
||||
logger.error(message);
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
|
||||
return customTranslate(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译处理
|
||||
*
|
||||
* @param source 原始文本
|
||||
* @return 翻译结果
|
||||
*/
|
||||
public abstract String customTranslate(String source) throws Exception;
|
||||
|
||||
/**
|
||||
* 设置延迟对象
|
||||
*
|
||||
* @param word 带翻译单词
|
||||
*/
|
||||
public void setDelayTime(TranslateUtil.DelayWord word) {
|
||||
// 设置翻译源
|
||||
word.setSource(source());
|
||||
|
||||
// 设置翻译延迟
|
||||
int time = word.getIndex() / qps();
|
||||
word.setTime(time, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.ApiKey;
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 谷歌 免费翻译接口
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class BaiduTranslateProcessor extends AbstractTranslateProcessor {
|
||||
|
||||
private ApiKey apiKey;
|
||||
|
||||
public BaiduTranslateProcessor(TranslateSource translateSource) {
|
||||
super(translateSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return "https://fanyi-api.baidu.com/api/trans/vip/translate";
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译处理
|
||||
*
|
||||
* @param source 待翻译单词
|
||||
* @return 翻译结果
|
||||
*/
|
||||
@Override
|
||||
public String customTranslate(String source) throws IOException, InterruptedException {
|
||||
apiKey = getApiKey();
|
||||
String appid = apiKey.getAppid();
|
||||
String salt = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
JSONObject param = new JSONObject();
|
||||
param.put("q", source);
|
||||
param.put("from", "auto");
|
||||
param.put("to", "zh");
|
||||
param.put("appid", appid);
|
||||
param.put("salt", salt);
|
||||
param.put("sign", getSign(appid, source, salt));
|
||||
|
||||
String resp = httpUtil.get(url(), null, param);
|
||||
JSONObject jsonObject = JSON.parseObject(resp);
|
||||
|
||||
if (!jsonObject.containsKey("trans_result")) {
|
||||
Object errorMsg = jsonObject.get("error_msg");
|
||||
logger.error("翻译失败: {}", errorMsg);
|
||||
throw new RuntimeException("翻译失败: " + errorMsg);
|
||||
}
|
||||
|
||||
return jsonObject.getJSONArray("trans_result").getJSONObject(0).getString("dst");
|
||||
}
|
||||
|
||||
private String getSign(String appid, String q, String salt) {
|
||||
return encrypt2ToMD5(appid + q + salt + apiKey.getApiKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* MD5加密
|
||||
*
|
||||
* @param str 待加密字符串
|
||||
* @return 16进制加密字符串
|
||||
*/
|
||||
public String encrypt2ToMD5(String str) {
|
||||
|
||||
// 加密后的16进制字符串
|
||||
String hexStr = "";
|
||||
try {
|
||||
|
||||
// 此 MessageDigest 类为应用程序提供信息摘要算法的功能
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
// 转换为MD5码
|
||||
byte[] digest = md5.digest(str.getBytes(StandardCharsets.UTF_8));
|
||||
hexStr = bytesToHexString(digest);
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
return hexStr.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将byte数组转换成string类型表示
|
||||
*/
|
||||
private String bytesToHexString(byte[] src) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (src == null || src.length == 0) {
|
||||
return null;
|
||||
}
|
||||
String hv;
|
||||
for (byte b : src) {
|
||||
// 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
|
||||
hv = Integer.toHexString(b & 0xFF).toUpperCase();
|
||||
if (hv.length() < 2) {
|
||||
builder.append(0);
|
||||
}
|
||||
builder.append(hv);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
||||
|
||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 谷歌 免费翻译接口
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
|
||||
|
||||
public FreeGoogleTranslateProcessor(TranslateSource translateSource) {
|
||||
super(translateSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String url() {
|
||||
return "https://translate.googleapis.com/translate_a/single";
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译处理
|
||||
*
|
||||
* @param source 待翻译单词
|
||||
* @return 翻译结果
|
||||
*/
|
||||
@Override
|
||||
public String customTranslate(String source) throws IOException, InterruptedException {
|
||||
|
||||
JSONObject form = new JSONObject();
|
||||
form.put("client", "gtx");
|
||||
form.put("dt", "t");
|
||||
form.put("sl", "auto");
|
||||
form.put("tl", "zh-CN");
|
||||
form.put("q", source);
|
||||
|
||||
JSONObject header = new JSONObject();
|
||||
StringBuilder retStr = new StringBuilder();
|
||||
// TODO 短时大量请求会被ban,需要浏览器验证添加cookie
|
||||
String resp = httpUtil.get(url(), header, form);
|
||||
JSONArray jsonObject = JSONArray.parseArray(resp);
|
||||
for (Object o : jsonObject.getJSONArray(0)) {
|
||||
JSONArray a = (JSONArray) o;
|
||||
retStr.append(a.getString(0));
|
||||
}
|
||||
|
||||
return retStr.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
||||
|
||||
/**
|
||||
* 翻译处理器
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public interface TranslateProcessor {
|
||||
/**
|
||||
* 翻译源 api接口地址
|
||||
*/
|
||||
String url();
|
||||
|
||||
/**
|
||||
* 是否需要配置API认证
|
||||
*/
|
||||
boolean needApiKey();
|
||||
|
||||
/**
|
||||
* 已配置API认证
|
||||
*/
|
||||
boolean configuredKey();
|
||||
|
||||
/**
|
||||
* qps 每秒访问的数量限制
|
||||
*/
|
||||
int qps();
|
||||
|
||||
/**
|
||||
* 翻译
|
||||
*
|
||||
* @param source 原始文本
|
||||
* @return 翻译结果
|
||||
* @throws Exception 翻译出错
|
||||
*/
|
||||
String translate(String source) throws Exception;
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.word;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* csv单词对象
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class WordCsvItem extends WordItem {
|
||||
/**
|
||||
* 简体中文
|
||||
*/
|
||||
private StringProperty chineseSimp;
|
||||
|
||||
/**
|
||||
* 文件中坐标(简体中文)
|
||||
*/
|
||||
private int[] positionSimp;
|
||||
|
||||
public WordCsvItem() {
|
||||
}
|
||||
|
||||
public WordCsvItem(File stringTable, int lines, String original, String chineses, int[] position, String chineseSimp, int[] positionSimp) {
|
||||
super(stringTable, lines, original, chineses, position);
|
||||
this.chineseSimp = new SimpleStringProperty(chineseSimp);
|
||||
this.positionSimp = positionSimp;
|
||||
}
|
||||
|
||||
public StringProperty chineseSimpProperty() {
|
||||
return chineseSimp;
|
||||
}
|
||||
|
||||
public String getChineseSimp() {
|
||||
return chineseSimp.get();
|
||||
}
|
||||
|
||||
public void setChineseSimp(String chineseSimp) {
|
||||
this.chineseSimp.setValue(chineseSimp);
|
||||
}
|
||||
|
||||
public int[] getPositionSimp() {
|
||||
return positionSimp;
|
||||
}
|
||||
|
||||
public void setPositionSimp(int[] positionSimp) {
|
||||
this.positionSimp = positionSimp;
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package cn.octopusyan.dayzmodtranslator.manager.word;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 待翻译单词子项
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class WordItem {
|
||||
/**
|
||||
* 所在文件
|
||||
* <p>PS: 文本所在的文件
|
||||
*/
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* 原始文本
|
||||
*/
|
||||
private StringProperty original;
|
||||
|
||||
/**
|
||||
* 汉化
|
||||
*/
|
||||
private StringProperty chinese;
|
||||
|
||||
/**
|
||||
* 行内下标
|
||||
*/
|
||||
private int[] position;
|
||||
|
||||
/**
|
||||
* 文件第几行
|
||||
*/
|
||||
private int lines;
|
||||
|
||||
public WordItem() {
|
||||
}
|
||||
|
||||
public WordItem(File file, int lines, String original, String chinese, int[] position) {
|
||||
this.file = file;
|
||||
this.original = new SimpleStringProperty(original);
|
||||
this.chinese = new SimpleStringProperty(chinese);
|
||||
this.position = position;
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public StringProperty originalProperty() {
|
||||
return original;
|
||||
}
|
||||
|
||||
public String getOriginal() {
|
||||
return original.get();
|
||||
}
|
||||
|
||||
public void setOriginal(String original) {
|
||||
this.original.setValue(original);
|
||||
}
|
||||
|
||||
public StringProperty chineseProperty() {
|
||||
return chinese;
|
||||
}
|
||||
|
||||
public String getChinese() {
|
||||
return chinese.get();
|
||||
}
|
||||
|
||||
public void setChinese(String chinese) {
|
||||
this.chinese.setValue(chinese);
|
||||
}
|
||||
|
||||
public int[] getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(int[] position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public int getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(int lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 弹窗工具
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class AlertUtil {
|
||||
private static Window mOwner;
|
||||
private static Builder builder;
|
||||
|
||||
public static void initOwner(Stage stage) {
|
||||
AlertUtil.mOwner = stage;
|
||||
}
|
||||
|
||||
public static class Builder<T extends Dialog> {
|
||||
T alert;
|
||||
|
||||
public Builder(T alert) {
|
||||
this.alert = alert;
|
||||
if (mOwner != null) this.alert.initOwner(mOwner);
|
||||
}
|
||||
|
||||
public Builder<T> title(String title) {
|
||||
alert.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> header(String header) {
|
||||
alert.setHeaderText(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> content(String content) {
|
||||
alert.setContentText(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> icon(String path) {
|
||||
icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> icon(Image image) {
|
||||
getStage().getIcons().add(image);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (AlertUtil.builder == null) {
|
||||
AlertUtil.builder = this;
|
||||
} else if (AlertUtil.builder.alert.isShowing()) {
|
||||
if (!Objects.equals(AlertUtil.builder.alert.getContentText(), alert.getContentText()))
|
||||
((Alert) AlertUtil.builder.alert).setOnHidden(event -> {
|
||||
AlertUtil.builder = null;
|
||||
show();
|
||||
});
|
||||
}
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.confirm
|
||||
*/
|
||||
public void show(OnClickListener listener) {
|
||||
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
|
||||
listener.onClicked(result.get().getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.confirm
|
||||
*/
|
||||
public void show(OnChoseListener listener) {
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
if (result.get() == ButtonType.OK) {
|
||||
listener.confirm();
|
||||
} else {
|
||||
listener.cancelOrClose(result.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.input
|
||||
* 如果用户点击了取消按钮,将会返回null
|
||||
*/
|
||||
public String getInput() {
|
||||
Optional<String> result = alert.showAndWait();
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.choices
|
||||
*/
|
||||
public <R> R getChoice(R... choices) {
|
||||
Optional result = alert.showAndWait();
|
||||
return (R) result.get();
|
||||
}
|
||||
|
||||
private Stage getStage() {
|
||||
return (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder<Alert> info(String content) {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION)).content(content).header(null);
|
||||
}
|
||||
|
||||
public static Builder<Alert> info() {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION));
|
||||
}
|
||||
|
||||
public static Builder<Alert> error(String message) {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.ERROR)).header(null).content(message);
|
||||
}
|
||||
|
||||
public static Builder<Alert> warning() {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.WARNING));
|
||||
}
|
||||
|
||||
public static Builder<Alert> exception(Exception ex) {
|
||||
return new Builder<Alert>(exceptionAlert(ex));
|
||||
}
|
||||
|
||||
public static Alert exceptionAlert(Exception ex) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Exception Dialog");
|
||||
alert.setHeaderText(ex.getClass().getSimpleName());
|
||||
alert.setContentText(ex.getMessage());
|
||||
|
||||
// 创建可扩展的异常。
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
ex.printStackTrace(pw);
|
||||
String exceptionText = sw.toString();
|
||||
|
||||
Label label = new Label("The exception stacktrace was :");
|
||||
|
||||
TextArea textArea = new TextArea(exceptionText);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
|
||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||
|
||||
GridPane expContent = new GridPane();
|
||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||
expContent.add(label, 0, 0);
|
||||
expContent.add(textArea, 0, 1);
|
||||
|
||||
// 将可扩展异常设置到对话框窗格中。
|
||||
alert.getDialogPane().setExpandableContent(expContent);
|
||||
return alert;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认对话框
|
||||
*/
|
||||
public static Builder<Alert> confirm() {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle("确认对话框");
|
||||
return new Builder<Alert>(alert);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义确认对话框 <p>
|
||||
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
|
||||
*/
|
||||
public static Builder<Alert> confirm(String... buttons) {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
List<ButtonType> buttonList = Arrays.stream(buttons).map((type) -> {
|
||||
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
|
||||
if ("Cancel".equals(type) || "取消".equals(type))
|
||||
buttonData = ButtonBar.ButtonData.CANCEL_CLOSE;
|
||||
return new ButtonType(type, buttonData);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
alert.getButtonTypes().setAll(buttonList);
|
||||
|
||||
return new Builder<Alert>(alert);
|
||||
}
|
||||
|
||||
public static Builder<TextInputDialog> input(String content) {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setContentText(content);
|
||||
return new Builder<TextInputDialog>(dialog);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Builder<ChoiceDialog<T>> choices(String hintText, T... choices) {
|
||||
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
|
||||
dialog.setContentText(hintText);
|
||||
return new Builder<ChoiceDialog<T>>(dialog);
|
||||
}
|
||||
|
||||
|
||||
public interface OnChoseListener {
|
||||
void confirm();
|
||||
|
||||
void cancelOrClose(ButtonType buttonType);
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClicked(String result);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.*;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <p> author : octopus yan
|
||||
* <p> email : octopus_yan@foxmail.com
|
||||
* <p> description : 剪切板工具
|
||||
* <p> create : 2022-4-14 23:21
|
||||
*/
|
||||
public class ClipUtil {
|
||||
//获取系统剪切板
|
||||
private static final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
|
||||
public static void setClip(String data) {
|
||||
//构建String数据类型
|
||||
StringSelection stringSelection = new StringSelection(data);
|
||||
//添加文本到系统剪切板
|
||||
clipboard.setContents(stringSelection, null);
|
||||
}
|
||||
|
||||
public static String getString() {
|
||||
Transferable content = clipboard.getContents(null);//从系统剪切板中获取数据
|
||||
if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {//判断是否为文本类型
|
||||
try {
|
||||
//从数据中获取文本值
|
||||
return (String) content.getTransferData(DataFlavor.stringFlavor);
|
||||
} catch (UnsupportedFlavorException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
135
src/main/java/cn/octopusyan/dayzmodtranslator/util/FileUtil.java
Normal file
135
src/main/java/cn/octopusyan/dayzmodtranslator/util/FileUtil.java
Normal file
@ -0,0 +1,135 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.CanReadFileFilter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件工具类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class FileUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
|
||||
|
||||
public static File[] ls(String path) {
|
||||
File dir = new File(path);
|
||||
if (!dir.exists())
|
||||
throw new RuntimeException(path + "不存在!");
|
||||
|
||||
if (!dir.isDirectory())
|
||||
throw new RuntimeException(path + "不是一个文件夹!");
|
||||
|
||||
return dir.listFiles();
|
||||
}
|
||||
|
||||
public static void copyFilesFromDir(String path, String dest) throws IOException {
|
||||
if (StringUtils.isBlank(path) || StringUtils.isBlank(dest)) {
|
||||
logger.error("path is blank !");
|
||||
return;
|
||||
}
|
||||
File dir = new File(path);
|
||||
if (!dir.exists()) {
|
||||
logger.error("[" + path + "] 不存在!");
|
||||
return;
|
||||
}
|
||||
if (!dir.isDirectory()) {
|
||||
logger.error("[" + path + "] 不是一个文件夹!");
|
||||
}
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) return;
|
||||
|
||||
File directory = new File(dest);
|
||||
if (directory.exists() && !directory.isDirectory()) {
|
||||
logger.error("[" + dest + "] 不是一个文件夹!");
|
||||
}
|
||||
|
||||
FileUtils.forceMkdir(directory);
|
||||
|
||||
for (File file : files) {
|
||||
copyFile(file, new File(dest + File.separator + file.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyFile(File in, File out) throws IOException {
|
||||
copyFile(Files.newInputStream(in.toPath()), out);
|
||||
}
|
||||
|
||||
public static void copyFile(InputStream input, File out) throws IOException {
|
||||
OutputStream output = null;
|
||||
try {
|
||||
output = Files.newOutputStream(out.toPath());
|
||||
byte[] buf = new byte[1024];
|
||||
int bytesRead;
|
||||
while ((bytesRead = input.read(buf)) > 0) {
|
||||
output.write(buf, 0, bytesRead);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("", e);
|
||||
} finally {
|
||||
if (output != null) input.close();
|
||||
if (output != null) output.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件主名称
|
||||
*
|
||||
* @param file 文件对象
|
||||
* @return 文件名称
|
||||
*/
|
||||
public static String mainName(File file) {
|
||||
//忽略判断
|
||||
String fileName = file.getName();
|
||||
return fileName.substring(0, fileName.lastIndexOf("."));
|
||||
}
|
||||
|
||||
public static List<String> listFileNames(String path) {
|
||||
Collection<File> files = FileUtils.listFiles(new File(path), CanReadFileFilter.CAN_READ, null);
|
||||
return files.stream().map(File::getName).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回被查找到的文件的绝对路径(匹配到一个就返回)
|
||||
*
|
||||
* @param root 根目录文件
|
||||
* @param fileName 要找的文件名
|
||||
* @return 绝对路径
|
||||
*/
|
||||
private static String findFiles(File root, String fileName) {
|
||||
//定义一个返回值
|
||||
String path = null;
|
||||
//如果传进来的是目录,并且存在
|
||||
if (root.exists() && root.isDirectory()) {
|
||||
//遍历文件夹中的各个文件
|
||||
File[] files = root.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
//如果path的值没有变化
|
||||
if (path == null) {
|
||||
if (file.isFile() && file.getName().contains(fileName)) {
|
||||
path = file.getAbsolutePath();
|
||||
} else {
|
||||
path = findFiles(file, fileName);
|
||||
}
|
||||
} else {
|
||||
break;//跳出循环,增加性能
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.JavaFXBuilderFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* FXML 工具
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class FxmlUtil {
|
||||
|
||||
public static FXMLLoader load(String name) {
|
||||
String prefix = "/fxml/";
|
||||
String suffix = ".fxml";
|
||||
return new FXMLLoader(
|
||||
FxmlUtil.class.getResource(prefix + name + suffix),
|
||||
null,
|
||||
new JavaFXBuilderFactory(),
|
||||
null,
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
/**
|
||||
* 加载等待弹窗
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class Loading {
|
||||
protected Stage stage;
|
||||
protected StackPane root;
|
||||
protected Label messageLb;
|
||||
protected ImageView loadingView = new ImageView(new Image("https://blog-static.cnblogs.com/files/miaoqx/loading.gif"));
|
||||
|
||||
public Loading(Stage owner) {
|
||||
|
||||
messageLb = new Label("请耐心等待...");
|
||||
messageLb.setFont(Font.font(20));
|
||||
|
||||
root = new StackPane();
|
||||
root.setMouseTransparent(true);
|
||||
root.setPrefSize(owner.getWidth(), owner.getHeight());
|
||||
root.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.3), null, null)));
|
||||
root.getChildren().addAll(loadingView, messageLb);
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
scene.setFill(Color.TRANSPARENT);
|
||||
|
||||
stage = new Stage();
|
||||
stage.setX(owner.getX());
|
||||
stage.setY(owner.getY());
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.initOwner(owner);
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.getIcons().addAll(owner.getIcons());
|
||||
stage.setX(owner.getX());
|
||||
stage.setY(owner.getY());
|
||||
stage.setHeight(owner.getHeight());
|
||||
stage.setWidth(owner.getWidth());
|
||||
}
|
||||
|
||||
// 更改信息
|
||||
public Loading showMessage(String message) {
|
||||
Platform.runLater(() -> messageLb.setText(message));
|
||||
return this;
|
||||
}
|
||||
|
||||
// 更改信息
|
||||
public Loading image(Image image) {
|
||||
Platform.runLater(() -> loadingView.imageProperty().set(image));
|
||||
return this;
|
||||
}
|
||||
|
||||
// 显示
|
||||
public void show() {
|
||||
Platform.runLater(() -> stage.show());
|
||||
}
|
||||
|
||||
// 关闭
|
||||
public void closeStage() {
|
||||
Platform.runLater(() -> stage.close());
|
||||
}
|
||||
|
||||
// 是否正在展示
|
||||
public boolean showing() {
|
||||
return stage.isShowing();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import org.apache.commons.exec.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 命令工具类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class ProcessesUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProcessesUtil.class);
|
||||
private static final String NEWLINE = System.lineSeparator();
|
||||
private static final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
|
||||
|
||||
public static boolean exec(String command) {
|
||||
try {
|
||||
exec(command, new OnExecuteListener() {
|
||||
@Override
|
||||
public void onExecute(String msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteSuccess(int exitValue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteError(Exception e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExecuteOver() {
|
||||
|
||||
}
|
||||
});
|
||||
handler.waitFor();
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
return 0 == handler.getExitValue();
|
||||
}
|
||||
|
||||
public static void exec(String command, OnExecuteListener listener) {
|
||||
LogOutputStream logout = new LogOutputStream() {
|
||||
@Override
|
||||
protected void processLine(String line, int logLevel) {
|
||||
if (listener != null) listener.onExecute(line + NEWLINE);
|
||||
}
|
||||
};
|
||||
|
||||
CommandLine commandLine = CommandLine.parse(command);
|
||||
DefaultExecutor executor = DefaultExecutor.builder().get();
|
||||
executor.setStreamHandler(new PumpStreamHandler(logout, logout));
|
||||
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
|
||||
@Override
|
||||
public void onProcessComplete(int exitValue) {
|
||||
if (listener != null) {
|
||||
listener.onExecuteSuccess(exitValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProcessFailed(ExecuteException e) {
|
||||
if (listener != null) {
|
||||
listener.onExecuteError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
executor.execute(commandLine, handler);
|
||||
} catch (IOException e) {
|
||||
if (listener != null) listener.onExecuteError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnExecuteListener {
|
||||
void onExecute(String msg);
|
||||
|
||||
void onExecuteSuccess(int exitValue);
|
||||
|
||||
void onExecuteError(Exception e);
|
||||
void onExecuteOver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent construction.
|
||||
*/
|
||||
private ProcessesUtil() {
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 配置文件读取工具
|
||||
*
|
||||
* @author liubin5620
|
||||
* @see <a href="https://blog.csdn.net/liubin5620/article/details/104618950">配置文件信息读取工具类【PropertiesUtils】</a>
|
||||
*/
|
||||
public class PropertiesUtils {
|
||||
/**
|
||||
* 主配置文件
|
||||
*/
|
||||
private Properties properties;
|
||||
/**
|
||||
* 启用配置文件
|
||||
*/
|
||||
private Properties propertiesCustom;
|
||||
|
||||
private static PropertiesUtils propertiesUtils = new PropertiesUtils();
|
||||
|
||||
/**
|
||||
* 私有构造,禁止直接创建
|
||||
*/
|
||||
private PropertiesUtils() {
|
||||
// 读取配置启用的配置文件名
|
||||
properties = new Properties();
|
||||
propertiesCustom = new Properties();
|
||||
InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties");
|
||||
try {
|
||||
properties.load(in);
|
||||
// 加载启用的配置
|
||||
String property = properties.getProperty("profiles.active");
|
||||
if (!StringUtils.isBlank(property)) {
|
||||
InputStream cin = PropertiesUtils.class.getClassLoader().getResourceAsStream("application-" + property + ".properties");
|
||||
propertiesCustom.load(cin);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例
|
||||
*
|
||||
* @return PropertiesUtils
|
||||
*/
|
||||
public static PropertiesUtils getInstance() {
|
||||
if (propertiesUtils == null) {
|
||||
propertiesUtils = new PropertiesUtils();
|
||||
}
|
||||
return propertiesUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例
|
||||
*
|
||||
* @return PropertiesUtils
|
||||
*/
|
||||
public static PropertiesUtils getInstance(File file) {
|
||||
PropertiesUtils util = new PropertiesUtils();
|
||||
|
||||
return util;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据属性名读取值
|
||||
* 先去主配置查询,如果查询不到,就去启用配置查询
|
||||
*
|
||||
* @param name 名称
|
||||
*/
|
||||
public String getProperty(String name) {
|
||||
String val = properties.getProperty(name);
|
||||
if (StringUtils.isBlank(val)) {
|
||||
val = propertiesCustom.getProperty(name);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package cn.octopusyan.dayzmodtranslator.util;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Window;
|
||||
|
||||
/**
|
||||
* 提示工具
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class TooltipUtil {
|
||||
private static TooltipUtil util;
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
private Window owner;
|
||||
private ChangeListener<Number> xListener;
|
||||
private ChangeListener<Number> yListener;
|
||||
private boolean paneMove = false;
|
||||
|
||||
private TooltipUtil(Window window) {
|
||||
this.owner = window;
|
||||
this.tooltip.styleProperty().set(
|
||||
"-fx-background-color: white;" +
|
||||
"-fx-text-fill: grey;" +
|
||||
"-fx-font-size: 12px;"
|
||||
);
|
||||
}
|
||||
|
||||
public static TooltipUtil getInstance(Pane pane) {
|
||||
if (pane == null) return null;
|
||||
Window window = pane.getScene().getWindow();
|
||||
if (window == null) return null;
|
||||
|
||||
if (util == null) {
|
||||
util = new TooltipUtil(window);
|
||||
// 窗口位置监听
|
||||
util.xListener = (observable, oldValue, newValue) -> {
|
||||
util.tooltip.setAnchorX(util.tooltip.getAnchorX() + (newValue.doubleValue() - oldValue.doubleValue()));
|
||||
util.paneMove = true;
|
||||
};
|
||||
util.yListener = (observable, oldValue, newValue) -> {
|
||||
util.tooltip.setAnchorY(util.tooltip.getAnchorY() + (newValue.doubleValue() - oldValue.doubleValue()));
|
||||
util.paneMove = true;
|
||||
};
|
||||
util.tooltip.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue) util.paneMove = false;
|
||||
});
|
||||
// 随窗口移动
|
||||
util.owner.xProperty().addListener(util.xListener);
|
||||
util.owner.yProperty().addListener(util.yListener);
|
||||
}
|
||||
|
||||
if (!window.equals(util.owner)) {
|
||||
// 删除旧监听
|
||||
util.owner.xProperty().removeListener(util.xListener);
|
||||
util.owner.yProperty().removeListener(util.yListener);
|
||||
// 新窗口
|
||||
util.owner = window;
|
||||
// 随窗口移动
|
||||
util.owner.xProperty().addListener(util.xListener);
|
||||
util.owner.yProperty().addListener(util.yListener);
|
||||
}
|
||||
|
||||
// 点击关闭
|
||||
pane.setOnMouseClicked(event -> {
|
||||
if (!util.paneMove) util.tooltip.hide();
|
||||
util.paneMove = false;
|
||||
});
|
||||
|
||||
util.tooltip.hide();
|
||||
|
||||
return util;
|
||||
}
|
||||
|
||||
public void showProxyTypeTip(MouseEvent event) {
|
||||
tooltip.setText(
|
||||
"提示:XTCP 映射成功率并不高,具体取决于 NAT 设备的复杂度。\n" +
|
||||
"TCP :基础的 TCP 映射,适用于大多数服务,例如远程桌面、SSH、Minecraft、泰拉瑞亚等\n" +
|
||||
"UDP :基础的 UDP 映射,适用于域名解析、部分基于 UDP 协议的游戏等\n" +
|
||||
"HTTP :搭建网站专用映射,并通过 80 端口访问\n" +
|
||||
"HTTPS :带有 SSL 加密的网站映射,通过 443 端口访问,服务器需要支持 SSL\n" +
|
||||
"XTCP :客户端之间点对点 (P2P) 连接协议,流量不经过服务器,适合大流量传输的场景,需要两台设备之间都运行一个客户端\n" +
|
||||
"STCP :安全交换 TCP 连接协议,基于 TCP,访问此服务的用户也需要运行一个客户端,才能建立连接,流量由服务器转发"
|
||||
);
|
||||
show(event);
|
||||
}
|
||||
|
||||
private void show(MouseEvent event) {
|
||||
|
||||
if (tooltip.isShowing()) {
|
||||
tooltip.hide();
|
||||
} else {
|
||||
tooltip.show(owner);
|
||||
double mx = event.getScreenX();
|
||||
double my = event.getScreenY();
|
||||
double tw = tooltip.widthProperty().doubleValue();
|
||||
double th = tooltip.heightProperty().doubleValue();
|
||||
|
||||
tooltip.setX(mx - tw / 2);
|
||||
tooltip.setY(my - th - 10);
|
||||
}
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
tooltip.hide();
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return tooltip.isShowing();
|
||||
}
|
||||
}
|
22
src/main/java/module-info.java
Normal file
22
src/main/java/module-info.java
Normal file
@ -0,0 +1,22 @@
|
||||
module cn.octopusyan.dayzmodtranslator {
|
||||
requires java.net.http;
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires javafx.swing;
|
||||
requires org.apache.commons.io;
|
||||
requires org.apache.commons.lang3;
|
||||
requires org.apache.commons.exec;
|
||||
requires org.slf4j;
|
||||
requires ch.qos.logback.core;
|
||||
requires ch.qos.logback.classic;
|
||||
requires com.alibaba.fastjson2;
|
||||
|
||||
exports cn.octopusyan.dayzmodtranslator;
|
||||
opens cn.octopusyan.dayzmodtranslator to javafx.fxml;
|
||||
exports cn.octopusyan.dayzmodtranslator.base;
|
||||
opens cn.octopusyan.dayzmodtranslator.base to javafx.fxml;
|
||||
exports cn.octopusyan.dayzmodtranslator.controller;
|
||||
opens cn.octopusyan.dayzmodtranslator.controller to javafx.fxml;
|
||||
exports cn.octopusyan.dayzmodtranslator.manager.word;
|
||||
opens cn.octopusyan.dayzmodtranslator.manager.word to javafx.base;
|
||||
}
|
3
src/main/resources/application.properties
Normal file
3
src/main/resources/application.properties
Normal file
@ -0,0 +1,3 @@
|
||||
app.name=${project.name}
|
||||
app.title=DayZ\u6A21\u7EC4\u6C49\u5316\u5DE5\u5177
|
||||
app.version=${project.version}
|
9
src/main/resources/css/main-view.css
Normal file
9
src/main/resources/css/main-view.css
Normal file
@ -0,0 +1,9 @@
|
||||
#root{
|
||||
-fx-background-color: white;
|
||||
}
|
||||
|
||||
#dragFileLabel {
|
||||
-fx-font-family: "Microsoft YaHei";
|
||||
-fx-text-fill: black;
|
||||
-fx-font-size: 20;
|
||||
}
|
10
src/main/resources/css/root.css
Normal file
10
src/main/resources/css/root.css
Normal file
@ -0,0 +1,10 @@
|
||||
.box_class {
|
||||
-fx-background-radius: 5;
|
||||
-fx-border-style: solid;
|
||||
-fx-border-color: rgba(136, 136, 136, 0.5);
|
||||
-fx-background-color: white;
|
||||
}
|
||||
|
||||
.conf_menu_item {
|
||||
-fx-pref-width: 100;
|
||||
}
|
73
src/main/resources/fxml/main-view.fxml
Normal file
73
src/main/resources/fxml/main-view.fxml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox fx:id="root" minHeight="330" minWidth="430" prefHeight="430.0" prefWidth="700" stylesheets="@../css/main-view.css"
|
||||
xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cn.octopusyan.dayzmodtranslator.controller.MainController">
|
||||
<MenuBar>
|
||||
<Menu mnemonicParsing="false" text="文件">
|
||||
<MenuItem fx:id="openFileSetupBtn" mnemonicParsing="false" styleClass="conf_menu_item" text="打开"/>
|
||||
<SeparatorMenuItem/>
|
||||
<MenuItem onAction="#onDestroy" mnemonicParsing="false" styleClass="conf_menu_item" text="退出"/>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="设置">
|
||||
<MenuItem fx:id="translateSetupBtn" mnemonicParsing="false" styleClass="conf_menu_item" text="翻译"/>
|
||||
<MenuItem fx:id="proxySetupBtn" mnemonicParsing="false" styleClass="conf_menu_item" text="代理"/>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="帮助">
|
||||
<MenuItem mnemonicParsing="false" styleClass="conf_menu_item" text="关于"/>
|
||||
</Menu>
|
||||
</MenuBar>
|
||||
<HBox alignment="CENTER_LEFT">
|
||||
<Label text="PBO文件:">
|
||||
<HBox.margin>
|
||||
<Insets right="5.0"/>
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
<Label fx:id="filePath"/>
|
||||
<HBox HBox.hgrow="ALWAYS"/>
|
||||
<HBox>
|
||||
<Button fx:id="translateWordBtn" text="一键翻译" visible="false"/>
|
||||
<Button fx:id="packBtn" text="打包" visible="false">
|
||||
<HBox.margin>
|
||||
<Insets left="5"/>
|
||||
</HBox.margin>
|
||||
</Button>
|
||||
</HBox>
|
||||
<VBox.margin>
|
||||
<Insets bottom="5.0" top="5.0"/>
|
||||
</VBox.margin>
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0"/>
|
||||
</padding>
|
||||
</HBox>
|
||||
<SplitPane dividerPositions="0.3" prefHeight="${root.height}">
|
||||
<StackPane fx:id="fileBox" SplitPane.resizableWithParent="false" maxWidth="420" minWidth="210">
|
||||
<VBox fx:id="openFileBox" alignment="CENTER" styleClass="box_class">
|
||||
<Button fx:id="openFile" text="打开文件"/>
|
||||
<Label style="-fx-text-fill: rgba(136,136,136,0.7)" text="或将 pbo文件 拖到此处">
|
||||
<VBox.margin>
|
||||
<Insets top="10.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
</VBox>
|
||||
<VBox fx:id="dragFileBox" alignment="CENTER" styleClass="box_class" visible="false">
|
||||
<Label fx:id="dragFileLabel" alignment="CENTER" prefHeight="-Infinity" text="将文件拖放到此处"
|
||||
textAlignment="CENTER"/>
|
||||
</VBox>
|
||||
<VBox fx:id="loadFileBox" alignment="CENTER" styleClass="box_class" visible="false">
|
||||
<Label fx:id="loadFileLabel"/>
|
||||
<ProgressBar fx:id="loadFileProgressBar"/>
|
||||
</VBox>
|
||||
<TreeView fx:id="treeFileBox" styleClass="box_class" visible="false"/>
|
||||
</StackPane>
|
||||
<StackPane fx:id="wordBox" styleClass="box_class" GridPane.columnIndex="1" minWidth="210">
|
||||
<VBox fx:id="wordMsgBox" alignment="CENTER" styleClass="box_class">
|
||||
<Label fx:id="wordMsgLabel"/>
|
||||
<ProgressBar fx:id="loadWordProgressBar" visible="false"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</SplitPane>
|
||||
</VBox>
|
70
src/main/resources/fxml/proxy-view.fxml
Normal file
70
src/main/resources/fxml/proxy-view.fxml
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<StackPane fx:id="root" prefHeight="276.0" prefWidth="260.0" xmlns="http://javafx.com/javafx/17.0.2-ea"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cn.octopusyan.dayzmodtranslator.controller.SetupProxyController">
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<Label alignment="CENTER" prefWidth="Infinity" text="网络代理设置"/>
|
||||
<Label text="Host">
|
||||
<VBox.margin>
|
||||
<Insets top="20"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="hostField">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
<Label text="port">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="portField">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
<Label text="测试地址">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="testPath">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
<VBox alignment="CENTER">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
<Button onAction="#test" style="-fx-background-color: transparent; -fx-text-fill: #000FFF"
|
||||
text="网络代理测试"/>
|
||||
</VBox>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
<Button text="确定" onAction="#save">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0"/>
|
||||
</padding>
|
||||
</Button>
|
||||
<Button text="取消" onAction="#close">
|
||||
<HBox.margin>
|
||||
<Insets left="5.0"/>
|
||||
</HBox.margin>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0"/>
|
||||
</padding>
|
||||
</Button>
|
||||
</HBox>
|
||||
<StackPane.margin>
|
||||
<Insets left="20.0" right="20.0"/>
|
||||
</StackPane.margin>
|
||||
</VBox>
|
||||
</StackPane>
|
89
src/main/resources/fxml/translate-view.fxml
Normal file
89
src/main/resources/fxml/translate-view.fxml
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<StackPane fx:id="root" prefHeight="155.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/17.0.2-ea"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="cn.octopusyan.dayzmodtranslator.controller.SetupTranslateController">
|
||||
<VBox alignment="CENTER">
|
||||
<VBox>
|
||||
<Label text="翻译源">
|
||||
<VBox.margin>
|
||||
<Insets right="15.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<ComboBox fx:id="translateSourceCombo">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</ComboBox>
|
||||
</VBox>
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
<Label text="QPS(每秒翻译数量)">
|
||||
<VBox.margin>
|
||||
<Insets right="15.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="qps">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
</VBox>
|
||||
<VBox alignment="CENTER_LEFT" fx:id="appidBox">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
<Label text="appid">
|
||||
<VBox.margin>
|
||||
<Insets right="15.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="appid">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
</VBox>
|
||||
<VBox alignment="CENTER_LEFT" fx:id="apikeyBox">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
<Label text="apikey">
|
||||
<VBox.margin>
|
||||
<Insets right="15.0"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<TextField fx:id="apikey">
|
||||
<VBox.margin>
|
||||
<Insets top="5.0"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
</VBox>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<VBox.margin>
|
||||
<Insets top="15.0"/>
|
||||
</VBox.margin>
|
||||
<Button onAction="#save" text="确定">
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0"/>
|
||||
</padding>
|
||||
</Button>
|
||||
<Button onAction="#onDestroy" text="取消">
|
||||
<HBox.margin>
|
||||
<Insets left="5.0"/>
|
||||
</HBox.margin>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0"/>
|
||||
</padding>
|
||||
</Button>
|
||||
</HBox>
|
||||
<StackPane.margin>
|
||||
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
|
||||
</StackPane.margin>
|
||||
</VBox>
|
||||
</StackPane>
|
98
src/main/resources/logback.xml
Normal file
98
src/main/resources/logback.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
|
||||
<property name="logback.logdir" value="log"/>
|
||||
<property name="CHARSET" value="utf-8"/>
|
||||
<property name="logback.app" value="DayzModTranslator"/>
|
||||
<!-- 彩色日志格式 -->
|
||||
<property name="CONSOLE_LOG_PATTERN" value="%highlight(%d{HH:mm:ss.SSS}) ${logback.app} %boldYellow([%thread]) %highlight(%-5level) %cyan(%logger{36}) - %mdc{client} [%X{trace_id}] %msg%n"/>
|
||||
|
||||
<!--输出到控制台 ConsoleAppender-->
|
||||
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>${CHARSET}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--输出到文件 fileLog-->
|
||||
<appender name="fileLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"/>
|
||||
<File>${logback.logdir}/${logback.app}.info.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
|
||||
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.info.log</FileNamePattern>
|
||||
<!--只保留最近30天的日志-->
|
||||
<maxHistory>30</maxHistory>
|
||||
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<!--日志输出编码格式化-->
|
||||
<encoder>
|
||||
<charset>${CHARSET}</charset>
|
||||
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
|
||||
</encoder>
|
||||
<!--只打印错误日志-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="fileLog-debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"/>
|
||||
<File>${logback.logdir}/${logback.app}.debug.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
|
||||
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.debug.log</FileNamePattern>
|
||||
<!--只保留最近30天的日志-->
|
||||
<maxHistory>30</maxHistory>
|
||||
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<!--日志输出编码格式化-->
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
|
||||
</encoder>
|
||||
<!--只打印错误日志-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 错误日志 -->
|
||||
<appender name="fileLog-err" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<File>${logback.logdir}/${logback.app}.err.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.err.log</FileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
|
||||
</encoder>
|
||||
<!--只打印错误日志-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!--指定最基础的日志输出级别-->
|
||||
<root level="INFO">
|
||||
<!--appender将会添加到这个logger-->
|
||||
<appender-ref ref="consoleLog"/>
|
||||
<appender-ref ref="fileLog"/>
|
||||
<appender-ref ref="fileLog-debug"/>
|
||||
<appender-ref ref="fileLog-err"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
BIN
src/main/resources/static/CfgConvert/CfgConvert.exe
Normal file
BIN
src/main/resources/static/CfgConvert/CfgConvert.exe
Normal file
Binary file not shown.
BIN
src/main/resources/static/pboc/Qt6Core.dll
Normal file
BIN
src/main/resources/static/pboc/Qt6Core.dll
Normal file
Binary file not shown.
BIN
src/main/resources/static/pboc/pboc.exe
Normal file
BIN
src/main/resources/static/pboc/pboc.exe
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user