Skip to content

Commit

Permalink
feat: optimize subtitle translation speed and enable batch processing
Browse files Browse the repository at this point in the history
  • Loading branch information
rockbenben committed Jun 13, 2024
1 parent 09d1c81 commit 7dfad35
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 90 deletions.
171 changes: 81 additions & 90 deletions src/app/(translate)/sublabel-translator/client.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import React, { useState, useEffect } from "react";
import { Flex, Button, Input, Upload, Form, Space, message, Typography, Select, Modal, Progress, Radio, RadioChangeEvent } from "antd";
import { InboxOutlined } from "@ant-design/icons";
import { languages, translationMethods } from "@/app/components/transalteConstants";
import { translateText } from "@/app/components/translateText";
import { splitTextIntoChunks, translateText } from "@/app/components/translateText";
import { copyToClipboard } from "@/app/components/copyToClipboard";

const { Title, Paragraph } = Typography;
Expand Down Expand Up @@ -81,7 +82,8 @@ const ClientPage = () => {
setFile(file);
const reader = new FileReader();
reader.onload = (e) => {
setSourceText(e.target?.result as string);
const text = (e.target?.result as string).replace(/\r\n/g, "\n");
setSourceText(text);
};
reader.readAsText(file);
return false;
Expand Down Expand Up @@ -146,73 +148,97 @@ const ClientPage = () => {
}
};

const handleTranslate = async () => {
const filterContentLines = (lines: string[]) => {
const contentLines: string[] = [];
const contentIndices: number[] = [];

lines.forEach((line, index) => {
const isTimecode = /^[\d:,]+ --> [\d:,]+$/.test(line);
const isIndex = /^\d+$/.test(line);
const isNumeric = /^\d+(\.\d+)?$/.test(line.trim());
if (!isIndex && !isTimecode && !isNumeric && line.trim().length > 0) {
contentLines.push(line);
contentIndices.push(index);
}
});

return { contentLines, contentIndices };
};

const validateInputs = async () => {
if (sourceLanguage === targetLanguage) {
message.error("源语言和目标语言不能相同");
return;
return false;
}

if (translationMethod !== "deeplx" && !apiKeyDeepl && !apiKeyGoogleTranslate && !apiKeyAzure) {
message.error("请设置 API Key");
return;
return false;
}

if (translationMethod === "deeplx") {
const isDeeplxWorking = await testDeeplxTranslation();
if (!isDeeplxWorking) {
message.error("当前 Deeplx 节点有问题,请切换其他翻译模式");
setTranslationMethod("google"); // 默认切换到 Google 翻译
return;
return false;
}
}

setTranslateInProgress(true);
setStartTime(Date.now());
return true;
};

const performTranslation = async (sourceText: string, fileName?: string) => {
const lines = sourceText.split("\n");
const translatedLines: string[] = [];
const { contentLines, contentIndices } = filterContentLines(lines);

for (const line of lines) {
const isTimecode = /^[\d:,]+ --> [\d:,]+$/.test(line);
const isIndex = /^\d+$/.test(line);
const isNumeric = /^\d+(\.\d+)?$/.test(line.trim());
if (!isIndex && !isTimecode && !isNumeric && line.trim().length > 0) {
try {
const translatedLine = await translateTextUsingMethod(line);
translatedLines.push(translatedLine);
} catch (error) {
message.error("翻译过程中发生错误");
setTranslateInProgress(false);
return;
}
try {
const chunks = splitTextIntoChunks(contentLines.join("\n"), 3000);
const translatedLines: string[] = [];

for (const chunk of chunks) {
const translatedContent = await translateTextUsingMethod(chunk);
translatedLines.push(translatedContent);
}

const finalTranslatedLines = translatedLines.join("\n").split("\n");
const translatedTextArray = [...lines];
contentIndices.forEach((index, i) => {
translatedTextArray[index] = finalTranslatedLines[i];
});

const translatedText = translatedTextArray.join("\n");

if (fileName) {
const blob = new Blob([translatedText], {
type: "text/plain;charset=utf-8",
});
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
} else {
translatedLines.push(line);
setTranslatedText(translatedText);
}
} catch (error) {
message.error("翻译过程中发生错误");
} finally {
setTranslateInProgress(false);
}

setTranslatedText(translatedLines.join("\n"));
setTranslateInProgress(false);
};

const handleMultipleTranslate = async () => {
if (sourceLanguage === targetLanguage) {
message.error("源语言和目标语言不能相同");
return;
}
const handleTranslate = async () => {
if (!(await validateInputs())) return;

if (translationMethod !== "deeplx" && !apiKeyDeepl && !apiKeyGoogleTranslate && !apiKeyAzure) {
message.error("请设置 API Key");
return;
}
setTranslateInProgress(true);
setStartTime(Date.now());

if (translationMethod === "deeplx") {
const isDeeplxWorking = await testDeeplxTranslation();
if (!isDeeplxWorking) {
message.error("当前 Deeplx 节点有问题,请切换其他翻译模式");
setTranslationMethod("google"); // 默认切换到 Google 翻译
return;
}
}
await performTranslation(sourceText);
};

const handleMultipleTranslate = async () => {
if (!(await validateInputs())) return;

if (multipleFiles.length === 0) {
message.error("请选择要翻译的字幕文件");
Expand All @@ -222,54 +248,19 @@ const ClientPage = () => {
setTranslateInProgress(true);
setStartTime(Date.now());

try {
for (const currentFile of multipleFiles) {
const reader = new FileReader();
await new Promise<void>((resolve) => {
reader.onload = async (e) => {
const sourceText = e.target?.result as string;
const lines = sourceText.split("\n");
const translatedLines: string[] = [];

for (const line of lines) {
const isTimecode = /^[\d:,]+ --> [\d:,]+$/.test(line);
const isIndex = /^\d+$/.test(line);
const isNumeric = /^\d+(\.\d+)?$/.test(line.trim());
if (!isIndex && !isTimecode && !isNumeric && line.trim().length > 0) {
try {
const translatedLine = await translateTextUsingMethod(line);
translatedLines.push(translatedLine);
} catch (error) {
message.error("翻译过程中发生错误");
setTranslateInProgress(false);
return;
}
} else {
translatedLines.push(line);
}
}

const translatedText = translatedLines.join("\n");
const blob = new Blob([translatedText], {
type: "text/plain;charset=utf-8",
});
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `${currentFile.name}`;
link.click();
URL.revokeObjectURL(link.href);

resolve();
};
reader.readAsText(currentFile);
});
}
message.success("翻译完成,已自动下载所有翻译后的字幕文件");
} catch (error) {
message.error("翻译过程中发生错误");
} finally {
setTranslateInProgress(false);
for (const currentFile of multipleFiles) {
const reader = new FileReader();
await new Promise<void>((resolve) => {
reader.onload = async (e) => {
const text = (e.target?.result as string).replace(/\r\n/g, "\n");
await performTranslation(text, currentFile.name);
resolve();
};
reader.readAsText(currentFile);
});
}

message.success("翻译完成,已自动下载所有翻译后的字幕文件");
};

const handleExportSubtitle = () => {
Expand Down
20 changes: 20 additions & 0 deletions src/app/components/translateText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ interface TranslateTextParams {
apiRegion?: string;
}

export const splitTextIntoChunks = (text: string, maxLength: number) => {
const chunks = [];
let currentChunk = "";

text.split("\n").forEach((line) => {
if (currentChunk.length + line.length + 1 > maxLength) {
chunks.push(currentChunk);
currentChunk = line;
} else {
currentChunk += currentChunk ? "\n" + line : line;
}
});

if (currentChunk) {
chunks.push(currentChunk);
}

return chunks;
};

export const translateText = async ({ text, translationMethod, targetLanguage, sourceLanguage, apiKey, apiRegion = "eastasia" }: TranslateTextParams): Promise<string | null> => {
try {
// 如果文本为空或源语言和目标语言相同,则直接返回原文
Expand Down

0 comments on commit 7dfad35

Please sign in to comment.