feat(translator): add custom target language input
- Replace Select with Input for custom language entry - Users can now type any target language they want - Add i18n translations for all 8 languages
This commit is contained in:
@@ -373,7 +373,8 @@
|
||||
"success": "Textpaar zum Ordner hinzugefügt",
|
||||
"error": "Fehler beim Hinzufügen des Textpaars zum Ordner"
|
||||
},
|
||||
"autoSave": "Autom. Speichern"
|
||||
"autoSave": "Autom. Speichern",
|
||||
"customLanguage": "oder Sprache eingeben..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "Wörterbuch",
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"unfavorite": "Unfavorite",
|
||||
"pleaseLogin": "Please login first"
|
||||
},
|
||||
"folder_id": {
|
||||
"folder_id": {
|
||||
"unauthorized": "You are not the owner of this folder",
|
||||
"back": "Back",
|
||||
"textPairs": "Text Pairs",
|
||||
@@ -287,7 +287,7 @@
|
||||
"favorites": "Favorites",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"ocr": {
|
||||
"ocr": {
|
||||
"title": "OCR Vocabulary Extractor",
|
||||
"description": "Upload vocabulary table screenshots from textbooks to extract word-definition pairs",
|
||||
"uploadSection": "Upload Image",
|
||||
@@ -400,6 +400,7 @@
|
||||
"auto": "Auto",
|
||||
"generateIPA": "generate ipa",
|
||||
"translateInto": "translate into",
|
||||
"customLanguage": "or type language...",
|
||||
"chinese": "Chinese",
|
||||
"english": "English",
|
||||
"french": "French",
|
||||
|
||||
@@ -402,7 +402,8 @@
|
||||
"success": "Paire de texte ajoutée au dossier",
|
||||
"error": "Échec de l'ajout de la paire de texte au dossier"
|
||||
},
|
||||
"autoSave": "Sauvegarde automatique"
|
||||
"autoSave": "Sauvegarde automatique",
|
||||
"customLanguage": "ou tapez la langue..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "Dictionnaire",
|
||||
@@ -508,15 +509,6 @@
|
||||
"view": "Voir"
|
||||
}
|
||||
},
|
||||
"follow": {
|
||||
"follow": "Suivre",
|
||||
"following": "Abonnements",
|
||||
"followers": "Abonnés",
|
||||
"followersOf": "Abonnés de {username}",
|
||||
"followingOf": "Abonnements de {username}",
|
||||
"noFollowers": "Pas encore d'abonnés",
|
||||
"noFollowing": "Ne suit personne"
|
||||
},
|
||||
"follow": {
|
||||
"follow": "Suivre",
|
||||
"following": "Abonné",
|
||||
|
||||
@@ -402,7 +402,8 @@
|
||||
"success": "Coppia di testo aggiunta alla cartella",
|
||||
"error": "Impossibile aggiungere coppia di testo alla cartella"
|
||||
},
|
||||
"autoSave": "Salvataggio Automatico"
|
||||
"autoSave": "Salvataggio Automatico",
|
||||
"customLanguage": "o digita lingua..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "Dizionario",
|
||||
@@ -508,15 +509,6 @@
|
||||
"view": "Visualizza"
|
||||
}
|
||||
},
|
||||
"follow": {
|
||||
"follow": "Segui",
|
||||
"following": "Seguiti",
|
||||
"followers": "Seguaci",
|
||||
"followersOf": "Seguaci di {username}",
|
||||
"followingOf": "Seguiti di {username}",
|
||||
"noFollowers": "Nessun seguace ancora",
|
||||
"noFollowing": "Non segui ancora nessuno"
|
||||
},
|
||||
"follow": {
|
||||
"follow": "Segui",
|
||||
"following": "Stai seguendo",
|
||||
|
||||
@@ -411,7 +411,8 @@
|
||||
"success": "テキストペアがフォルダーに追加されました",
|
||||
"error": "テキストペアをフォルダーに追加できませんでした"
|
||||
},
|
||||
"autoSave": "自動保存"
|
||||
"autoSave": "自動保存",
|
||||
"customLanguage": "または言語を入力..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "辞書",
|
||||
|
||||
@@ -402,7 +402,8 @@
|
||||
"success": "텍스트 쌍이 폴더에 추가됨",
|
||||
"error": "폴더에 텍스트 쌍 추가 실패"
|
||||
},
|
||||
"autoSave": "자동 저장"
|
||||
"autoSave": "자동 저장",
|
||||
"customLanguage": "또는 언어 입력..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "사전",
|
||||
@@ -508,15 +509,6 @@
|
||||
"view": "보기"
|
||||
}
|
||||
},
|
||||
"follow": {
|
||||
"follow": "팔로우",
|
||||
"following": "팔로잉",
|
||||
"followers": "팔로워",
|
||||
"followersOf": "{username}의 팔로워",
|
||||
"followingOf": "{username}의 팔로잉",
|
||||
"noFollowers": "아직 팔로워가 없습니다",
|
||||
"noFollowing": "아직 팔로잉하는 사람이 없습니다"
|
||||
},
|
||||
"follow": {
|
||||
"follow": "팔로우",
|
||||
"following": "팔로잉",
|
||||
|
||||
@@ -402,7 +402,8 @@
|
||||
"success": "تېكىست جۈپى قىسقۇچقا قوشۇلدى",
|
||||
"error": "تېكىست جۈپىنى قىسقۇچقا قوشۇش مەغلۇپ بولدى"
|
||||
},
|
||||
"autoSave": "ئاپتوماتىك ساقلاش"
|
||||
"autoSave": "ئاپتوماتىك ساقلاش",
|
||||
"customLanguage": "ياكى تىل تىل كىرۇڭ..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "لۇغەت",
|
||||
@@ -508,15 +509,6 @@
|
||||
"view": "كۆرۈش"
|
||||
}
|
||||
},
|
||||
"follow": {
|
||||
"follow": "ئەگىشىش",
|
||||
"following": "ئەگىشىۋاتقانلار",
|
||||
"followers": "ئەگەشكۈچىلەر",
|
||||
"followersOf": "{username} نىڭ ئەگەشكۈچىلىرى",
|
||||
"followingOf": "{username} نىڭ ئەگىشىۋاتقانلىرى",
|
||||
"noFollowers": "تېخى ئەگەشكۈچى يوق",
|
||||
"noFollowing": "تېخى ئەگىشىۋاتقان يوق"
|
||||
},
|
||||
"follow": {
|
||||
"follow": "ئەگىشىش",
|
||||
"following": "ئەگىشىۋاتىدۇ",
|
||||
|
||||
@@ -424,7 +424,8 @@
|
||||
"success": "文本对已添加到文件夹",
|
||||
"error": "添加文本对到文件夹失败"
|
||||
},
|
||||
"autoSave": "自动保存"
|
||||
"autoSave": "自动保存",
|
||||
"customLanguage": "或输入语言..."
|
||||
},
|
||||
"dictionary": {
|
||||
"title": "词典",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { LightButton, PrimaryButton,
|
||||
IconClick } from "@/design-system/base/button";
|
||||
import { LightButton, PrimaryButton, IconClick } from "@/design-system/base/button";
|
||||
import { Select } from "@/design-system/base/select";
|
||||
import { Input } from "@/design-system/base/input";
|
||||
import { Textarea } from "@/design-system/base/textarea";
|
||||
import { IMAGES } from "@/config/images";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
@@ -46,6 +46,7 @@ export default function TranslatorPage() {
|
||||
const taref = useRef<HTMLTextAreaElement>(null);
|
||||
const [sourceLanguage, setSourceLanguage] = useState<string>("Auto");
|
||||
const [targetLanguage, setTargetLanguage] = useState<string>("Chinese");
|
||||
const [customTargetLanguage, setCustomTargetLanguage] = useState<string>("");
|
||||
const [translationResult, setTranslationResult] = useState<TSharedTranslationResult | null>(null);
|
||||
const [needIpa, setNeedIpa] = useState(true);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
@@ -93,18 +94,18 @@ export default function TranslatorPage() {
|
||||
setProcessing(true);
|
||||
|
||||
const sourceText = taref.current.value;
|
||||
const effectiveTargetLanguage = customTargetLanguage.trim() || targetLanguage;
|
||||
|
||||
// 判断是否需要强制重新翻译
|
||||
// 只有当源文本、源语言和目标语言都与上次相同时,才强制重新翻译
|
||||
const forceRetranslate =
|
||||
lastTranslation?.sourceText === sourceText &&
|
||||
lastTranslation?.sourceLanguage === sourceLanguage &&
|
||||
lastTranslation?.targetLanguage === targetLanguage;
|
||||
lastTranslation?.targetLanguage === effectiveTargetLanguage;
|
||||
|
||||
try {
|
||||
const result = await actionTranslateText({
|
||||
sourceText,
|
||||
targetLanguage,
|
||||
targetLanguage: effectiveTargetLanguage,
|
||||
forceRetranslate,
|
||||
needIpa,
|
||||
sourceLanguage: sourceLanguage === "Auto" ? undefined : sourceLanguage,
|
||||
@@ -115,7 +116,7 @@ export default function TranslatorPage() {
|
||||
setLastTranslation({
|
||||
sourceText,
|
||||
sourceLanguage,
|
||||
targetLanguage,
|
||||
targetLanguage: effectiveTargetLanguage,
|
||||
});
|
||||
} else {
|
||||
toast.error(result.message || "翻译失败,请重试");
|
||||
@@ -246,39 +247,43 @@ export default function TranslatorPage() {
|
||||
<div className="option2 w-full flex gap-1 items-center overflow-x-auto">
|
||||
<span className="shrink-0">{t("translateInto")}</span>
|
||||
<LightButton
|
||||
selected={targetLanguage === "Chinese"}
|
||||
onClick={() => setTargetLanguage("Chinese")}
|
||||
selected={!customTargetLanguage && targetLanguage === "Chinese"}
|
||||
onClick={() => {
|
||||
setTargetLanguage("Chinese");
|
||||
setCustomTargetLanguage("");
|
||||
}}
|
||||
className="shrink-0 hidden lg:inline-flex"
|
||||
>
|
||||
{t("chinese")}
|
||||
</LightButton>
|
||||
<LightButton
|
||||
selected={targetLanguage === "English"}
|
||||
onClick={() => setTargetLanguage("English")}
|
||||
selected={!customTargetLanguage && targetLanguage === "English"}
|
||||
onClick={() => {
|
||||
setTargetLanguage("English");
|
||||
setCustomTargetLanguage("");
|
||||
}}
|
||||
className="shrink-0 hidden lg:inline-flex"
|
||||
>
|
||||
{t("english")}
|
||||
</LightButton>
|
||||
<LightButton
|
||||
selected={targetLanguage === "Japanese"}
|
||||
onClick={() => setTargetLanguage("Japanese")}
|
||||
selected={!customTargetLanguage && targetLanguage === "Japanese"}
|
||||
onClick={() => {
|
||||
setTargetLanguage("Japanese");
|
||||
setCustomTargetLanguage("");
|
||||
}}
|
||||
className="shrink-0 hidden xl:inline-flex"
|
||||
>
|
||||
{t("japanese")}
|
||||
</LightButton>
|
||||
<Select
|
||||
value={targetLanguage}
|
||||
onChange={(e) => setTargetLanguage(e.target.value)}
|
||||
variant="light"
|
||||
<Input
|
||||
variant="bordered"
|
||||
size="sm"
|
||||
className="w-auto min-w-[100px] shrink-0"
|
||||
>
|
||||
{TARGET_LANGUAGES.map((lang) => (
|
||||
<option key={lang.value} value={lang.value}>
|
||||
{t(lang.labelKey)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
value={customTargetLanguage}
|
||||
onChange={(e) => setCustomTargetLanguage(e.target.value)}
|
||||
placeholder={t("customLanguage")}
|
||||
className="w-auto min-w-[120px] shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user