20 Commits

Author SHA1 Message Date
aa5ad064a8 feat: 添加微软翻译接口 2026-01-23 22:51:00 +08:00
36bd572e38 fix: 更新百度翻译接口;一些小修改 2026-01-23 22:50:42 +08:00
2b8f30b72d feat: 更新依赖 2026-01-23 22:40:05 +08:00
a4ff07ce7b fix: 接口qps设置 2025-09-20 19:50:34 +08:00
3ed4979aa9 feat: 打开文件历史 2024-12-23 03:54:02 +08:00
16aa917d09 chore: rename DownloadBodyHandler 2024-12-21 22:09:08 +08:00
5214485a36 fix: 包内同时有bin和cpp文件时,写入翻译报错 2024-12-16 13:08:30 +08:00
5a9226901f feat: 使用hutool csv工具 2024-12-16 12:46:07 +08:00
223abc7ac5 style: 缩进 2024-12-01 22:14:32 +08:00
1294fb5414 chore: readme 2024-12-01 14:53:55 +08:00
a10636c772 chore: clean up 2024-11-22 00:03:25 +08:00
c6f0268ff5 fix: 修复当前文本翻译完成后,再次加载pbo后,无法开始翻译任务的问题 2024-11-22 00:01:54 +08:00
0c9052216b fix: csv连续双引号处理 2024-11-22 00:00:46 +08:00
904f37a15b fix: csv翻译文本判断 2024-11-21 17:47:05 +08:00
e6dd1c39f8 perf: 优化对不规整CSV内容的处理 2024-11-21 16:57:27 +08:00
912e11e673 perf: 识别cpp文件内容时跳过带#号内容 2024-11-19 20:22:31 +08:00
8a6ef06ae6 fix: csv文本带引号时替换会报错的问题 2024-11-19 20:19:37 +08:00
3cb5637972 perf: 读取layout时 忽略#号开头文本 2024-11-18 23:10:13 +08:00
cb5e0229ff fix: 修复二次加载pbo时,翻译进度条变粗的问题 2024-11-18 17:26:03 +08:00
c6ad66bb59 fix: 执行带空格路径命令的错误 2024-11-16 01:07:10 +08:00
21 changed files with 495 additions and 188 deletions

View File

@ -8,9 +8,6 @@
<br> <br>
[![license](https://img.shields.io/github/license/octopusYan/dayz-mod-translator)](https://github.com/octopusYan/dayz-mod-translator) [![license](https://img.shields.io/github/license/octopusYan/dayz-mod-translator)](https://github.com/octopusYan/dayz-mod-translator)
![commit](https://img.shields.io/github/commit-activity/m/octopusYan/dayz-mod-translator?color=%23ff69b4) ![commit](https://img.shields.io/github/commit-activity/m/octopusYan/dayz-mod-translator?color=%23ff69b4)
<br>
![stars](https://img.shields.io/github/stars/octopusYan/dayz-mod-translator?style=social)
![GitHub all releases](https://img.shields.io/github/downloads/octopusYan/dayz-mod-translator/total?style=social)
<br> <br>

44
pom.xml
View File

@ -30,15 +30,16 @@
<junit.version>5.11.0</junit.version> <junit.version>5.11.0</junit.version>
<javafx.version>21.0.4</javafx.version> <javafx.version>21.0.4</javafx.version>
<slf4j.version>2.0.16</slf4j.version> <slf4j.version>2.0.17</slf4j.version>
<logback.version>1.5.7</logback.version> <logback.version>1.5.25</logback.version>
<fastjson.version>2.0.52</fastjson.version> <fastjson.version>2.0.52</fastjson.version>
<common-lang3.version>3.16.0</common-lang3.version> <common-lang3.version>3.20.0</common-lang3.version>
<common-io.version>2.17.0</common-io.version> <common-io.version>2.21.0</common-io.version>
<common-exec.version>1.4.0</common-exec.version> <common-exec.version>1.6.0</common-exec.version>
<lombok.version>1.18.32</lombok.version> <lombok.version>1.18.42</lombok.version>
<jackson.version>2.15.4</jackson.version> <jackson.version>3.0.3</jackson.version>
<ikonli.version>12.3.1</ikonli.version> <ikonli.version>12.4.0</ikonli.version>
<jjwt.version>0.13.0</jjwt.version>
</properties> </properties>
<dependencies> <dependencies>
@ -66,11 +67,13 @@
<version>2.0.1</version> <version>2.0.1</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version> <version>${slf4j.version}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
@ -124,10 +127,20 @@
</dependency> </dependency>
<!-- jackson --> <!-- jackson -->
<!-- Source: https://mvnrepository.com/artifact/tools.jackson.dataformat/jackson-dataformat-yaml -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId> <artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version> <version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<!-- Hutool -->
<!-- https://hutool.cn -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.43</version>
</dependency> </dependency>
<!-- https://kordamp.org/ikonli/ --> <!-- https://kordamp.org/ikonli/ -->
@ -143,6 +156,19 @@
<groupId>org.kordamp.ikonli</groupId> <groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-feather-pack</artifactId> <artifactId>ikonli-feather-pack</artifactId>
</dependency> </dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>

View File

@ -12,14 +12,15 @@ import cn.octopusyan.dmt.model.Translate;
import cn.octopusyan.dmt.model.UpgradeConfig; import cn.octopusyan.dmt.model.UpgradeConfig;
import cn.octopusyan.dmt.translate.TranslateApi; import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import javafx.application.Platform; import javafx.application.Platform;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.dataformat.yaml.YAMLMapper;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -38,7 +39,12 @@ import java.util.stream.Collectors;
*/ */
public class ConfigManager { public class ConfigManager {
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class); private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
public static ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
private static final ObjectMapper objectMapper = YAMLMapper.builder()
//对象的所有字段全部列入序列化
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
.changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL))
.build();
public static final UpgradeConfig upgradeConfig = new UpgradeConfig(); public static final UpgradeConfig upgradeConfig = new UpgradeConfig();
public static final String DEFAULT_THEME = new PrimerLight().getName(); public static final String DEFAULT_THEME = new PrimerLight().getName();
@ -53,11 +59,6 @@ public class ConfigManager {
private static ConfigModel configModel; private static ConfigModel configModel;
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
public static void load() { public static void load() {
configModel = loadConfig(Constants.CONFIG_FILE_PATH, ConfigModel.class); configModel = loadConfig(Constants.CONFIG_FILE_PATH, ConfigModel.class);
if (configModel == null) if (configModel == null)
@ -89,7 +90,7 @@ public class ConfigManager {
public static void save() { public static void save() {
try { try {
objectMapper.writeValue(new File(Constants.CONFIG_FILE_PATH), configModel); objectMapper.writeValue(new File(Constants.CONFIG_FILE_PATH), configModel);
} catch (IOException e) { } catch (JacksonException e) {
logger.error("save config error", e); logger.error("save config error", e);
} }
} }

View File

@ -1,10 +1,10 @@
package cn.octopusyan.dmt.common.manager.http; package cn.octopusyan.dmt.common.manager.http;
import cn.octopusyan.dmt.common.enums.ProxySetup; 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.common.util.JsonUtil;
import cn.octopusyan.dmt.model.ProxyInfo; import cn.octopusyan.dmt.model.ProxyInfo;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.io.File; import java.io.File;
@ -111,8 +111,15 @@ public class HttpUtil {
} }
public String postForm(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException { public String postForm(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
return postForm(uri, header, param, null);
}
public String postForm(String uri, JsonNode header, JsonNode param, JsonNode body) throws IOException, InterruptedException {
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header) HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
.POST(HttpRequest.BodyPublishers.noBody()); .POST(body == null ?
HttpRequest.BodyPublishers.noBody() :
HttpRequest.BodyPublishers.ofString(JsonUtil.toJsonString(body))
);
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return response.body(); return response.body();
@ -130,7 +137,7 @@ public class HttpUtil {
} }
// 下载处理器 // 下载处理器
var handler = BodyHandler.create( var handler = DownloadBodyHandler.create(
Path.of(savePath), Path.of(savePath),
StandardOpenOption.CREATE, StandardOpenOption.WRITE StandardOpenOption.CREATE, StandardOpenOption.WRITE
); );
@ -172,8 +179,8 @@ public class HttpUtil {
for (Map.Entry<String, JsonNode> property : params.properties()) { for (Map.Entry<String, JsonNode> property : params.properties()) {
String key = property.getKey(); String key = property.getKey();
JsonNode value = params.get(key); JsonNode value = params.get(key);
if (value.isTextual()) { if (value.isString()) {
String value_ = URLEncoder.encode(String.valueOf(value.asText()), StandardCharsets.UTF_8); String value_ = URLEncoder.encode(String.valueOf(value.asString()), StandardCharsets.UTF_8);
formParams.append("&").append(key).append("=").append(value_); formParams.append("&").append(key).append("=").append(value_);
} else if (value.isNumber()) { } else if (value.isNumber()) {
formParams.append("&").append(key).append("=").append(value); formParams.append("&").append(key).append("=").append(value);

View File

@ -21,16 +21,16 @@ import java.util.function.Consumer;
* @author octopus_yan * @author octopus_yan
*/ */
@Slf4j @Slf4j
public class BodyHandler implements HttpResponse.BodyHandler<Path> { public class DownloadBodyHandler implements HttpResponse.BodyHandler<Path> {
private final HttpResponse.BodyHandler<Path> handler; private final HttpResponse.BodyHandler<Path> handler;
private BiConsumer<Long, Long> consumer; private BiConsumer<Long, Long> consumer;
private BodyHandler(HttpResponse.BodyHandler<Path> handler) { private DownloadBodyHandler(HttpResponse.BodyHandler<Path> handler) {
this.handler = handler; this.handler = handler;
} }
public static BodyHandler create(Path directory, OpenOption... openOptions) { public static DownloadBodyHandler create(Path directory, OpenOption... openOptions) {
return new BodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions)); return new DownloadBodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
} }
@Override @Override

View File

@ -1,18 +1,20 @@
package cn.octopusyan.dmt.common.util; package cn.octopusyan.dmt.common.util;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import tools.jackson.core.JacksonException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.json.JsonMapper;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.TimeZone;
/** /**
* Jackson 封装工具类 * Jackson 封装工具类
@ -21,25 +23,26 @@ import java.text.SimpleDateFormat;
*/ */
public class JsonUtil { public class JsonUtil {
private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
/** /**
* 时间日期格式 * 时间日期格式
*/ */
private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
static { private static final ObjectMapper objectMapper = JsonMapper.builder()
//对象的所有字段全部列入序列化 //对象的所有字段全部列入序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
//取消默认转换timestamps形式 .changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL))
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); //取消默认转换timestamps形式
//忽略空Bean转json的错误 .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); //忽略空Bean转json的错误
//所有的日期格式都统一为以下的格式即yyyy-MM-dd HH:mm:ss .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT)); //忽略 在json字符串中存在但在java对象中不存在对应属性的情况。防止错误
//忽略 在json字符串中存在但在java对象中不存在对应属性的情况。防止错误 .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); //所有的日期格式都统一为以下的格式即yyyy-MM-dd HH:mm:ss
} .defaultDateFormat(new SimpleDateFormat(STANDARD_FORMAT))
.defaultTimeZone(TimeZone.getDefault())
.build();
/** /**
* Json字符串 转 JavaBean * Json字符串 转 JavaBean
@ -53,7 +56,7 @@ public class JsonUtil {
T t = null; T t = null;
try { try {
t = objectMapper.readValue(jsonString, clazz); t = objectMapper.readValue(jsonString, clazz);
} catch (JsonProcessingException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return t; return t;
@ -71,7 +74,7 @@ public class JsonUtil {
T t = null; T t = null;
try { try {
t = objectMapper.readValue(file, clazz); t = objectMapper.readValue(file, clazz);
} catch (IOException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return t; return t;
@ -89,7 +92,7 @@ public class JsonUtil {
T t = null; T t = null;
try { try {
t = objectMapper.readValue(jsonArray, reference); t = objectMapper.readValue(jsonArray, reference);
} catch (JsonProcessingException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return t; return t;
@ -106,7 +109,7 @@ public class JsonUtil {
String jsonString = null; String jsonString = null;
try { try {
jsonString = objectMapper.writeValueAsString(object); jsonString = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return jsonString; return jsonString;
@ -122,7 +125,7 @@ public class JsonUtil {
byte[] bytes = null; byte[] bytes = null;
try { try {
bytes = objectMapper.writeValueAsBytes(object); bytes = objectMapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return bytes; return bytes;
@ -137,7 +140,7 @@ public class JsonUtil {
public static void objectToFile(File file, Object object) { public static void objectToFile(File file, Object object) {
try { try {
objectMapper.writeValue(file, object); objectMapper.writeValue(file, object);
} catch (Exception e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
} }
@ -153,7 +156,7 @@ public class JsonUtil {
JsonNode jsonNode = null; JsonNode jsonNode = null;
try { try {
jsonNode = objectMapper.readTree(jsonString); jsonNode = objectMapper.readTree(jsonString);
} catch (JsonProcessingException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return jsonNode; return jsonNode;
@ -179,7 +182,7 @@ public class JsonUtil {
String jsonString = null; String jsonString = null;
try { try {
jsonString = objectMapper.writeValueAsString(jsonNode); jsonString = objectMapper.writeValueAsString(jsonNode);
} catch (JsonProcessingException e) { } catch (JacksonException e) {
log.error("失败:{}", e.getMessage()); log.error("失败:{}", e.getMessage());
} }
return jsonString; return jsonString;

View File

@ -89,6 +89,9 @@ public class MainController extends BaseController<MainViewModel> {
fileChooser.getExtensionFilters().add(extFilter); fileChooser.getExtensionFilters().add(extFilter);
} }
private File historySelectFolder;
private File historySaveFolder;
@Override @Override
public Pane getRootPanel() { public Pane getRootPanel() {
return root; return root;
@ -150,6 +153,8 @@ public class MainController extends BaseController<MainViewModel> {
* 打开文件选择器 * 打开文件选择器
*/ */
public void selectFile() { public void selectFile() {
if (historySelectFolder != null)
fileChooser.setInitialDirectory(historySelectFolder);
selectFile(fileChooser.showOpenDialog(getWindow())); selectFile(fileChooser.showOpenDialog(getWindow()));
} }
@ -247,11 +252,15 @@ public class MainController extends BaseController<MainViewModel> {
public void onPackOver(File packFile) { public void onPackOver(File packFile) {
// 选择文件保存地址 // 选择文件保存地址
fileChooser.setInitialFileName(packFile.getName()); fileChooser.setInitialFileName(packFile.getName());
if (historySaveFolder != null)
fileChooser.setInitialDirectory(historySaveFolder);
File file = fileChooser.showSaveDialog(getWindow()); File file = fileChooser.showSaveDialog(getWindow());
if (file == null) if (file == null)
return; return;
historySaveFolder = file.getParentFile();
if (file.exists()) { if (file.exists()) {
//文件已存在,则删除覆盖文件 //文件已存在,则删除覆盖文件
FileUtils.deleteQuietly(file); FileUtils.deleteQuietly(file);
@ -322,6 +331,10 @@ public class MainController extends BaseController<MainViewModel> {
* 打开文件 * 打开文件
*/ */
private void selectFile(File file) { private void selectFile(File file) {
if (file != null) {
// 设置选择文件记录
historySelectFolder = file.getParentFile();
}
viewModel.selectFile(file); viewModel.selectFile(file);
viewModel.unpack(); viewModel.unpack();
} }

View 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;
}
}

View File

@ -34,6 +34,8 @@ public class WordItem {
*/ */
private StringProperty chineseProperty = new SimpleStringProperty(); private StringProperty chineseProperty = new SimpleStringProperty();
public WordItem() {}
public WordItem(File file, Integer lines, Integer index, String original, String chinese) { public WordItem(File file, Integer lines, Integer index, String original, String chinese) {
this.file = file; this.file = file;
this.lines = lines; this.lines = lines;

View File

@ -6,8 +6,8 @@ import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.model.UpgradeConfig; import cn.octopusyan.dmt.model.UpgradeConfig;
import cn.octopusyan.dmt.task.base.BaseTask; import cn.octopusyan.dmt.task.base.BaseTask;
import cn.octopusyan.dmt.task.listener.DefaultTaskListener; import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
import com.fasterxml.jackson.databind.JsonNode; import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.StringUtils; import tools.jackson.databind.JsonNode;
/** /**
* 检查更新任务 * 检查更新任务
@ -28,10 +28,10 @@ public class UpgradeTask extends BaseTask<UpgradeTask.UpgradeListener> {
JsonNode response = JsonUtil.parseJsonObject(responseStr); JsonNode response = JsonUtil.parseJsonObject(responseStr);
// TODO 校验返回内容 // TODO 校验返回内容
String newVersion = response.get("tag_name").asText(); String newVersion = response.get("tag_name").asString();
if (listener != null) if (listener != null)
listener.onChecked(!StringUtils.equals(upgradeConfig.getVersion(), newVersion), newVersion); listener.onChecked(!Strings.CS.equals(upgradeConfig.getVersion(), newVersion), newVersion);
} }
/** /**

View File

@ -12,6 +12,7 @@ import lombok.Getter;
public enum TranslateApi { public enum TranslateApi {
FREE_BAIDU("free_baidu", "百度", false), FREE_BAIDU("free_baidu", "百度", false),
FREE_GOOGLE("free_google", "谷歌", false), FREE_GOOGLE("free_google", "谷歌", false),
FREE_MICROSOFT("free_microsoft", "微软", false),
BAIDU("baidu", "百度(需认证)", true), BAIDU("baidu", "百度(需认证)", true),
; ;

View File

@ -3,10 +3,7 @@ package cn.octopusyan.dmt.translate.factory;
import cn.octopusyan.dmt.model.WordItem; import cn.octopusyan.dmt.model.WordItem;
import cn.octopusyan.dmt.translate.DelayWord; import cn.octopusyan.dmt.translate.DelayWord;
import cn.octopusyan.dmt.translate.TranslateApi; import cn.octopusyan.dmt.translate.TranslateApi;
import cn.octopusyan.dmt.translate.processor.AbstractTranslateProcessor; import cn.octopusyan.dmt.translate.processor.*;
import cn.octopusyan.dmt.translate.processor.BaiduTranslateProcessor;
import cn.octopusyan.dmt.translate.processor.FreeBaiduTranslateProcessor;
import cn.octopusyan.dmt.translate.processor.FreeGoogleTranslateProcessor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.*; import java.util.*;
@ -38,6 +35,7 @@ public class TranslateFactoryImpl implements TranslateFactory {
private void initProcessor() { private void initProcessor() {
processorList.addAll(Arrays.asList( processorList.addAll(Arrays.asList(
new FreeGoogleTranslateProcessor(), new FreeGoogleTranslateProcessor(),
new FreeMicrosoftTranslateProcessor(),
new FreeBaiduTranslateProcessor(), new FreeBaiduTranslateProcessor(),
new BaiduTranslateProcessor() new BaiduTranslateProcessor()
)); ));

View File

@ -3,7 +3,7 @@ package cn.octopusyan.dmt.translate.processor;
import cn.octopusyan.dmt.common.util.JsonUtil; import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.ApiKey; import cn.octopusyan.dmt.translate.ApiKey;
import cn.octopusyan.dmt.translate.TranslateApi; import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -59,7 +59,7 @@ public class BaiduTranslateProcessor extends AbstractTranslateProcessor {
throw new RuntimeException(String.valueOf(errorMsg)); throw new RuntimeException(String.valueOf(errorMsg));
} }
return json.get("trans_result").get(0).get("dst").asText(); return json.get("trans_result").get(0).get("dst").asString();
} }
private String getSign(String appid, String q, String salt) { private String getSign(String appid, String q, String salt) {

View File

@ -3,7 +3,7 @@ package cn.octopusyan.dmt.translate.processor;
import cn.octopusyan.dmt.common.manager.http.CookieManager; import cn.octopusyan.dmt.common.manager.http.CookieManager;
import cn.octopusyan.dmt.common.util.JsonUtil; import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.TranslateApi; import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.net.HttpCookie; import java.net.HttpCookie;
@ -23,6 +23,7 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
static { static {
header.put("Origin", "https://fanyi.baidu.com"); header.put("Origin", "https://fanyi.baidu.com");
header.put("acs-token", "");
header.put("Referer", "https://fanyi.baidu.com"); header.put("Referer", "https://fanyi.baidu.com");
header.put("User-Agent", "Apifox/1.0.0 (https://apifox.com)"); header.put("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
header.put("Accept","*/*"); header.put("Accept","*/*");
@ -37,11 +38,6 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
return "https://fanyi.baidu.com/transapi"; return "https://fanyi.baidu.com/transapi";
} }
@Override
public int qps() {
return source().getDefaultQps();
}
/** /**
* 翻译处理 * 翻译处理
* *
@ -61,7 +57,7 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
JsonNode json = JsonUtil.parseJsonObject(resp); JsonNode json = JsonUtil.parseJsonObject(resp);
if (!json.has("data")) { if (!json.has("data")) {
String errorMsg = json.get("errmsg").asText(); String errorMsg = json.get("errmsg").asString();
if("访问出现异常,请刷新后重试!".equals(errorMsg) && !header.containsKey("Cookie")) { if("访问出现异常,请刷新后重试!".equals(errorMsg) && !header.containsKey("Cookie")) {
checkCookie(); checkCookie();
return customTranslate(source); return customTranslate(source);
@ -69,14 +65,12 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
throw new RuntimeException(errorMsg); throw new RuntimeException(errorMsg);
} }
return json.get("data").get(0).get("dst").asText(); return json.get("data").get(0).get("dst").asString();
} }
private void checkCookie() throws IOException, InterruptedException { private void checkCookie() throws IOException, InterruptedException {
// 短时大量请求会被ban需要添加验证cookie // 短时大量请求会被ban需要添加验证cookie
if (header.containsKey("Cookie")) return;
List<HttpCookie> cookieList = CookieManager.getStore().get(URI.create("https://baidu.com")); List<HttpCookie> cookieList = CookieManager.getStore().get(URI.create("https://baidu.com"));
boolean noneMatch = cookieList.stream() boolean noneMatch = cookieList.stream()
.filter(cookie -> "ab_sr".equals(cookie.getName()) || "BAIDUID".equals(cookie.getName())) .filter(cookie -> "ab_sr".equals(cookie.getName()) || "BAIDUID".equals(cookie.getName()))

View File

@ -3,7 +3,7 @@ package cn.octopusyan.dmt.translate.processor;
import cn.octopusyan.dmt.common.util.JsonUtil; import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.TranslateApi; import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
@ -25,11 +25,6 @@ public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
return "https://translate.googleapis.com/translate_a/single"; return "https://translate.googleapis.com/translate_a/single";
} }
@Override
public int qps() {
return source().getDefaultQps();
}
/** /**
* 翻译处理 * 翻译处理
* *
@ -39,22 +34,28 @@ public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
@Override @Override
public String customTranslate(String source) throws IOException, InterruptedException { public String customTranslate(String source) throws IOException, InterruptedException {
Map<String, Object> header = new HashMap<>();
header.put("User-Agent", "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0");
header.put("Referer", "https://translate.google.com/");
header.put("Host", "translate.googleapis.com");
Map<String, Object> form = new HashMap<>(); Map<String, Object> form = new HashMap<>();
form.put("client", "gtx"); form.put("client", "gtx");
form.put("dt", "t"); form.put("dt", "t");
form.put("sl", "auto"); form.put("sl", "auto");
form.put("tl", "zh-CN"); form.put("tl", "zh-CN");
form.put("q", source);
Map<String, Object> header = new HashMap<>(); Map<String, Object> body = new HashMap<>();
body.put("q", source);
StringBuilder retStr = new StringBuilder(); StringBuilder retStr = new StringBuilder();
// TODO 短时大量请求会被ban需要浏览器验证添加cookie // TODO 短时大量请求会被ban需要浏览器验证添加cookie
String resp = httpUtil.get(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form)); String resp = httpUtil.postForm(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form), JsonUtil.parseJsonObject(body));
JsonNode json = JsonUtil.parseJsonObject(resp); JsonNode json = JsonUtil.parseJsonObject(resp);
for (JsonNode o : json.get(0)) { for (JsonNode o : json.get(0)) {
retStr.append(o.get(0).asText()); retStr.append(o.get(0).asString());
} }
return retStr.toString(); return retStr.toString();

View File

@ -0,0 +1,89 @@
package cn.octopusyan.dmt.translate.processor;
import cn.hutool.core.date.DateUtil;
import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.TranslateApi;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import tools.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 微软 免费翻译接口
*
* @author octopus_yan@foxmail.com
*/
public class FreeMicrosoftTranslateProcessor extends AbstractTranslateProcessor {
private String token;
public FreeMicrosoftTranslateProcessor() {
super(TranslateApi.FREE_MICROSOFT);
}
@Override
public String url() {
return "https://api.cognitive.microsofttranslator.com/translate";
}
/**
* 翻译处理
*
* @param source 待翻译单词
* @return 翻译结果
*/
@Override
public String customTranslate(String source) throws IOException, InterruptedException {
Map<String, Object> form = new HashMap<>();
form.put("api-version", "3.0");
form.put("to", "zh-Hans");
form.put("textType", "plain");
Map<String, Object> header = new HashMap<>();
header.put("Authorization", STR."Bearer \{oauth()}");
String body = STR."""
[
{
"Text": "\{source}"
}
]
""";
StringBuilder retStr = new StringBuilder();
String resp = httpUtil.postForm(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form), JsonUtil.parseJsonObject(body));
JsonNode json = JsonUtil.parseJsonObject(resp);
for (JsonNode o : json.get(0)) {
retStr.append(o.get(0).asString());
}
return retStr.toString();
}
private synchronized String oauth() throws IOException, InterruptedException {
if (token == null) {
token = httpUtil.get("https://edge.microsoft.com/translate/auth", null, null);
} else {
// 解析,对应的签名或者加密方式
Claims claims = Jwts.parser()
// .verifyWith(key) // 指定用于验证的密钥
// .decryptWith(key) // 加密方式,指定用于解密的密钥。
.build()
.parseSignedClaims(token) // 签名方式
// .parseEncryptedClaims(token) // 加密方式,对 JWT 进行解密,并获取其声明部分。
.getPayload(); // 获取解密后的声明claims内容。
if(DateUtil.compare(DateUtil.date(), claims.getExpiration()) > 0) {
token = null;
return oauth();
}
}
return token;
}
}

View File

@ -1,8 +1,10 @@
package cn.octopusyan.dmt.utils; package cn.octopusyan.dmt.utils;
import cn.hutool.core.text.csv.*;
import cn.octopusyan.dmt.common.config.Constants; import cn.octopusyan.dmt.common.config.Constants;
import cn.octopusyan.dmt.common.config.Context; import cn.octopusyan.dmt.common.config.Context;
import cn.octopusyan.dmt.common.util.ProcessesUtil; import cn.octopusyan.dmt.common.util.ProcessesUtil;
import cn.octopusyan.dmt.model.WordCsvItem;
import cn.octopusyan.dmt.model.WordItem; import cn.octopusyan.dmt.model.WordItem;
import cn.octopusyan.dmt.view.ConsoleLog; import cn.octopusyan.dmt.view.ConsoleLog;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@ -14,7 +16,10 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; 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.concurrent.atomic.AtomicInteger;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -32,9 +37,9 @@ public class PBOUtil {
private static final ProcessesUtil processesUtil = ProcessesUtil.init(Constants.BIN_DIR_PATH); 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} {}"; 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 PACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" pack {} -o {}";
private static final String CFG_COMMAND = STR."\{Constants.CFG_CONVERT_FILE} {} -dst {} {}"; 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_STRING_TABLE = "stringtable.csv";
private static final String FILE_NAME_CONFIG_BIN = "config.bin"; private static final String FILE_NAME_CONFIG_BIN = "config.bin";
@ -117,7 +122,7 @@ public class PBOUtil {
FileUtils.deleteQuietly(packFile); FileUtils.deleteQuietly(packFile);
} }
String command = ProcessesUtil.format(PACK_COMMAND, Constants.TMP_DIR_PATH, unpackPath); String command = ProcessesUtil.format(PACK_COMMAND, unpackPath, Constants.TMP_DIR_PATH);
consoleLog.debug(STR."pack command ==> [\{command}]"); consoleLog.debug(STR."pack command ==> [\{command}]");
boolean exec = processesUtil.exec(command); boolean exec = processesUtil.exec(command);
@ -141,6 +146,10 @@ public class PBOUtil {
return wordItems; return wordItems;
List<File> files = new ArrayList<>(FileUtils.listFiles(file, FILE_NAME_LIST, true)); 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) { for (File item : files) {
wordItems.addAll(findWordByFile(item)); wordItems.addAll(findWordByFile(item));
} }
@ -153,7 +162,7 @@ public class PBOUtil {
* *
* @param wordFileMap 文件对应文本map * @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()) { for (Map.Entry<File, List<WordItem>> entry : wordFileMap.entrySet()) {
@ -164,41 +173,35 @@ public class PBOUtil {
// 需要转bin文件时写入bak目录下cpp文件 // 需要转bin文件时写入bak目录下cpp文件
boolean hasBin = new File(outFilePath(file, ".bin")).exists(); boolean hasBin = new File(outFilePath(file, ".bin")).exists();
// 写入TMP下文件
String writePath = file.getAbsolutePath().replace(Constants.BAK_DIR_PATH, Constants.TMP_DIR_PATH); String writePath = file.getAbsolutePath().replace(Constants.BAK_DIR_PATH, Constants.TMP_DIR_PATH);
File writeFile = hasBin ? file : new File(writePath); File writeFile = hasBin ? file : new File(writePath);
AtomicInteger lineIndex = new AtomicInteger(0); List<String> lines;
List<String> lines = new ArrayList<>();
consoleLog.info("正在写入文件[{}]", writeFile.getAbsolutePath()); consoleLog.info("正在写入文件 => {}", writeFile.getAbsolutePath());
try (LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name())) { try (LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name())) {
while (it.hasNext()) {
String line = it.next();
WordItem word = wordMap.get(lineIndex.get());
// 当前行是否有需要替换的文本 if (FILE_NAME_STRING_TABLE.equals(file.getName())) {
// TODO 是否替换空文本 // 写入 CSV 文件
if (word != null && line.contains(word.getOriginal())) { lines = writeCsv(file, it, wordMap);
line = line.substring(0, word.getIndex()) + } else {
line.substring(word.getIndex()).replace(word.getOriginal(), word.getChinese()); // 写入 CPP 或 layout 文件
} lines = writeOther(it, wordMap);
// 缓存行内容
lines.add(line);
lineIndex.addAndGet(1);
} }
} catch (IOException e) { } catch (IOException e) {
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", e); consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", e);
throw e;
} }
// 写入文件
try { try {
// 写入文件
String charsets = writeFile.getName().endsWith(".layout") ? FileUtil.getCharsets(writeFile) : StandardCharsets.UTF_8.name(); String charsets = writeFile.getName().endsWith(".layout") ? FileUtil.getCharsets(writeFile) : StandardCharsets.UTF_8.name();
FileUtils.writeLines(writeFile, charsets, lines); FileUtils.writeLines(writeFile, charsets, lines);
} catch (IOException e) { } catch (IOException e) {
consoleLog.error(STR."文件(\{writeFile.getAbsoluteFile()})写入失败", e); consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]写入出错", e);
throw e;
} }
// CPP转BIN (覆盖TMP下BIN文件) // CPP转BIN (覆盖TMP下BIN文件)
@ -206,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;
}
/** /**
* 查找文件内可翻译文本 * 查找文件内可翻译文本
* *
@ -238,7 +333,7 @@ public class PBOUtil {
} }
// CSV // CSV
if (FILE_NAME_STRING_TABLE.equals(file.getName())) { if (FILE_NAME_STRING_TABLE.equals(file.getName())) {
return findWordByCSV(file, it); return findWordByCSV(file);
} }
// layout // layout
if (file.getName().endsWith(".layout")) { if (file.getName().endsWith(".layout")) {
@ -255,37 +350,39 @@ public class PBOUtil {
return Collections.emptyList(); return Collections.emptyList();
} }
/**
* 从csv文件中读取可翻译文本 private static List<WordItem> findWordByCSV(File file) {
*
* @param file csv文件
* @param it 行内容遍历器
* @return 可翻译文本列表
*/
private static List<WordItem> findWordByCSV(File file, LineIterator it) {
ArrayList<WordItem> wordItems = new ArrayList<>(); 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) { CsvReadConfig config = CsvReadConfig.defaultConfig().setTrimField(true).setContainsHeader(true);
index = split.indexOf("\"chinese\""); CsvReader reader = CsvUtil.getReader(config);
} else if (index < split.size()) { CsvData data = reader.read(file);
// 原文
String original = split.get(index).replaceAll("\"", ""); // 读取CSV
// 开始下标 List<CsvRow> rows = data.getRows();
Integer startIndex = line.indexOf(original); for (CsvRow row : rows) {
// 添加单词 WordItem item;
if (original.length() > 1) { int lines = (int) (row.getOriginalLineNumber() + 1);
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
} 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);
} }
wordItems.add(item);
lines.addAndGet(1);
} }
return wordItems; return wordItems;
} }
@ -302,11 +399,17 @@ public class PBOUtil {
String line; String line;
Matcher matcher; Matcher matcher;
while (it.hasNext()) { while (it.hasNext()) {
lines.addAndGet(1);
line = it.next(); line = it.next();
matcher = LAYOUT_PATTERN.matcher(line); matcher = LAYOUT_PATTERN.matcher(line);
if (StringUtils.isNoneEmpty(line) && matcher.matches()) {
// 原文 if (StringUtils.isEmpty(line) || !matcher.matches())
String original = matcher.group(1); continue;
// 原文
String original = matcher.group(1);
if (!original.startsWith("#")) {
// 开始下标 // 开始下标
Integer startIndex = line.indexOf(original); Integer startIndex = line.indexOf(original);
// 添加单词 // 添加单词
@ -314,8 +417,6 @@ public class PBOUtil {
wordItems.add(new WordItem(file, lines.get(), startIndex, original, "")); wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
} }
} }
lines.addAndGet(1);
} }
return wordItems; return wordItems;
} }
@ -332,24 +433,23 @@ public class PBOUtil {
AtomicInteger lines = new AtomicInteger(0); AtomicInteger lines = new AtomicInteger(0);
while (it.hasNext()) { 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); 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; return wordItems;
} }
@ -414,4 +514,14 @@ public class PBOUtil {
private static String outFilePath(File file, String suffix) { private static String outFilePath(File file, String suffix) {
return file.getParentFile().getAbsolutePath() + File.separator + FileUtil.mainName(file) + 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();
}
} }

View File

@ -64,10 +64,7 @@ public class EditButtonTableCell extends TableCell<WordItem, WordItem> {
if (empty) { if (empty) {
setGraphic(null); setGraphic(null);
} else { } else {
/* // 添加多个操作按钮
* TODO 添加多个操作按钮
* setGraphic(Hbox(btn1,btn2));
*/
setGraphic(new HBox(edit, translate)); setGraphic(new HBox(edit, translate));
} }
} }

View File

@ -10,13 +10,15 @@ import cn.octopusyan.dmt.task.UnpackTask;
import cn.octopusyan.dmt.task.listener.DefaultTaskListener; import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
import cn.octopusyan.dmt.translate.DelayWord; import cn.octopusyan.dmt.translate.DelayWord;
import cn.octopusyan.dmt.translate.TranslateUtil; import cn.octopusyan.dmt.translate.TranslateUtil;
import cn.octopusyan.dmt.utils.PBOUtil;
import cn.octopusyan.dmt.view.ConsoleLog; import cn.octopusyan.dmt.view.ConsoleLog;
import cn.octopusyan.dmt.view.alert.AlertUtil;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker; import javafx.concurrent.Worker;
import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ProgressIndicator;
import org.apache.commons.lang3.StringUtils;
import org.kordamp.ikonli.feather.Feather; import org.kordamp.ikonli.feather.Feather;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
@ -24,7 +26,6 @@ import java.io.File;
import java.util.List; import java.util.List;
import java.util.concurrent.DelayQueue; import java.util.concurrent.DelayQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/** /**
* 主界面 * 主界面
@ -103,12 +104,14 @@ public class MainViewModel extends BaseViewModel<MainController> {
* 开始翻译 * 开始翻译
*/ */
public void startTranslate() { public void startTranslate() {
if(wordItems.isEmpty()) return; if (wordItems.isEmpty()) return;
if (translateTask == null) { 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); delayQueue = TranslateUtil.getDelayQueue(words);
translateTask = createTask(); translateTask = createTask();
translateTask.execute();
return;
} }
if (!translateTask.isRunning()) { if (!translateTask.isRunning()) {
@ -135,7 +138,7 @@ public class MainViewModel extends BaseViewModel<MainController> {
* 打包 * 打包
*/ */
public void pack() { public void pack() {
if(wordItems.isEmpty()) return; if (wordItems.isEmpty()) return;
PackTask packTask = new PackTask(wordItems, unpackPath); PackTask packTask = new PackTask(wordItems, unpackPath);
packTask.onListen(new PackTask.PackListener() { packTask.onListen(new PackTask.PackListener() {
@ -149,6 +152,13 @@ public class MainViewModel extends BaseViewModel<MainController> {
Platform.runLater(() -> controller.onPackOver(file)); 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(); packTask.execute();
} }
@ -192,19 +202,12 @@ public class MainViewModel extends BaseViewModel<MainController> {
private void resetProgress() { private void resetProgress() {
translateTask = null; translateTask = null;
controller.translate.setGraphic(startIcon); 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.progressProperty().unbind();
controller.translateProgress.setProgress(0); controller.translateProgress.setProgress(0);
controller.translateProgress.setVisible(false); controller.translateProgress.setVisible(false);
} }
/**
* 给定字符串是否含有中文
*
* @param str 需要判断的字符串
* @return 是否含有中文
*/
private boolean containsChinese(String str) {
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
}
} }

View File

@ -12,15 +12,17 @@ module cn.octopusyan.dmt {
requires static lombok; requires static lombok;
requires atlantafx.base; requires atlantafx.base;
requires com.fasterxml.jackson.annotation; requires com.fasterxml.jackson.annotation;
requires com.fasterxml.jackson.databind; requires tools.jackson.databind;
requires com.fasterxml.jackson.dataformat.yaml; requires tools.jackson.dataformat.yaml;
requires java.prefs; requires java.prefs;
requires org.kordamp.ikonli.javafx; requires org.kordamp.ikonli.javafx;
requires org.kordamp.ikonli.feather; requires org.kordamp.ikonli.feather;
requires java.management; requires java.management;
requires cn.hutool.core;
requires jjwt.api;
exports cn.octopusyan.dmt; exports cn.octopusyan.dmt;
exports cn.octopusyan.dmt.model to com.fasterxml.jackson.databind; exports cn.octopusyan.dmt.model to tools.jackson.databind;
opens cn.octopusyan.dmt.model to javafx.base; opens cn.octopusyan.dmt.model to javafx.base;
opens cn.octopusyan.dmt.common.base to javafx.fxml; opens cn.octopusyan.dmt.common.base to javafx.fxml;
opens cn.octopusyan.dmt.controller to javafx.fxml; opens cn.octopusyan.dmt.controller to javafx.fxml;