pref: 更新界面、添加翻译进度反馈、展示日志信息

feat: 添加百度free翻译接口
This commit is contained in:
2024-11-15 01:55:34 +08:00
parent 50032cc599
commit 943056168f
1099 changed files with 7386 additions and 4106 deletions

View File

@ -0,0 +1,181 @@
package cn.octopusyan.dmt.utils;
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.*;
import java.nio.file.Files;
import java.nio.file.Path;
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 String getMimeType(Path path) {
try {
return Files.probeContentType(path);
} catch (IOException e) {
return null;
}
}
public static Collection<File> listFile(File file) {
return FileUtils.listFiles(file, CanReadFileFilter.CAN_READ, null);
}
public static List<String> listFileNames(String path) {
return listFileNames(new File(path));
}
public static List<String> listFileNames(File file) {
Collection<File> files = listFile(file);
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;
}
/**
* 判断文件的编码格式
*
* @return 文件编码格式
*/
public static String getCharsets(File file) {
try (InputStream in = new FileInputStream(file)) {
int p = (in.read() << 8) + in.read();
String code = "GBK";
switch (p) {
case 59524:
code = "UTF-8";
break;
case 0xfffe:
code = "Unicode";
break;
case 0xfeff:
code = "UTF-16BE";
break;
case 48581:
code = "GBK";
break;
default:
}
return code;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,407 @@
package cn.octopusyan.dmt.utils;
import cn.octopusyan.dmt.common.config.Constants;
import cn.octopusyan.dmt.common.util.ProcessesUtil;
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 java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* PBO 文件工具
*
* @author octopus_yan
*/
public class PBOUtil {
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 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 static final String[] FILE_NAME_LIST = new String[]{"csv", "bin", "cpp", "layout"};
private static final Pattern CPP_PATTERN = Pattern.compile(".*(displayName|descriptionShort) ?= ?\"(.*)\";.*");
private static final Pattern LAYOUT_PATTERN = Pattern.compile(".*text \"(.*)\".*");
public static void init() {
String srcFilePath = Objects.requireNonNull(PBOUtil.class.getResource("/bin")).getPath();
try {
File destDir = new File(Constants.BIN_DIR_PATH);
FileUtils.forceMkdir(destDir);
FileUtils.copyDirectory(new File(srcFilePath), destDir);
} catch (IOException e) {
consoleLog.error("Util 初始化失败", e);
}
}
/**
* 解包pbo文件
*
* @param path pbo文件地址
* @return 解包输出路径
*/
public static String unpack(String path) {
return unpack(new File(path));
}
/**
* 解包pbo文件
*
* @param pboFile pbo文件
* @return 解包输出路径
*/
public static String unpack(File pboFile) {
if (!pboFile.exists())
throw new RuntimeException("文件不存在!");
File directory = new File(Constants.TMP_DIR_PATH);
String outputPath = Constants.TMP_DIR_PATH + File.separator + FileUtil.mainName(pboFile);
try {
FileUtils.deleteQuietly(new File(outputPath));
FileUtils.forceMkdir(directory);
} catch (IOException e) {
throw new RuntimeException("文件夹创建失败", e);
}
String command = String.format(UNPACK_COMMAND, pboFile.getAbsolutePath());
consoleLog.debug(STR."unpack command ==> [\{command}]");
boolean exec = processesUtil.exec(command);
if (!exec)
throw new RuntimeException("解包失败!");
return outputPath;
}
/**
* 打包pbo文件
*
* @param unpackPath pbo解包文件路径
* @return 打包文件
*/
public static File pack(String unpackPath) {
String outputPath = STR."\{unpackPath}.pbo";
// 打包文件临时保存路径
File packFile = new File(outputPath);
if (packFile.exists()) {
// 如果存在则删除
FileUtils.deleteQuietly(packFile);
}
String command = String.format(PACK_COMMAND, Constants.TMP_DIR_PATH, unpackPath);
consoleLog.debug(STR."pack command ==> [\{command}]");
boolean exec = processesUtil.exec(command);
if (!exec) throw new RuntimeException("打包失败!");
return packFile;
}
/**
* 查找可翻译文本
*
* @param path 根目录
*/
public static List<WordItem> findWord(String path) {
return findWord(new File(path));
}
public static List<WordItem> findWord(File file) {
ArrayList<WordItem> wordItems = new ArrayList<>();
if (!file.exists())
return wordItems;
List<File> files = new ArrayList<>(FileUtils.listFiles(file, FILE_NAME_LIST, true));
for (File item : files) {
wordItems.addAll(findWordByFile(item));
}
return wordItems;
}
/**
* 写入文件
*
* @param wordFileMap 文件对应文本map
*/
public static void writeWords(Map<File, List<WordItem>> wordFileMap) {
for (Map.Entry<File, List<WordItem>> entry : wordFileMap.entrySet()) {
Map<Integer, WordItem> wordMap = entry.getValue().stream()
.collect(Collectors.toMap(WordItem::getLines, Function.identity()));
File file = entry.getKey();
// 需要转bin文件时写入bak目录下cpp文件
boolean hasBin = new File(outFilePath(file, ".bin")).exists();
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<>();
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);
}
} catch (IOException e) {
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", 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);
}
// CPP转BIN (覆盖TMP下BIN文件)
if (hasBin) cpp2bin(writeFile);
}
}
/**
* 查找文件内可翻译文本
*
* @param file 文件
* @return 可翻译文本信息列表
*/
private static List<WordItem> findWordByFile(File file) {
if (!FILE_NAME_CONFIG_CPP.equals(file.getName())
&& !FILE_NAME_CONFIG_BIN.equals(file.getName())
&& !FILE_NAME_STRING_TABLE.equals(file.getName())
&& !file.getName().endsWith(".layout")
) {
return Collections.emptyList();
}
// 创建备份(在bak文件夹下的同级目录
file = createBak(file);
// bin转cpp
if (FILE_NAME_CONFIG_BIN.equals(file.getName())) {
file = bin2cpp(file);
}
String charset = file.getName().endsWith(".layout") ? FileUtil.getCharsets(file) : StandardCharsets.UTF_8.name();
try (LineIterator it = FileUtils.lineIterator(file, charset)) {
// CPP
if (FILE_NAME_CONFIG_CPP.equals(file.getName())) {
return findWordByCPP(file, it);
}
// CSV
if (FILE_NAME_STRING_TABLE.equals(file.getName())) {
return findWordByCSV(file, it);
}
// layout
if (file.getName().endsWith(".layout")) {
return findWordByLayout(file, it);
}
// TODO 待添加更多文件格式
return Collections.emptyList();
} catch (IOException e) {
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", e);
}
return Collections.emptyList();
}
/**
* 从csv文件中读取可翻译文本
*
* @param file csv文件
* @param it 行内容遍历器
* @return 可翻译文本列表
*/
private static List<WordItem> findWordByCSV(File file, LineIterator it) {
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, ""));
}
}
lines.addAndGet(1);
}
return wordItems;
}
/**
* 从layout文件中读取可翻译文本
*
* @param file layout文件
* @param it 行内容遍历器
* @return 可翻译文本列表
*/
private static List<WordItem> findWordByLayout(File file, LineIterator it) {
ArrayList<WordItem> wordItems = new ArrayList<>();
AtomicInteger lines = new AtomicInteger(0);
String line;
Matcher matcher;
while (it.hasNext()) {
line = it.next();
matcher = LAYOUT_PATTERN.matcher(line);
if (StringUtils.isNoneEmpty(line) && matcher.matches()) {
// 原文
String original = matcher.group(1);
// 开始下标
Integer startIndex = line.indexOf(original);
// 添加单词
if (original.length() > 1) {
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
}
}
lines.addAndGet(1);
}
return wordItems;
}
/**
* 读取cpp文件内可翻译文本
*
* @param file cpp文件
* @param it 行内容遍历器
* @return 可翻译文本列表
*/
private static List<WordItem> findWordByCPP(File file, LineIterator it) {
ArrayList<WordItem> wordItems = new ArrayList<>();
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);
}
return wordItems;
}
/**
* 创建备份文件
*/
private static File createBak(File file) {
try {
String absolutePath = file.getAbsolutePath().replace(Constants.TMP_DIR_PATH, Constants.BAK_DIR_PATH);
File destFile = new File(absolutePath);
FileUtils.copyFile(file, destFile);
return destFile;
} catch (IOException e) {
consoleLog.error(STR."创建备份文件失败[\{file.getAbsolutePath()}]", e);
}
return file;
}
/**
* bin 转 cpp
*
* @param file bin文件
* @return cpp文件
*/
private static File bin2cpp(File file) {
boolean exec = processesUtil.exec(toTxtCommand(file));
if (!exec) throw new RuntimeException("bin2cpp 失败");
return new File(outFilePath(file, ".cpp"));
}
/**
* cpp 转 bin
*
* @param file bin文件
*/
private static void cpp2bin(File file) {
boolean exec = processesUtil.exec(toBinCommand(file));
if (!exec) throw new RuntimeException("cpp2bin 失败");
}
/**
* cpp to bin 命令
*/
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());
}
/**
* bin to cpp 命令
*/
private static String toTxtCommand(File binFile) {
String outFilePath = outFilePath(binFile, ".cpp");
return String.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;
}
}

View File

@ -0,0 +1,43 @@
/* SPDX-License-Identifier: MIT */
package cn.octopusyan.dmt.utils;
import cn.octopusyan.dmt.AppLauncher;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.Objects;
import java.util.prefs.Preferences;
public final class Resources {
public static final String MODULE_DIR = "/";
public static InputStream getResourceAsStream(String resource) {
String path = resolve(resource);
return Objects.requireNonNull(
AppLauncher.class.getResourceAsStream(resolve(path)),
"Resource not found: " + path
);
}
public static URI getResource(String resource) {
String path = resolve(resource);
URL url = Objects.requireNonNull(AppLauncher.class.getResource(resolve(path)), "Resource not found: " + path);
return URI.create(url.toExternalForm());
}
public static String resolve(String resource) {
Objects.requireNonNull(resource);
return resource.startsWith("/") ? resource : MODULE_DIR + resource;
}
public static String getPropertyOrEnv(String propertyKey, String envKey) {
return System.getProperty(propertyKey, System.getenv(envKey));
}
public static Preferences getPreferences() {
return Preferences.userRoot().node("atlantafx");
}
}