Feat: 单次翻译质量优化提示
This commit is contained in:
parent
6cdc3699cf
commit
8335970e2e
|
|
@ -9,7 +9,6 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { toast } from "sonner";
|
||||
import { ClipboardPaste } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { memo, useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -34,13 +35,26 @@ type Props = {
|
|||
) => Promise<void> | void;
|
||||
};
|
||||
|
||||
function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, model, initialSelectedLanguages, getExistingByPath, onConfirm }: Props) {
|
||||
function AiTranslateModalImpl({
|
||||
open,
|
||||
onOpenChange,
|
||||
languages,
|
||||
paths,
|
||||
prompt,
|
||||
model,
|
||||
initialSelectedLanguages,
|
||||
getExistingByPath,
|
||||
onConfirm,
|
||||
}: Props) {
|
||||
const [inputsByKey, setInputsByKey] = useState<Record<string, string>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedLangs, setSelectedLangs] = useState<string[]>([]);
|
||||
const [overwrite, setOverwrite] = useState(false);
|
||||
const [existingCache, setExistingCache] = useState<Record<string, { lang: string; text: string }[]>>({});
|
||||
const [contextHint, setContextHint] = useState("");
|
||||
const [existingCache, setExistingCache] = useState<
|
||||
Record<string, { lang: string; text: string }[]>
|
||||
>({});
|
||||
const MAX_ITEMS = 50;
|
||||
const overLimit = paths.length > MAX_ITEMS;
|
||||
|
||||
|
|
@ -51,12 +65,14 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, mo
|
|||
setInputsByKey(init);
|
||||
|
||||
// 初始化默认勾选语言
|
||||
const initSelected = (initialSelectedLanguages && initialSelectedLanguages.length > 0)
|
||||
const initSelected =
|
||||
initialSelectedLanguages && initialSelectedLanguages.length > 0
|
||||
? initialSelectedLanguages
|
||||
: languages;
|
||||
|
||||
setSelectedLangs(initSelected);
|
||||
setOverwrite(false);
|
||||
setContextHint("");
|
||||
setExistingCache({});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
@ -77,7 +93,10 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, mo
|
|||
setError(`一次最多支持 ${MAX_ITEMS} 条`);
|
||||
return;
|
||||
}
|
||||
const items = paths.map((key) => ({ key, text: (inputsByKey[key] ?? "").trim() }));
|
||||
const items = paths.map((key) => ({
|
||||
key,
|
||||
text: (inputsByKey[key] ?? "").trim(),
|
||||
}));
|
||||
if (items.some((it) => !it.text)) {
|
||||
setError("请为所有条目输入原文");
|
||||
return;
|
||||
|
|
@ -92,9 +111,18 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, mo
|
|||
}
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await requestTranslations({ items, languages: selectedLangs, prompt, model });
|
||||
const result = await requestTranslations({
|
||||
items,
|
||||
languages: selectedLangs,
|
||||
prompt,
|
||||
model,
|
||||
contextHint: contextHint.trim() || undefined,
|
||||
});
|
||||
|
||||
await onConfirm(result, { overwrite, selectedLanguages: selectedLangs });
|
||||
|
||||
setInputsByKey({});
|
||||
onOpenChange(false);
|
||||
} catch (e) {
|
||||
|
|
@ -158,9 +186,7 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, mo
|
|||
) : (
|
||||
(existingCache[p] || []).map((opt) => (
|
||||
<>
|
||||
<DropdownMenuLabel>
|
||||
{opt.lang}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>{opt.lang}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
key={`${p}:${opt.lang}`}
|
||||
onClick={() =>
|
||||
|
|
@ -220,6 +246,19 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, mo
|
|||
<span className="text-sm">覆盖已有翻译(默认仅填充缺失项)</span>
|
||||
</label>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
补充说明(可选):
|
||||
</div>
|
||||
<Textarea
|
||||
value={contextHint}
|
||||
onChange={(e) => setContextHint(e.target.value)}
|
||||
placeholder="例如:这是一个电商 App 的结算页面,「立即购买」希望翻译得更有行动号召力"
|
||||
rows={2}
|
||||
className="resize-none text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{(error || overLimit) && (
|
||||
<div className="text-sm text-red-600" role="alert">
|
||||
{error}
|
||||
|
|
@ -249,5 +288,3 @@ function AiTranslateModalImpl({ open, onOpenChange, languages, paths, prompt, mo
|
|||
}
|
||||
|
||||
export const AiTranslateModal = memo(AiTranslateModalImpl);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export type AiTranslateParams = {
|
|||
languages: string[];
|
||||
model?: string;
|
||||
prompt?: string;
|
||||
contextHint?: string;
|
||||
};
|
||||
|
||||
function getClient() {
|
||||
|
|
@ -22,6 +23,7 @@ export async function requestTranslations({
|
|||
languages,
|
||||
model,
|
||||
prompt,
|
||||
contextHint,
|
||||
}: AiTranslateParams): Promise<Record<string, Record<string, string>>> {
|
||||
const client = getClient();
|
||||
const mdl =
|
||||
|
|
@ -38,6 +40,7 @@ export async function requestTranslations({
|
|||
`请使用当地人的习惯用语,将多条原文同时翻译为这些目标语言:${targetList},注意英语的首字母务必是大写。`,
|
||||
"翻译偏好:",
|
||||
prompt,
|
||||
...(contextHint ? ["用户补充的语境与翻译期望:", contextHint] : []),
|
||||
"严格要求:",
|
||||
"- 仅返回一个 JSON 对象,不包含任何其他内容(例如说明、代码块标记、注释)。",
|
||||
"- JSON 的顶层键必须严格等于目标语言代码;其值必须是一个对象,键为条目 key,值为该 key 的翻译纯文本。",
|
||||
|
|
|
|||
Loading…
Reference in New Issue