mirror of
https://github.com/octopusYan/dayz-mod-translator.git
synced 2025-12-08 09:11:57 +08:00
Compare commits
22 Commits
9edc015af0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a4ff07ce7b | |||
| 3ed4979aa9 | |||
| 16aa917d09 | |||
| 5214485a36 | |||
| 5a9226901f | |||
| 223abc7ac5 | |||
| 1294fb5414 | |||
| a10636c772 | |||
| c6f0268ff5 | |||
| 0c9052216b | |||
| 904f37a15b | |||
| e6dd1c39f8 | |||
| 912e11e673 | |||
| 8a6ef06ae6 | |||
| 3cb5637972 | |||
| cb5e0229ff | |||
| c6ad66bb59 | |||
| 92f9737e27 | |||
| 227f565991 | |||
| 76968cfd49 | |||
| 5ac5297184 | |||
| 1dc6e95340 |
@ -8,9 +8,6 @@
|
||||
<br>
|
||||
[](https://github.com/octopusYan/dayz-mod-translator)
|
||||

|
||||
<br>
|
||||

|
||||

|
||||
|
||||
<br>
|
||||
|
||||
@ -53,7 +50,7 @@
|
||||
```
|
||||
2. 运行
|
||||
```bash
|
||||
mvn clean javafx:run
|
||||
mvn clean javafx:run -Pdev
|
||||
```
|
||||
|
||||
#### 打包
|
||||
@ -64,7 +61,7 @@
|
||||
```
|
||||
2. 运行
|
||||
```bash
|
||||
mvn clean package
|
||||
mvn clean package -Pbuild
|
||||
```
|
||||
|
||||
### 可能会用到
|
||||
|
||||
55
pom.xml
55
pom.xml
@ -6,8 +6,8 @@
|
||||
|
||||
<groupId>cn.octopusyan</groupId>
|
||||
<artifactId>dmt</artifactId>
|
||||
<version>0.0.2</version>
|
||||
<name>dmt</name>
|
||||
<version>0.1.2</version>
|
||||
<name>DayzModTranslator</name>
|
||||
|
||||
<organization>
|
||||
<name>octopus_yan</name>
|
||||
@ -130,6 +130,14 @@
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Hutool -->
|
||||
<!-- https://hutool.cn -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>5.8.34</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://kordamp.org/ikonli/ -->
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
@ -172,14 +180,41 @@
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<properties>
|
||||
<debug.option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005</debug.option>
|
||||
</properties>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>build</id>
|
||||
<properties>
|
||||
<debug.option/>
|
||||
</properties>
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>bin/</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -233,7 +268,7 @@
|
||||
<configuration>
|
||||
<options>
|
||||
<option>--enable-preview</option>
|
||||
<!-- <option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005</option>-->
|
||||
<option>${debug.option}</option>
|
||||
</options>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
@ -16,6 +16,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -39,11 +41,15 @@ public class Context {
|
||||
@Getter
|
||||
private static final Map<String, BaseController<?>> controllers = new HashMap<>();
|
||||
|
||||
|
||||
private Context() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
public static boolean isDebugMode() {
|
||||
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
|
||||
return runtimeMXBean.getInputArguments().toString().contains("-agentlib:jdwp");
|
||||
}
|
||||
|
||||
// 获取控制工厂
|
||||
public static Callback<Class<?>, Object> getControlFactory() {
|
||||
return type -> {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package cn.octopusyan.dmt.common.manager.http;
|
||||
|
||||
import cn.octopusyan.dmt.common.enums.ProxySetup;
|
||||
import cn.octopusyan.dmt.common.manager.http.response.BodyHandler;
|
||||
import cn.octopusyan.dmt.common.manager.http.response.DownloadBodyHandler;
|
||||
import cn.octopusyan.dmt.common.util.JsonUtil;
|
||||
import cn.octopusyan.dmt.model.ProxyInfo;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@ -130,7 +130,7 @@ public class HttpUtil {
|
||||
}
|
||||
|
||||
// 下载处理器
|
||||
var handler = BodyHandler.create(
|
||||
var handler = DownloadBodyHandler.create(
|
||||
Path.of(savePath),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.WRITE
|
||||
);
|
||||
|
||||
@ -21,16 +21,16 @@ import java.util.function.Consumer;
|
||||
* @author octopus_yan
|
||||
*/
|
||||
@Slf4j
|
||||
public class BodyHandler implements HttpResponse.BodyHandler<Path> {
|
||||
public class DownloadBodyHandler implements HttpResponse.BodyHandler<Path> {
|
||||
private final HttpResponse.BodyHandler<Path> handler;
|
||||
private BiConsumer<Long, Long> consumer;
|
||||
|
||||
private BodyHandler(HttpResponse.BodyHandler<Path> handler) {
|
||||
private DownloadBodyHandler(HttpResponse.BodyHandler<Path> handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public static BodyHandler create(Path directory, OpenOption... openOptions) {
|
||||
return new BodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
|
||||
public static DownloadBodyHandler create(Path directory, OpenOption... openOptions) {
|
||||
return new DownloadBodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -52,12 +52,34 @@ public class ProcessesUtil {
|
||||
public static ProcessesUtil init(String workingDirectory) {
|
||||
return init(new File(workingDirectory));
|
||||
}
|
||||
|
||||
public static ProcessesUtil init(File workingDirectory) {
|
||||
ProcessesUtil util = new ProcessesUtil(workingDirectory);
|
||||
set.add(util);
|
||||
return util;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转命令
|
||||
*
|
||||
* @param command 命令模板
|
||||
* @param params 参数
|
||||
* @return 命令
|
||||
*/
|
||||
public static String format(String command, Object... params) {
|
||||
|
||||
int i = 0;
|
||||
while (command.contains("{}") && params != null) {
|
||||
String param = String.valueOf(params[i++]);
|
||||
|
||||
if (param.contains(" "))
|
||||
param = STR."\"\{param}\"";
|
||||
|
||||
command = command.replaceFirst("\\{}", param.replace("\\", "\\\\"));
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
public boolean exec(String command) {
|
||||
commandLine = CommandLine.parse(command);
|
||||
try {
|
||||
|
||||
@ -89,6 +89,9 @@ public class MainController extends BaseController<MainViewModel> {
|
||||
fileChooser.getExtensionFilters().add(extFilter);
|
||||
}
|
||||
|
||||
private File historySelectFolder;
|
||||
private File historySaveFolder;
|
||||
|
||||
@Override
|
||||
public Pane getRootPanel() {
|
||||
return root;
|
||||
@ -146,58 +149,12 @@ public class MainController extends BaseController<MainViewModel> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件拖拽效果
|
||||
*/
|
||||
private void setDragAction(Pane fileBox) {
|
||||
|
||||
// 进入
|
||||
fileBox.setOnDragEntered(dragEvent -> {
|
||||
var dragboard = dragEvent.getDragboard();
|
||||
if (dragboard.hasFiles() && isPboFile(dragboard.getFiles().getFirst())) {
|
||||
selectFileBox.setVisible(true);
|
||||
dragFileView.setVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
//离开
|
||||
fileBox.setOnDragExited(_ -> {
|
||||
selectFileBox.setVisible(false);
|
||||
dragFileView.setVisible(false);
|
||||
});
|
||||
|
||||
//
|
||||
fileBox.setOnDragOver(dragEvent -> {
|
||||
var 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 -> {
|
||||
dragFileView.setVisible(false);
|
||||
|
||||
var db = dragEvent.getDragboard();
|
||||
boolean success = false;
|
||||
var file = db.getFiles().getFirst();
|
||||
if (db.hasFiles() && isPboFile(file)) {
|
||||
selectFile(file);
|
||||
success = true;
|
||||
}
|
||||
/* 让源知道字符串是否已成功传输和使用 */
|
||||
dragEvent.setDropCompleted(success);
|
||||
|
||||
dragEvent.consume();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件选择器
|
||||
*/
|
||||
public void selectFile() {
|
||||
if (historySelectFolder != null)
|
||||
fileChooser.setInitialDirectory(historySelectFolder);
|
||||
selectFile(fileChooser.showOpenDialog(getWindow()));
|
||||
}
|
||||
|
||||
@ -215,6 +172,13 @@ public class MainController extends BaseController<MainViewModel> {
|
||||
ViewUtil.openDecorated("翻译设置", "setup/translate-view");
|
||||
}
|
||||
|
||||
/**
|
||||
* 帮助
|
||||
*/
|
||||
public void openHelp() {
|
||||
Context.openUrl("https://www.52pojie.cn/thread-1891962-1-1.html");
|
||||
}
|
||||
|
||||
/**
|
||||
* 关于
|
||||
*/
|
||||
@ -222,6 +186,26 @@ public class MainController extends BaseController<MainViewModel> {
|
||||
ViewUtil.openDecorated("关于", "about-view");
|
||||
}
|
||||
|
||||
public void startTranslate() {
|
||||
viewModel.startTranslate();
|
||||
}
|
||||
|
||||
public void startPack() {
|
||||
viewModel.pack();
|
||||
}
|
||||
|
||||
public void selectAllLog() {
|
||||
logArea.selectAll();
|
||||
}
|
||||
|
||||
public void copyLog() {
|
||||
logArea.copy();
|
||||
}
|
||||
|
||||
public void clearLog() {
|
||||
logArea.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载PBO文件
|
||||
*/
|
||||
@ -268,11 +252,15 @@ public class MainController extends BaseController<MainViewModel> {
|
||||
public void onPackOver(File packFile) {
|
||||
// 选择文件保存地址
|
||||
fileChooser.setInitialFileName(packFile.getName());
|
||||
if (historySaveFolder != null)
|
||||
fileChooser.setInitialDirectory(historySaveFolder);
|
||||
File file = fileChooser.showSaveDialog(getWindow());
|
||||
|
||||
if (file == null)
|
||||
return;
|
||||
|
||||
historySaveFolder = file.getParentFile();
|
||||
|
||||
if (file.exists()) {
|
||||
//文件已存在,则删除覆盖文件
|
||||
FileUtils.deleteQuietly(file);
|
||||
@ -289,32 +277,64 @@ public class MainController extends BaseController<MainViewModel> {
|
||||
}
|
||||
}
|
||||
|
||||
public void startTranslate() {
|
||||
viewModel.startTranslate();
|
||||
}
|
||||
// ======================================{ private }========================================
|
||||
|
||||
public void startPack() {
|
||||
viewModel.pack();
|
||||
}
|
||||
/**
|
||||
* 设置文件拖拽效果
|
||||
*/
|
||||
private void setDragAction(Pane fileBox) {
|
||||
|
||||
public void selectAllLog() {
|
||||
logArea.selectAll();
|
||||
}
|
||||
// 进入
|
||||
fileBox.setOnDragEntered(dragEvent -> {
|
||||
var dragboard = dragEvent.getDragboard();
|
||||
if (dragboard.hasFiles() && isPboFile(dragboard.getFiles().getFirst())) {
|
||||
selectFileBox.setVisible(true);
|
||||
dragFileView.setVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
public void copyLog() {
|
||||
logArea.copy();
|
||||
}
|
||||
//离开
|
||||
fileBox.setOnDragExited(_ -> {
|
||||
selectFileBox.setVisible(false);
|
||||
dragFileView.setVisible(false);
|
||||
});
|
||||
|
||||
public void clearLog() {
|
||||
logArea.clear();
|
||||
}
|
||||
//
|
||||
fileBox.setOnDragOver(dragEvent -> {
|
||||
var 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 -> {
|
||||
dragFileView.setVisible(false);
|
||||
|
||||
var db = dragEvent.getDragboard();
|
||||
boolean success = false;
|
||||
var file = db.getFiles().getFirst();
|
||||
if (db.hasFiles() && isPboFile(file)) {
|
||||
selectFile(file);
|
||||
success = true;
|
||||
}
|
||||
/* 让源知道字符串是否已成功传输和使用 */
|
||||
dragEvent.setDropCompleted(success);
|
||||
|
||||
dragEvent.consume();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开文件
|
||||
*/
|
||||
private void selectFile(File file) {
|
||||
if (file != null) {
|
||||
// 设置选择文件记录
|
||||
historySelectFolder = file.getParentFile();
|
||||
}
|
||||
viewModel.selectFile(file);
|
||||
viewModel.unpack();
|
||||
}
|
||||
|
||||
@ -37,10 +37,14 @@ public class AboutController extends BaseController<AboutViewModel> {
|
||||
}
|
||||
|
||||
public void openGitee() {
|
||||
Context.openUrl("https://gitee.com/octopus_yan/dayz-mod-translator");
|
||||
Context.openUrl("https://gitee.com/octopus_yan/dayz-mod-translator/releases");
|
||||
}
|
||||
|
||||
public void openGithub() {
|
||||
Context.openUrl("https://github.com/octopusYan/dayz-mod-translator");
|
||||
Context.openUrl("https://github.com/octopusYan/dayz-mod-translator/releases");
|
||||
}
|
||||
|
||||
public void openForum() {
|
||||
Context.openUrl("https://www.52pojie.cn/thread-1891962-1-1.html");
|
||||
}
|
||||
}
|
||||
|
||||
63
src/main/java/cn/octopusyan/dmt/model/WordCsvItem.java
Normal file
63
src/main/java/cn/octopusyan/dmt/model/WordCsvItem.java
Normal file
@ -0,0 +1,63 @@
|
||||
package cn.octopusyan.dmt.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* csv
|
||||
*
|
||||
* @author octopus_yan
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WordCsvItem extends WordItem {
|
||||
|
||||
/**
|
||||
* 是否规整(有些翻译列数不完整,无法正常分割)
|
||||
*/
|
||||
private boolean regular;
|
||||
|
||||
/**
|
||||
* csv中Language列文本
|
||||
* <p>
|
||||
* 当{@code regular}为{@code false}时,用于获取于csv原文内容,用于拼接格式化文本
|
||||
*/
|
||||
private String header;
|
||||
|
||||
/**
|
||||
* 原文(获取于csv繁体位置,用于替换翻译文本
|
||||
*/
|
||||
private String originalTrad;
|
||||
|
||||
/**
|
||||
* csv(规整)文本对象
|
||||
*
|
||||
* @param file 文件
|
||||
* @param lines 行数
|
||||
* @param original 原文
|
||||
* @param chinese 中文位置对应的文本
|
||||
* @param originalTrad 繁体中文位置对应的文本
|
||||
*/
|
||||
public WordCsvItem(File file, Integer lines, String original, String chinese, String originalTrad) {
|
||||
super(file, lines, 0, original, chinese);
|
||||
this.regular = true;
|
||||
this.originalTrad = originalTrad;
|
||||
}
|
||||
|
||||
/**
|
||||
* csv(不规整)文本对象
|
||||
* <p>
|
||||
*
|
||||
* @param file 文件
|
||||
* @param lines 行数
|
||||
* @param header Language列对应名称,用于拼接格式化文本
|
||||
* @param original 原文
|
||||
*/
|
||||
public WordCsvItem(File file, Integer lines, String header, String original) {
|
||||
super(file, lines, null, original, "");
|
||||
this.regular = false;
|
||||
this.header = header;
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,8 @@ public class WordItem {
|
||||
*/
|
||||
private StringProperty chineseProperty = new SimpleStringProperty();
|
||||
|
||||
public WordItem() {}
|
||||
|
||||
public WordItem(File file, Integer lines, Integer index, String original, String chinese) {
|
||||
this.file = file;
|
||||
this.lines = lines;
|
||||
|
||||
@ -37,11 +37,6 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
|
||||
return "https://fanyi.baidu.com/transapi";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int qps() {
|
||||
return source().getDefaultQps();
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译处理
|
||||
*
|
||||
|
||||
@ -25,11 +25,6 @@ public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
|
||||
return "https://translate.googleapis.com/translate_a/single";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int qps() {
|
||||
return source().getDefaultQps();
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译处理
|
||||
*
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
package cn.octopusyan.dmt.utils;
|
||||
|
||||
import cn.hutool.core.text.csv.*;
|
||||
import cn.octopusyan.dmt.common.config.Constants;
|
||||
import cn.octopusyan.dmt.common.config.Context;
|
||||
import cn.octopusyan.dmt.common.util.ProcessesUtil;
|
||||
import cn.octopusyan.dmt.model.WordCsvItem;
|
||||
import cn.octopusyan.dmt.model.WordItem;
|
||||
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.LineIterator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
@ -24,13 +32,14 @@ import java.util.stream.Collectors;
|
||||
* @author octopus_yan
|
||||
*/
|
||||
public class PBOUtil {
|
||||
private static final Logger log = LoggerFactory.getLogger(PBOUtil.class);
|
||||
public static final ConsoleLog consoleLog = ConsoleLog.getInstance(PBOUtil.class);
|
||||
|
||||
private static final ProcessesUtil processesUtil = ProcessesUtil.init(Constants.BIN_DIR_PATH);
|
||||
|
||||
private static final String UNPACK_COMMAND = STR."\{Constants.PBOC_FILE} unpack -o \{Constants.TMP_DIR_PATH} %s";
|
||||
private static final String PACK_COMMAND = STR."\{Constants.PBOC_FILE} pack -o %s %s";
|
||||
private static final String CFG_COMMAND = STR."\{Constants.CFG_CONVERT_FILE} %s -dst %s %s";
|
||||
private static final String UNPACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" unpack -o \"\{Constants.TMP_DIR_PATH}\" {}";
|
||||
private static final String PACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" pack -o {} {}";
|
||||
private static final String CFG_COMMAND = STR."\"\{Constants.CFG_CONVERT_FILE}\" {} -dst {} {}";
|
||||
|
||||
private static final String FILE_NAME_STRING_TABLE = "stringtable.csv";
|
||||
private static final String FILE_NAME_CONFIG_BIN = "config.bin";
|
||||
@ -42,14 +51,20 @@ public class PBOUtil {
|
||||
|
||||
|
||||
public static void init() {
|
||||
|
||||
String srcFilePath = Objects.requireNonNull(PBOUtil.class.getResource("/bin")).getPath();
|
||||
try {
|
||||
File destDir = new File(Constants.BIN_DIR_PATH);
|
||||
|
||||
if (destDir.exists()) return;
|
||||
|
||||
if (!Context.isDebugMode())
|
||||
throw new RuntimeException("Util 初始化失败");
|
||||
|
||||
String srcFilePath = Resources.getResource("bin").getPath();
|
||||
FileUtils.forceMkdir(destDir);
|
||||
FileUtils.copyDirectory(new File(srcFilePath), destDir);
|
||||
|
||||
} catch (IOException e) {
|
||||
consoleLog.error("Util 初始化失败", e);
|
||||
log.error("Util 初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +97,7 @@ public class PBOUtil {
|
||||
throw new RuntimeException("文件夹创建失败", e);
|
||||
}
|
||||
|
||||
String command = String.format(UNPACK_COMMAND, pboFile.getAbsolutePath());
|
||||
String command = ProcessesUtil.format(UNPACK_COMMAND, pboFile.getAbsolutePath());
|
||||
consoleLog.debug(STR."unpack command ==> [\{command}]");
|
||||
boolean exec = processesUtil.exec(command);
|
||||
if (!exec)
|
||||
@ -107,7 +122,7 @@ public class PBOUtil {
|
||||
FileUtils.deleteQuietly(packFile);
|
||||
}
|
||||
|
||||
String command = String.format(PACK_COMMAND, Constants.TMP_DIR_PATH, unpackPath);
|
||||
String command = ProcessesUtil.format(PACK_COMMAND, Constants.TMP_DIR_PATH, unpackPath);
|
||||
consoleLog.debug(STR."pack command ==> [\{command}]");
|
||||
|
||||
boolean exec = processesUtil.exec(command);
|
||||
@ -131,6 +146,10 @@ public class PBOUtil {
|
||||
return wordItems;
|
||||
|
||||
List<File> files = new ArrayList<>(FileUtils.listFiles(file, FILE_NAME_LIST, true));
|
||||
List<String> names = files.stream().map(File::getName).toList();
|
||||
if(names.contains("config.bin") && names.contains("config.cpp")) {
|
||||
files = files.stream().filter(item -> "config.cpp".equals(item.getName())).toList();
|
||||
}
|
||||
for (File item : files) {
|
||||
wordItems.addAll(findWordByFile(item));
|
||||
}
|
||||
@ -143,7 +162,7 @@ public class PBOUtil {
|
||||
*
|
||||
* @param wordFileMap 文件对应文本map
|
||||
*/
|
||||
public static void writeWords(Map<File, List<WordItem>> wordFileMap) {
|
||||
public static void writeWords(Map<File, List<WordItem>> wordFileMap) throws IOException {
|
||||
|
||||
for (Map.Entry<File, List<WordItem>> entry : wordFileMap.entrySet()) {
|
||||
|
||||
@ -154,41 +173,35 @@ public class PBOUtil {
|
||||
|
||||
// 需要转bin文件时,写入bak目录下cpp文件
|
||||
boolean hasBin = new File(outFilePath(file, ".bin")).exists();
|
||||
// 写入TMP下文件
|
||||
String writePath = file.getAbsolutePath().replace(Constants.BAK_DIR_PATH, Constants.TMP_DIR_PATH);
|
||||
File writeFile = hasBin ? file : new File(writePath);
|
||||
|
||||
AtomicInteger lineIndex = new AtomicInteger(0);
|
||||
List<String> lines = new ArrayList<>();
|
||||
List<String> lines;
|
||||
|
||||
consoleLog.info("正在写入文件[{}]", writeFile.getAbsolutePath());
|
||||
consoleLog.info("正在写入文件 => {}", writeFile.getAbsolutePath());
|
||||
|
||||
try (LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name())) {
|
||||
while (it.hasNext()) {
|
||||
String line = it.next();
|
||||
WordItem word = wordMap.get(lineIndex.get());
|
||||
|
||||
// 当前行是否有需要替换的文本
|
||||
// TODO 是否替换空文本
|
||||
if (word != null && line.contains(word.getOriginal())) {
|
||||
line = line.substring(0, word.getIndex()) +
|
||||
line.substring(word.getIndex()).replace(word.getOriginal(), word.getChinese());
|
||||
}
|
||||
|
||||
// 缓存行内容
|
||||
lines.add(line);
|
||||
|
||||
lineIndex.addAndGet(1);
|
||||
if (FILE_NAME_STRING_TABLE.equals(file.getName())) {
|
||||
// 写入 CSV 文件
|
||||
lines = writeCsv(file, it, wordMap);
|
||||
} else {
|
||||
// 写入 CPP 或 layout 文件
|
||||
lines = writeOther(it, wordMap);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
try {
|
||||
// 写入文件
|
||||
String charsets = writeFile.getName().endsWith(".layout") ? FileUtil.getCharsets(writeFile) : StandardCharsets.UTF_8.name();
|
||||
FileUtils.writeLines(writeFile, charsets, lines);
|
||||
} catch (IOException e) {
|
||||
consoleLog.error(STR."文件(\{writeFile.getAbsoluteFile()})写入失败", e);
|
||||
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]写入出错", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// CPP转BIN (覆盖TMP下BIN文件)
|
||||
@ -196,6 +209,98 @@ public class PBOUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 CPP 或 layout 文件
|
||||
*
|
||||
* @param it 行遍历器
|
||||
* @param wordMap 替换文本map
|
||||
* @return 待写入行文本列表
|
||||
*/
|
||||
private static List<String> writeOther(LineIterator it, Map<Integer, WordItem> wordMap) {
|
||||
AtomicInteger lineIndex = new AtomicInteger(0);
|
||||
List<String> lines = new ArrayList<>();
|
||||
while (it.hasNext()) {
|
||||
lineIndex.addAndGet(1);
|
||||
|
||||
String line = it.next();
|
||||
WordItem word = wordMap.get(lineIndex.get());
|
||||
|
||||
if (word != null && line.contains(word.getOriginal())) {
|
||||
line = line.substring(0, word.getIndex()) +
|
||||
line.substring(word.getIndex()).replace(word.getOriginal(), word.getChinese());
|
||||
}
|
||||
|
||||
lines.add(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 写入 CSV 文件
|
||||
*
|
||||
* @param it 行遍历器
|
||||
* @param wordMap 替换文本map
|
||||
* @return 待写入行文本列表
|
||||
*/
|
||||
private static List<String> writeCsv(File file, LineIterator it, Map<Integer, WordItem> wordMap) {
|
||||
AtomicInteger lineIndex = new AtomicInteger(0);
|
||||
List<String> lines = new ArrayList<>();
|
||||
|
||||
CsvReader reader = CsvUtil.getReader(CsvReadConfig.defaultConfig());
|
||||
CsvData data = reader.read(file);
|
||||
var rowMap = data.getRows().stream()
|
||||
.collect(Collectors.toMap(CsvRow::getOriginalLineNumber, Function.identity()));
|
||||
|
||||
while (it.hasNext()) {
|
||||
lineIndex.addAndGet(1);
|
||||
String line = it.next();
|
||||
|
||||
WordCsvItem word = (WordCsvItem) wordMap.get(lineIndex.get());
|
||||
|
||||
// 以 , 开头的行(视为内容带换行符,跳过)
|
||||
// ,,开头视为空值行(不跳过,尽量还原文本结构
|
||||
if (word == null && line.startsWith(",") && !line.startsWith(",,")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断当前行是否有需要替换的文本
|
||||
if (word != null && line.contains(word.getOriginal())) {
|
||||
|
||||
// 是否规整(可简单读取的)
|
||||
if (word.isRegular()) {
|
||||
CsvRow strings = rowMap.get(Integer.valueOf(lineIndex.get()).longValue() - 1L);
|
||||
// 替换翻译文本
|
||||
strings.set(11, word.getChinese());// 繁体
|
||||
strings.set(14, word.getChinese());// 简体
|
||||
line = strings.stream().map(item -> STR."\"\{item}\"").collect(Collectors.joining(","));
|
||||
|
||||
// 处理带换行符文本
|
||||
var length = line.split("\r\n|\r|\n").length;
|
||||
for (int i = 1; i < length; i++) {
|
||||
lineIndex.addAndGet(1);
|
||||
String next = it.next();
|
||||
consoleLog.debug(STR."next => \"\{next}\"");
|
||||
}
|
||||
} else {
|
||||
// 不规整的直接原文填充
|
||||
// Language,original,english,czech,german,russian,polish,hungarian,italian,spanish,french,chinese,japanese,portuguese,chinesesimp
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\"").append(word.getHeader()).append("\"");
|
||||
for (int i = 1; i < 15; i++) {
|
||||
String str = (i == 11 || i == 14) ? word.getChinese() : word.getOriginal();
|
||||
sb.append(",\"").append(str).append("\"");
|
||||
}
|
||||
line = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找文件内可翻译文本
|
||||
*
|
||||
@ -228,7 +333,7 @@ public class PBOUtil {
|
||||
}
|
||||
// CSV
|
||||
if (FILE_NAME_STRING_TABLE.equals(file.getName())) {
|
||||
return findWordByCSV(file, it);
|
||||
return findWordByCSV(file);
|
||||
}
|
||||
// layout
|
||||
if (file.getName().endsWith(".layout")) {
|
||||
@ -245,37 +350,39 @@ public class PBOUtil {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从csv文件中读取可翻译文本
|
||||
*
|
||||
* @param file csv文件
|
||||
* @param it 行内容遍历器
|
||||
* @return 可翻译文本列表
|
||||
*/
|
||||
private static List<WordItem> findWordByCSV(File file, LineIterator it) {
|
||||
|
||||
private static List<WordItem> findWordByCSV(File file) {
|
||||
ArrayList<WordItem> wordItems = new ArrayList<>();
|
||||
AtomicInteger lines = new AtomicInteger(0);
|
||||
int index = -1;
|
||||
String line;
|
||||
while (it.hasNext()) {
|
||||
line = it.next();
|
||||
List<String> split = Arrays.stream(line.split(",")).toList();
|
||||
|
||||
if (lines.get() == 0) {
|
||||
index = split.indexOf("\"chinese\"");
|
||||
} else if (index < split.size()) {
|
||||
// 原文
|
||||
String original = split.get(index).replaceAll("\"", "");
|
||||
// 开始下标
|
||||
Integer startIndex = line.indexOf(original);
|
||||
// 添加单词
|
||||
if (original.length() > 1) {
|
||||
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||
}
|
||||
CsvReadConfig config = CsvReadConfig.defaultConfig().setTrimField(true).setContainsHeader(true);
|
||||
CsvReader reader = CsvUtil.getReader(config);
|
||||
CsvData data = reader.read(file);
|
||||
|
||||
// 读取CSV
|
||||
List<CsvRow> rows = data.getRows();
|
||||
for (CsvRow row : rows) {
|
||||
WordItem item;
|
||||
int lines = (int) (row.getOriginalLineNumber() + 1);
|
||||
|
||||
String original = row.get(1);
|
||||
// 跳过原文为空的行
|
||||
if (StringUtils.isEmpty(original)) continue;
|
||||
|
||||
// 是否可格式化读取
|
||||
if (row.size() >= 15) {
|
||||
|
||||
String chinese = row.get(14);
|
||||
|
||||
// 已有中文翻译,则跳过
|
||||
if (containsChinese(chinese)) continue;
|
||||
|
||||
item = new WordCsvItem(file, lines, original, chinese, row.get(11));
|
||||
} else {
|
||||
item = new WordCsvItem(file, lines, row.getFirst(), original);
|
||||
}
|
||||
|
||||
lines.addAndGet(1);
|
||||
wordItems.add(item);
|
||||
}
|
||||
|
||||
return wordItems;
|
||||
}
|
||||
|
||||
@ -292,11 +399,17 @@ public class PBOUtil {
|
||||
String line;
|
||||
Matcher matcher;
|
||||
while (it.hasNext()) {
|
||||
lines.addAndGet(1);
|
||||
line = it.next();
|
||||
matcher = LAYOUT_PATTERN.matcher(line);
|
||||
if (StringUtils.isNoneEmpty(line) && matcher.matches()) {
|
||||
// 原文
|
||||
String original = matcher.group(1);
|
||||
|
||||
if (StringUtils.isEmpty(line) || !matcher.matches())
|
||||
continue;
|
||||
|
||||
// 原文
|
||||
String original = matcher.group(1);
|
||||
|
||||
if (!original.startsWith("#")) {
|
||||
// 开始下标
|
||||
Integer startIndex = line.indexOf(original);
|
||||
// 添加单词
|
||||
@ -304,8 +417,6 @@ public class PBOUtil {
|
||||
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||
}
|
||||
}
|
||||
|
||||
lines.addAndGet(1);
|
||||
}
|
||||
return wordItems;
|
||||
}
|
||||
@ -322,24 +433,23 @@ public class PBOUtil {
|
||||
AtomicInteger lines = new AtomicInteger(0);
|
||||
|
||||
while (it.hasNext()) {
|
||||
String line = it.next();
|
||||
|
||||
Matcher matcher = CPP_PATTERN.matcher(line);
|
||||
if (!line.contains("$") && matcher.matches()) {
|
||||
|
||||
String name = matcher.group(1);
|
||||
|
||||
// 原始文本
|
||||
int startIndex = line.indexOf(name) + name.length();
|
||||
String original = matcher.group(2);
|
||||
|
||||
// 添加单词
|
||||
if (original.length() > 1) {
|
||||
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||
}
|
||||
}
|
||||
|
||||
lines.addAndGet(1);
|
||||
String line = it.next();
|
||||
Matcher matcher = CPP_PATTERN.matcher(line);
|
||||
|
||||
// 不匹配 或 是变量 则跳过
|
||||
if (!matcher.matches() || line.contains("$") || line.contains("#"))
|
||||
continue;
|
||||
|
||||
String name = matcher.group(1);
|
||||
// 原始文本
|
||||
int startIndex = line.indexOf(name) + name.length();
|
||||
String original = matcher.group(2);
|
||||
|
||||
// 添加单词
|
||||
if (original.length() > 1) {
|
||||
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||
}
|
||||
}
|
||||
return wordItems;
|
||||
}
|
||||
@ -390,7 +500,7 @@ public class PBOUtil {
|
||||
private static String toBinCommand(File cppFile) {
|
||||
String outFilePath = outFilePath(cppFile, ".bin");
|
||||
outFilePath = outFilePath.replace(Constants.BAK_DIR_PATH, Constants.TMP_DIR_PATH);
|
||||
return String.format(CFG_COMMAND, "-bin", outFilePath, cppFile.getAbsolutePath());
|
||||
return ProcessesUtil.format(CFG_COMMAND, "-bin", outFilePath, cppFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -398,10 +508,20 @@ public class PBOUtil {
|
||||
*/
|
||||
private static String toTxtCommand(File binFile) {
|
||||
String outFilePath = outFilePath(binFile, ".cpp");
|
||||
return String.format(CFG_COMMAND, "-txt", outFilePath, binFile.getAbsolutePath());
|
||||
return ProcessesUtil.format(CFG_COMMAND, "-txt", outFilePath, binFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private static String outFilePath(File file, String suffix) {
|
||||
return file.getParentFile().getAbsolutePath() + File.separator + FileUtil.mainName(file) + suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否含有中文
|
||||
*
|
||||
* @param str 需要判断的字符串
|
||||
* @return 是否含有中文
|
||||
*/
|
||||
public static boolean containsChinese(String str) {
|
||||
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package cn.octopusyan.dmt.view;
|
||||
|
||||
import cn.octopusyan.dmt.common.config.Context;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.TextArea;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -23,8 +23,6 @@ public class ConsoleLog {
|
||||
private static Logger markerLog;
|
||||
private static TextArea logArea;
|
||||
private final String tag;
|
||||
@Setter
|
||||
private static boolean showDebug = false;
|
||||
|
||||
public static void init(TextArea logArea) {
|
||||
ConsoleLog.logArea = logArea;
|
||||
@ -56,7 +54,7 @@ public class ConsoleLog {
|
||||
}
|
||||
|
||||
public void debug(String message, Object... param) {
|
||||
if (!showDebug) return;
|
||||
if (!Context.isDebugMode()) return;
|
||||
printLog(tag, Level.DEBUG, message, param);
|
||||
}
|
||||
|
||||
|
||||
@ -64,10 +64,7 @@ public class EditButtonTableCell extends TableCell<WordItem, WordItem> {
|
||||
if (empty) {
|
||||
setGraphic(null);
|
||||
} else {
|
||||
/*
|
||||
* TODO 添加多个操作按钮
|
||||
* setGraphic(Hbox(btn1,btn2));
|
||||
*/
|
||||
// 添加多个操作按钮
|
||||
setGraphic(new HBox(edit, translate));
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,13 +10,15 @@ import cn.octopusyan.dmt.task.UnpackTask;
|
||||
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||
import cn.octopusyan.dmt.translate.DelayWord;
|
||||
import cn.octopusyan.dmt.translate.TranslateUtil;
|
||||
import cn.octopusyan.dmt.utils.PBOUtil;
|
||||
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
@ -24,7 +26,6 @@ import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 主界面
|
||||
@ -103,12 +104,14 @@ public class MainViewModel extends BaseViewModel<MainController> {
|
||||
* 开始翻译
|
||||
*/
|
||||
public void startTranslate() {
|
||||
if(wordItems.isEmpty()) return;
|
||||
if (wordItems.isEmpty()) return;
|
||||
|
||||
if (translateTask == null) {
|
||||
List<WordItem> words = wordItems.stream().filter(item -> StringUtils.isEmpty(item.getChinese())).toList();
|
||||
List<WordItem> words = wordItems.stream().filter(item -> !PBOUtil.containsChinese(item.getChinese())).toList();
|
||||
delayQueue = TranslateUtil.getDelayQueue(words);
|
||||
translateTask = createTask();
|
||||
translateTask.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!translateTask.isRunning()) {
|
||||
@ -135,7 +138,7 @@ public class MainViewModel extends BaseViewModel<MainController> {
|
||||
* 打包
|
||||
*/
|
||||
public void pack() {
|
||||
if(wordItems.isEmpty()) return;
|
||||
if (wordItems.isEmpty()) return;
|
||||
|
||||
PackTask packTask = new PackTask(wordItems, unpackPath);
|
||||
packTask.onListen(new PackTask.PackListener() {
|
||||
@ -149,6 +152,13 @@ public class MainViewModel extends BaseViewModel<MainController> {
|
||||
Platform.runLater(() -> controller.onPackOver(file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(Throwable throwable) {
|
||||
super.onFailed(throwable);
|
||||
Platform.runLater(() -> {
|
||||
AlertUtil.getInstance().exception(new RuntimeException(throwable)).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
packTask.execute();
|
||||
}
|
||||
@ -192,19 +202,12 @@ public class MainViewModel extends BaseViewModel<MainController> {
|
||||
private void resetProgress() {
|
||||
translateTask = null;
|
||||
controller.translate.setGraphic(startIcon);
|
||||
Styles.toggleStyleClass(controller.translateProgress, Styles.SMALL);
|
||||
ObservableList<String> styleClass = controller.translateProgress.getStyleClass();
|
||||
if (!styleClass.contains(Styles.SMALL)) {
|
||||
Styles.toggleStyleClass(controller.translateProgress, Styles.SMALL);
|
||||
}
|
||||
controller.translateProgress.progressProperty().unbind();
|
||||
controller.translateProgress.setProgress(0);
|
||||
controller.translateProgress.setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否含有中文
|
||||
*
|
||||
* @param str 需要判断的字符串
|
||||
* @return 是否含有中文
|
||||
*/
|
||||
private boolean containsChinese(String str) {
|
||||
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ module cn.octopusyan.dmt {
|
||||
requires java.prefs;
|
||||
requires org.kordamp.ikonli.javafx;
|
||||
requires org.kordamp.ikonli.feather;
|
||||
requires java.management;
|
||||
requires cn.hutool.core;
|
||||
|
||||
exports cn.octopusyan.dmt;
|
||||
exports cn.octopusyan.dmt.model to com.fasterxml.jackson.databind;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#root #titleBox {
|
||||
-fx-padding: 0 0 0 120;
|
||||
-fx-padding: 0 0 0 70;
|
||||
-fx-background-color: -color-neutral-muted;
|
||||
}
|
||||
|
||||
@ -9,6 +9,6 @@
|
||||
}
|
||||
|
||||
#root #infoBox {
|
||||
-fx-spacing: 25;
|
||||
-fx-padding: 50 0 0 120;
|
||||
-fx-spacing: 20;
|
||||
-fx-padding: 30 0 0 70;
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
<VBox xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="cn.octopusyan.dmt.controller.help.AboutController"
|
||||
stylesheets="@../css/about-view.css"
|
||||
fx:id="root" prefHeight="400.0" prefWidth="600.0">
|
||||
fx:id="root" prefHeight="350.0" prefWidth="500.0">
|
||||
|
||||
<VBox id="titleBox" alignment="CENTER_LEFT" prefWidth="${root.width}" prefHeight="100">
|
||||
<Label fx:id="title" text="title"/>
|
||||
@ -21,5 +21,9 @@
|
||||
<Hyperlink text="Gitee" onAction="#openGitee"/>
|
||||
<Hyperlink text="Github" onAction="#openGithub"/>
|
||||
</HBox>
|
||||
<HBox spacing="20">
|
||||
<Label text="发布地址"/>
|
||||
<Hyperlink text="52pojie.cn" onAction="#openForum"/>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</VBox>
|
||||
|
||||
@ -35,6 +35,11 @@
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="帮助">
|
||||
<MenuItem text="帮助" onAction="#openHelp">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="fth-help-circle"/>
|
||||
</graphic>
|
||||
</MenuItem>
|
||||
<MenuItem text="关于" onAction="#openAbout">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="fth-info"/>
|
||||
|
||||
Reference in New Issue
Block a user