Feat: 增加语言过滤功能,临时性不显示对应语言

This commit is contained in:
奇趣保罗 2025-11-15 18:14:14 +08:00
parent 83e826e034
commit 2ae98780de
2 changed files with 49 additions and 7 deletions

View File

@ -35,7 +35,7 @@ export async function requestTranslations({
const keysJson = JSON.stringify(items.map((it) => it.key)); const keysJson = JSON.stringify(items.map((it) => it.key));
const instruction = [ const instruction = [
"你是一个专业的翻译助手。", "你是一个专业的翻译助手。",
`将多条原文同时翻译为这些目标语言:${targetList},注意英语的首字母务必是大写。`, `使用当地人的习惯用语,将多条原文同时翻译为这些目标语言:${targetList},注意英语的首字母务必是大写。`,
"翻译偏好:", "翻译偏好:",
prompt, prompt,
"严格要求:", "严格要求:",

View File

@ -17,7 +17,7 @@ import {
updateProject, updateProject,
deleteProjectDeep, deleteProjectDeep,
} from "@/lib/db"; } from "@/lib/db";
import { ArrowBigDownDash, ArrowBigUpDash, ArrowLeft, Brackets, CaseSensitive, Languages, LocateFixed, MoreVertical, PencilLine, Trash2 } from "lucide-react"; import { ArrowBigDownDash, ArrowBigUpDash, ArrowLeft, Brackets, CaseSensitive, Filter, Languages, LocateFixed, MoreVertical, PencilLine, Trash2 } from "lucide-react";
import { useTranslationInlineEdit } from "@/hooks/biz/use-translation-inline-edit"; import { useTranslationInlineEdit } from "@/hooks/biz/use-translation-inline-edit";
import { flattenEntries, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath } from "@/lib/i18n-structure"; import { flattenEntries, type FlatEntry, insertEntrySibling, removeEntryAtPath, renameEntryAtPath } from "@/lib/i18n-structure";
import { ImportLanguageModal } from "@/components/biz/import-language-modal"; import { ImportLanguageModal } from "@/components/biz/import-language-modal";
@ -31,6 +31,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuSeparator, DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useClipboard } from "@/hooks/use-clipboard"; import { useClipboard } from "@/hooks/use-clipboard";
@ -59,6 +60,7 @@ export default function Editor() {
const [aiBulkOpen, setAiBulkOpen] = useState(false); const [aiBulkOpen, setAiBulkOpen] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false);
const [selected, setSelected] = useState<Set<string>>(new Set()); const [selected, setSelected] = useState<Set<string>>(new Set());
const [visibleLangs, setVisibleLangs] = useState<Set<string>>(new Set());
const { copy } = useClipboard(); const { copy } = useClipboard();
@ -139,7 +141,14 @@ export default function Editor() {
onError: (m) => setPageError(m), onError: (m) => setPageError(m),
}); });
const tableWidth = useMemo(() => (languages.length * 200) + 36 + 60 + 300, [languages.length]); // 同步可见语言集合
useEffect(() => {
setVisibleLangs(new Set(languages));
}, [languages]);
const displayedLanguages = useMemo(() => languages.filter((l) => visibleLangs.has(l)), [languages, visibleLangs]);
const tableWidth = useMemo(() => (displayedLanguages.length * 200) + 36 + 60 + 300, [displayedLanguages.length]);
function computeSuggestedLanguages(pathsInput: string[]): string[] { function computeSuggestedLanguages(pathsInput: string[]): string[] {
if (languages.length === 0 || pathsInput.length === 0) return []; if (languages.length === 0 || pathsInput.length === 0) return [];
@ -204,12 +213,12 @@ export default function Editor() {
/> />
</th> </th>
<th style={{ width: 300 }} className="text-left px-3 py-2"></th> <th style={{ width: 300 }} className="text-left px-3 py-2"></th>
{languages.map((lang) => ( {displayedLanguages.map((lang) => (
<th key={lang} style={{ width: 200 }} className="text-left px-3 py-2">{lang}</th> <th key={lang} style={{ width: 200 }} className="text-left px-3 py-2">{lang}</th>
))} ))}
<th style={{ width: 60 }} className="sticky right-0 text-left px-3 py-2 bg-muted"></th> <th style={{ width: 60 }} className="sticky right-0 text-left px-3 py-2 bg-muted"></th>
</tr> </tr>
), [languages, allSelected, entries]); ), [displayedLanguages, allSelected, entries]);
const renderItemContent = useCallback((_idx: number, entry: FlatEntry) => { const renderItemContent = useCallback((_idx: number, entry: FlatEntry) => {
const handleCopy = () => { const handleCopy = () => {
@ -236,7 +245,7 @@ export default function Editor() {
{entry.path} {entry.path}
</button> </button>
</td> </td>
{languages.map((lang) => { {displayedLanguages.map((lang) => {
const isEditing = inline.isEditingCell(entry.path, lang); const isEditing = inline.isEditingCell(entry.path, lang);
const isSaving = inline.isSavingCell(entry.path, lang); const isSaving = inline.isSavingCell(entry.path, lang);
const displayValue = inline.getDisplayValue(entry.path, lang); const displayValue = inline.getDisplayValue(entry.path, lang);
@ -324,7 +333,7 @@ export default function Editor() {
</td> </td>
</> </>
); );
}, [languages, inline, projectId, structure, setStructure, setValuesByLang, copy, selected]); }, [displayedLanguages, inline, projectId, structure, setStructure, setValuesByLang, copy, selected]);
useEffect(() => { useEffect(() => {
if (!projectId || languages.length === 0) return; if (!projectId || languages.length === 0) return;
@ -414,6 +423,39 @@ export default function Editor() {
</Button> </Button>
</form> </form>
<div className="mb-2 flex items-center gap-2"> <div className="mb-2 flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" title="过滤显示的语言列">
<Filter />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
<DropdownMenuItem onClick={() => setVisibleLangs(new Set(languages))}>
</DropdownMenuItem>
<DropdownMenuSeparator />
{languages.map((lang) => {
const checked = visibleLangs.has(lang);
return (
<DropdownMenuCheckboxItem
key={lang}
onSelect={(e) => e.preventDefault()}
checked={checked}
onCheckedChange={(v) => {
setVisibleLangs((prev) => {
const next = new Set(prev);
if (v) next.add(lang); else next.delete(lang);
return next;
});
}}
>
{lang}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
<Button <Button
variant="outline" variant="outline"
disabled={selected.size === 0 || selected.size > MAX_AI_ITEMS} disabled={selected.size === 0 || selected.size > MAX_AI_ITEMS}