mirror of
https://github.com/octopusYan/dayz-mod-translator.git
synced 2024-11-22 12:16: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