) => ,
+ }), [tableWidth]);
+
+ const headerContent = useCallback(() => (
+
+ |
+ {
+ const next = new Set();
+ if (checked) {
+ for (const en of entries) next.add(en.path);
+ }
+ setSelected(next);
+ }}
+ />
+ |
+ 翻译条目名称 |
+ {displayedLanguages.map((lang) => (
+ {lang} |
+ ))}
+ 操作 |
+
+ ), [allSelected, displayedLanguages, entries, setSelected]);
+
+ const renderItemContent = useCallback((_idx: number, entry: FlatEntry) => {
+ const handleCopy = () => {
+ copy(entry.path);
+ toast.success("复制成功");
+ };
+
+ return (
+ <>
+
+ {
+ setSelected((prev) => {
+ const next = new Set(prev);
+ if (checked) next.add(entry.path); else next.delete(entry.path);
+ return next;
+ });
+ }}
+ />
+ |
+
+
+ |
+ {displayedLanguages.map((lang) => {
+ const isEditing = inlineEdit.isEditingCell(entry.path, lang);
+ const isSaving = inlineEdit.isSavingCell(entry.path, lang);
+ const displayValue = inlineEdit.getDisplayValue(entry.path, lang);
+
+ return (
+
+ {isEditing ? (
+ |
+ );
+ })}
+
+
+
+
+
+
+ onOpenAddEntry(entry.path, "below")}>
+
+ 在下面新增
+
+ onOpenAddEntry(entry.path, "above")}>
+
+ 在上面新增
+
+
+ onOpenAiTranslate(entry.path)}>
+
+ AI 翻译
+
+
+ onOpenRenameEntry(entry.path)}>
+
+ 重命名
+
+
+ {
+ await onMoveEntry(entry.path, -moveCountUp);
+ }}
+ >
+
+ 向上移动
+ {
+ const n = parseInt(e.target.value, 10);
+ setMoveCountUp(Number.isFinite(n) && n > 0 ? n : 1);
+ }}
+ className="h-7 w-16"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ />
+ 个条目
+
+ {
+ await onMoveEntry(entry.path, moveCountDown);
+ }}
+ >
+
+ 向下移动
+ {
+ const n = parseInt(e.target.value, 10);
+ setMoveCountDown(Number.isFinite(n) && n > 0 ? n : 1);
+ }}
+ className="h-7 w-16"
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ />
+ 个条目
+
+
+ {
+ if (!confirm("确认删除该条目?此操作会移除所有语言下的该键")) return;
+ await onDeleteEntry(entry.path);
+ }}
+ >
+
+ 删除
+
+
+
+ |
+ >
+ );
+ }, [copy, displayedLanguages, inlineEdit, moveCountDown, moveCountUp, onDeleteEntry, onMoveEntry, onOpenAddEntry, onOpenAiTranslate, onOpenRenameEntry, selected, setSelected]);
+
+
+ return (
+
+
+
+
+
+
+
+
+ setVisibleLangs(new Set(languages))}>
+ 全部显示
+
+
+ {languages.map((lang) => {
+ const checked = visibleLangs.has(lang);
+ return (
+ 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}
+
+ );
+ })}
+
+
+
+ {selected.size > MAX_AI_ITEMS && (
+ 最多支持 {MAX_AI_ITEMS} 条
+ )}
+
+
+
+
entry.path}
+ scrollerRef={(el) => { scrollerRootRef.current = el; }}
+ />
+
+
+ );
+}
diff --git a/src/components/biz/editor/use-table-option-state.ts b/src/components/biz/editor/use-table-option-state.ts
new file mode 100644
index 0000000..0126b03
--- /dev/null
+++ b/src/components/biz/editor/use-table-option-state.ts
@@ -0,0 +1,16 @@
+import { useState } from "react";
+
+export function useTableOptionState() {
+ const [query, setQuery] = useState("");
+ const [caseSensitive, setCaseSensitive] = useState(false);
+ const [fullMatch, setFullMatch] = useState(false);
+
+ return {
+ query,
+ setQuery,
+ caseSensitive,
+ setCaseSensitive,
+ fullMatch,
+ setFullMatch,
+ };
+}
diff --git a/src/components/biz/project-sources-wizard.tsx b/src/components/biz/project-sources-wizard.tsx
index f80b2e9..4d7068a 100644
--- a/src/components/biz/project-sources-wizard.tsx
+++ b/src/components/biz/project-sources-wizard.tsx
@@ -175,9 +175,15 @@ export function ProjectSourcesWizard({
return (
- 请选择一个目录,其中包含多个语言子目录(如{" "}
- en/messages.json、zh-CN/messages.json)。
+ 请选择一个目录,其中包含多个语言子目录。示例:
+
{`en/
+ common.json
+ messages.json
+zh-CN/
+ common.json
+ messages.json
+`}
diff --git a/src/store/sources-store.ts b/src/store/sources-store.ts
index 40374a3..8b66212 100644
--- a/src/store/sources-store.ts
+++ b/src/store/sources-store.ts
@@ -20,7 +20,7 @@ export type ProjectSourcesState = {
type ProjectSourcesActions = {
setSources: (state: ProjectSourcesState) => void;
clear: () => void;
- getLanguagePath: (language: string) => string | undefined;
+ getLanguageMeta: (language: string) => LanguageFileMeta | undefined;
};
const initialState: ProjectSourcesState = {
@@ -38,8 +38,7 @@ export const useProjectSourcesStore = create
set(() => initialState),
- getLanguagePath: (language) => {
- const meta = get().languages.find((lang) => lang.language === language);
- return meta?.path;
+ getLanguageMeta: (language) => {
+ return get().languages.find((lang) => lang.language === language);
},
}));