Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/features/skill-install-drag-drop/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 实施计划

## 影响范围
- `src/renderer/settings/components/skills/SkillInstallDialog.vue`(唯一改动的组件)
- `src/renderer/src/i18n/*/settings.json`(文案)

不改动:主进程 `skillPresenter`、IPC 路由契约、preload。

## 设计
### 拖拽状态
在组件内维护 `dragActive`(`'folder' | 'zip' | null`)用于区分高亮哪个区域,避免引入额外依赖。`dragenter/dragover` 时 `preventDefault` 以允许 drop。

### drop 处理(统一函数 `handleDrop(e, zone)`)
1. `installing` 为真时忽略。
2. 取 `e.dataTransfer`。优先用 `items[i].webkitGetAsEntry()`:
- `entry.isDirectory` → 取对应 `File`,`getPathForFile` 得路径 → `tryInstallFromFolder`。
- `entry.isFile` 且扩展名 `.zip` → 取路径 → `tryInstallFromZip`。
- 其他 → toast `dragInvalid`。
3. 只处理第一个有效项;多项或无效项给出错误。

### 路径获取
`window.api.getPathForFile(file)`(settings 渲染进程已暴露,参考 `KnowledgeFile.vue`)。

### 模板
folder / zip 两个 `<div>` 增加 `@dragenter/@dragover/@dragleave/@drop` 与基于 `dragActive` 的高亮 class;保留 `@click`。

## i18n
- 更新 `folderHint` / `zipHint` 文案体现「点击或拖拽」(en-US、zh-CN、zh-HK、zh-TW)。
- 移除已无意义的 `dragNotSupported`,新增 `dragInvalid`(所有语言,非中文用英文兜底)。
- 运行 `pnpm run i18n` 校验。

## 验证
`pnpm run format` → `pnpm run i18n` → `pnpm run lint` → `pnpm run typecheck`。
33 changes: 33 additions & 0 deletions docs/features/skill-install-drag-drop/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Skill 安装拖拽交互

## 背景
`SkillInstallDialog.vue` 当前提供「文件夹 / ZIP / URL」三种安装方式,文件夹与 ZIP 标签页仅支持点击触发原生文件选择器(`border-dashed` 区域只是视觉样式,绑定 `@click`)。i18n 中预留的 `dragNotSupported` 文案也说明拖拽暂未实现。

## 目标
为「文件夹」与「ZIP」两个上传区域增加拖拽(drag & drop)交互,并兼容:
- 拖入**文件夹** → 走 `installFromFolder`
- 拖入 **.zip 文件** → 走 `installFromZip`

点击选择的既有行为保持不变。

## 用户故事
- 作为用户,我可以把一个 skill 文件夹直接拖到弹窗的上传区域完成安装,而不必逐级点击系统文件选择器。
- 作为用户,我可以把一个 `.zip` 包拖进来直接安装。
- 拖拽过程中上传区域有高亮反馈;拖入不支持的内容时给出明确错误提示。

## 验收标准
1. 文件夹 / ZIP 两个标签页的拖拽区域响应 `dragenter/dragover/dragleave/drop`,拖拽悬停时有高亮态。
2. 拖入目录调用文件夹安装;拖入单个 `.zip` 文件调用 ZIP 安装(按拖入内容自动路由,不强依赖当前标签页)。
3. 拖入非 `.zip` 文件或同时拖入多项不合法内容时,toast 报错且不发起安装。
4. 安装中(`installing`)禁止再次拖拽触发。
5. 成功/冲突/失败的处理沿用既有 `handleInstallResult` 逻辑(含覆盖确认弹窗)。
6. 既有点击选择行为不回归。

## 非目标
- URL 标签页不涉及拖拽。
- 不改动主进程安装逻辑(`skillPresenter`)与 IPC 契约。

## 约束 / 技术要点
- Electron 渲染进程通过 `window.api.getPathForFile(file)`(底层 `webUtils.getPathForFile`)获取拖拽文件/文件夹的本地绝对路径,主进程安装接口均基于绝对路径。
- 用 `DataTransferItem.webkitGetAsEntry()` 同步判断 drop 项是目录还是文件。
- 复用既有 `useDragAndDrop` 思路管理高亮态。
6 changes: 6 additions & 0 deletions docs/features/skill-install-drag-drop/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 任务清单

- [x] T1 `SkillInstallDialog.vue`:新增 `dragActive` 状态与 `handleDrop`/`onDragEnter`/`onDragLeave` 逻辑。
- [x] T2 模板:folder/zip 区域绑定拖拽事件与高亮态,保留点击。
- [x] T3 i18n:更新 `folderHint`/`zipHint`,移除 `dragNotSupported`,新增 `dragInvalid`。
- [x] T4 校验:`pnpm run format` / `i18n` / `lint` / `typecheck` 全通过。
93 changes: 85 additions & 8 deletions src/renderer/settings/components/skills/SkillInstallDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,29 @@

<TabsContent value="folder" class="mt-4">
<div
class="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary/50 transition-colors"
class="border-2 border-dashed rounded-lg p-8 text-center transition-colors"
:class="
dragActive === 'folder'
? 'border-primary bg-primary/5'
: 'hover:border-primary/50 cursor-pointer'
"
@click="selectFolder"
@dragenter.prevent="onDragEnter('folder')"
@dragover.prevent
@dragleave.prevent="onDragLeave"
@drop.prevent="handleDrop($event)"
>
<Icon
v-if="!installing"
icon="lucide:folder-open"
class="w-10 h-10 mx-auto text-muted-foreground mb-2"
class="w-10 h-10 mx-auto text-muted-foreground mb-2 pointer-events-none"
/>
<Icon
v-else
icon="lucide:loader-2"
class="w-10 h-10 mx-auto text-muted-foreground mb-2 animate-spin"
class="w-10 h-10 mx-auto text-muted-foreground mb-2 animate-spin pointer-events-none"
/>
<p class="text-sm text-muted-foreground">
<p class="text-sm text-muted-foreground pointer-events-none">
{{ t('settings.skills.install.folderHint') }}
</p>
</div>
Expand All @@ -50,20 +59,29 @@

<TabsContent value="zip" class="mt-4">
<div
class="border-2 border-dashed rounded-lg p-8 text-center hover:border-primary/50 transition-colors"
class="border-2 border-dashed rounded-lg p-8 text-center transition-colors"
:class="
dragActive === 'zip'
? 'border-primary bg-primary/5'
: 'hover:border-primary/50 cursor-pointer'
"
@click="selectZip"
@dragenter.prevent="onDragEnter('zip')"
@dragover.prevent
@dragleave.prevent="onDragLeave"
@drop.prevent="handleDrop($event)"
>
<Icon
v-if="!installing"
icon="lucide:file-archive"
class="w-10 h-10 mx-auto text-muted-foreground mb-2"
class="w-10 h-10 mx-auto text-muted-foreground mb-2 pointer-events-none"
/>
<Icon
v-else
icon="lucide:loader-2"
class="w-10 h-10 mx-auto text-muted-foreground mb-2 animate-spin"
class="w-10 h-10 mx-auto text-muted-foreground mb-2 animate-spin pointer-events-none"
/>
<p class="text-sm text-muted-foreground">
<p class="text-sm text-muted-foreground pointer-events-none">
{{ t('settings.skills.install.zipHint') }}
</p>
</div>
Expand Down Expand Up @@ -169,6 +187,9 @@ const activeTab = ref('folder')
const installUrl = ref('')
const installing = ref(false)

// Drag and drop state: which zone is currently being dragged over
const dragActive = ref<'folder' | 'zip' | null>(null)

// Conflict handling
const conflictDialogOpen = ref(false)
const conflictSkillName = ref('')
Expand All @@ -180,6 +201,7 @@ watch(isOpen, (open) => {
pendingInstallAction.value = null
conflictDialogOpen.value = false
conflictSkillName.value = ''
dragActive.value = null
}
})

Expand Down Expand Up @@ -231,6 +253,61 @@ const tryInstallFromZip = async (zipPath: string, overwrite = false) => {
}
}

// Drag and drop handlers
const onDragEnter = (zone: 'folder' | 'zip') => {
if (installing.value) return
dragActive.value = zone
}

const onDragLeave = () => {
dragActive.value = null
}

const handleDrop = async (event: DragEvent) => {
dragActive.value = null
if (installing.value) return

const items = event.dataTransfer?.items
const files = event.dataTransfer?.files
if (!items || items.length === 0) return

if (items.length > 1 || (files && files.length > 1)) {
showDropError()
return
}

const item = items[0]
const entry = item.webkitGetAsEntry?.()
const file = item.getAsFile?.()
if (!file) {
showDropError()
return
}

const path = window.api.getPathForFile(file)
if (!path) {
showDropError()
return
}

// Route by dropped content type, independent of the active tab
if (entry?.isDirectory) {
await tryInstallFromFolder(path)
} else if (file.name.toLowerCase().endsWith('.zip')) {
await tryInstallFromZip(path)
Comment on lines +279 to +297

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Chromium/Electron drag-and-drop, can DataTransferItem.getAsFile() return null for directory drops while webkitGetAsEntry().isDirectory is true?

💡 Result:

Yes, in Chromium and Electron, DataTransferItem.getAsFile will return null for a directory drop, even when webkitGetAsEntry.isDirectory is true [1][2][3]. This behavior occurs because DataTransferItem.getAsFile is designed specifically to return a File object, which represents a single file [3]. A directory is not a File, and therefore the method returns null [3]. In contrast, webkitGetAsEntry is a non-standard (but widely supported in Chromium-based environments) API that provides a FileSystemEntry interface [1][2]. This interface allows you to distinguish between files (FileSystemFileEntry) and directories (FileSystemDirectoryEntry) using the isDirectory and isFile properties [1][4]. When handling drag-and-drop operations that include directories, you should use the following approach: 1. Check the item.kind property (it should be 'file' for both files and directories) [2][5]. 2. Use webkitGetAsEntry to obtain the entry [1][6]. 3. Check entry.isDirectory to handle the item as a folder, or use it to explore the directory contents [1][4]. For modern applications, it is recommended to use the File System Access API's DataTransferItem.getAsFileSystemHandle if available, as it provides a more robust and standard-track way to handle directory and file handles [2][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/settings/components/skills/SkillInstallDialog.vue"

# Show the exact surrounding code for the review snippet and nearby variable declarations.
sed -n '240,340p' "$FILE" | cat -n

Repository: ThinkInAIXYZ/deepchat

Length of output: 3209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/settings/components/skills/SkillInstallDialog.vue"

# Find the implementation and usage of tryInstallFromFolder and window.api.getPathForFile usage nearby.
rg -n "tryInstallFromFolder|getPathForFile|webkitGetAsEntry|isDirectory" "$FILE"

Repository: ThinkInAIXYZ/deepchat

Length of output: 459


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/renderer/settings/components/skills/SkillInstallDialog.vue"

sed -n '180,260p' "$FILE" | cat -n

echo "----"
rg -n "getPathForFile" -S . | head -n 50

Repository: ThinkInAIXYZ/deepchat

Length of output: 5664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/renderer/settings/components/skills/SkillInstallDialog.vue"

# Inspect tryInstallFromFolder implementation and its expectations for folderPath.
sed -n '180,260p' "$FILE" | cat -n

echo "----"

# Locate window.api.getPathForFile implementation/typing to determine what it can derive for directories.
rg -n "getPathForFile" -S . | head -n 50

Repository: ThinkInAIXYZ/deepchat

Length of output: 5664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect getPathForFile implementation.
sed -n '1,140p' src/renderer/api/FileClient.ts | cat -n
echo "----"
sed -n '1,120p' src/preload/index.ts | cat -n
echo "----"
# Inspect docs that describe expected directory drop behavior.
sed -n '1,120p' docs/features/skill-install-drag-drop/plan.md | cat -n
echo "----"
sed -n '1,120p' docs/features/skill-install-drag-drop/spec.md | cat -n

Repository: ThinkInAIXYZ/deepchat

Length of output: 9931


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "installFromFolder\\(" -S src | head -n 50

Repository: ThinkInAIXYZ/deepchat

Length of output: 930


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/main/presenter/skillPresenter/index.ts"

# Show the installFromFolder implementation.
sed -n '1120,1205p' "$FILE" | cat -n

Repository: ThinkInAIXYZ/deepchat

Length of output: 3522


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/main/presenter/skillPresenter/index.ts"

rg -n "installFromDirectory\\(" "$FILE"

Repository: ThinkInAIXYZ/deepchat

Length of output: 418


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/main/presenter/skillPresenter/index.ts"

sed -n '1220,1320p' "$FILE" | cat -n

Repository: ThinkInAIXYZ/deepchat

Length of output: 3746


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/main/presenter/skillPresenter/index.ts"

sed -n '1220,1320p' "$FILE" | cat -n

Repository: ThinkInAIXYZ/deepchat

Length of output: 3746


🌐 Web query:

In Chromium/Electron drag-and-drop of directories, what does DataTransfer.files contain (does it include the directory itself or only files inside), and does item.getAsFile() return null while items[0].webkitGetAsEntry().isDirectory is true?

💡 Result:

In Chromium and Electron environments, the handling of directory drag-and-drop behaves as follows: DataTransfer.files behavior: The DataTransfer.files object (a FileList) typically contains only the files explicitly dragged or files at the top level of the dropped selection [1][2]. It does not automatically include the dropped directory itself as a File object, nor does it automatically include the contents of that directory recursively [1][3]. Depending on the specific browser implementation or version, a directory might be ignored entirely in the files list, or in some cases, appear as an empty File-like object [1][3]. Because of these inconsistencies and the lack of support for recursive traversal, developers should not rely on DataTransfer.files to handle directory structures [1][3]. getAsFile and webkitGetAsEntry: When you use the Drag-and-Drop API to drag a directory, the corresponding DataTransferItem has a kind of "file" [4][5]. However, the item is conceptually a directory entry, not a standard file [5]. 1. If you call item.getAsFile, it returns null because the item is a directory, not a File [6][7]. 2. If you call item.webkitGetAsEntry, it returns a DirectoryEntry (where.isDirectory is true) [1][8]. In summary, yes, when dragging a directory, item.getAsFile returns null, and simultaneously, items.webkitGetAsEntry.isDirectory is true [1][8][7]. To correctly process directories and their contents in Chromium/Electron, you must use the File System Access API (getAsFileSystemHandle) or the legacy FileSystem API (webkitGetAsEntry) to iterate through the directory entries [8][4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "webkitGetAsEntry\\(|isDirectory" src docs | head -n 100

Repository: ThinkInAIXYZ/deepchat

Length of output: 9864


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "webkitGetAsEntry\\(|isDirectory" src docs | head -n 100

Repository: ThinkInAIXYZ/deepchat

Length of output: 9864


Fix directory drag-drop handling in SkillInstallDialog.vue

handleDrop currently calls item.getAsFile?.() and errors out when it’s null; in Chromium/Electron this happens for directory drops even when item.webkitGetAsEntry()?.isDirectory is true, so folder installs can’t work.

Also, the proposed approach of using event.dataTransfer.files?.[0] is unreliable here: tryInstallFromFolder expects a directory root containing SKILL.md (installFromDirectory checks ${resolvedSource}/SKILL.md), while DataTransfer.files typically exposes files (or inconsistent/empty results) rather than the directory itself.

Handle entry.isDirectory before getAsFile(), and derive the actual dropped folder path (e.g., from a candidate absolute file path by walking up parents until SKILL.md is found, or by using directory traversal / getAsFileSystemHandle when available) before calling tryInstallFromFolder.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/settings/components/skills/SkillInstallDialog.vue` around lines
279 - 297, handleDrop is calling item.getAsFile() before checking
item.webkitGetAsEntry()?.isDirectory, which fails for directory drops in
Chromium/Electron; change the logic to check entry.isDirectory first and, when
true, resolve the actual folder root path before calling tryInstallFromFolder
(e.g., use item.webkitGetAsEntry()/getAsFileSystemHandle if available or derive
an absolute candidate path and walk up parent directories until you find
SKILL.md or the directory root), only falling back to
item.getAsFile()/tryInstallFromZip when entry is not a directory; update the
branch around item.webkitGetAsEntry, getAsFile, tryInstallFromFolder, and
tryInstallFromZip accordingly.

} else {
showDropError()
}
}

const showDropError = () => {
toast({
title: t('settings.skills.install.failed'),
description: t('settings.skills.install.dragInvalid'),
variant: 'destructive'
})
}

// URL validation helper
const isValidUrl = (url: string): boolean => {
try {
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/da-DK/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1758,9 +1758,9 @@
"emptyHint": "Klik på knappen „Tilføj skill“ for at installere en ny skill",
"install": {
"description": "Vælg skill-installationsmetode",
"dragNotSupported": "Træk og slip understøttes ikke endnu. Klik venligst for at vælge.",
"dragInvalid": "Indholdet understøttes ikke. Træk en skill-mappe eller en enkelt .zip-fil.",
"failed": "Installation mislykkedes",
"folderHint": "Klik for at vælge skill-mappen",
"folderHint": "Klik eller træk en skill-mappe hertil",
"folderTip": "Understøtter direkte import fra ~/.claude/skills/ og andre klienter",
"fromFolder": "Installér fra mappe",
"installButton": "installere",
Expand All @@ -1774,7 +1774,7 @@
"title": "Installer skill",
"urlHint": "Indtast downloadlinket til skill-pakken",
"urlPlaceholder": "Indtast skill ZIP-downloadadresse",
"zipHint": "Klik for at vælge ZIP-fil"
"zipHint": "Klik eller træk en ZIP-fil hertil"
},
"noResults": "Ingen matchende skill fundet",
"openFolder": "Åbn mappe",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/de-DE/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1791,17 +1791,17 @@
"tabUrl": "URL",
"fromFolder": "Aus Ordner installieren",
"selectFolder": "skill-Ordner auswählen",
"folderHint": "Klicken, um skill-Ordner auszuwählen",
"folderHint": "Klicken oder einen skill-Ordner hierher ziehen",
"folderTip": "Direkter Import aus anderen Clients wie ~/.claude/skills/ wird unterstützt",
"zipHint": "Klicken, um ZIP-Datei auszuwählen",
"zipHint": "Klicken oder eine ZIP-Datei hierher ziehen",
"urlPlaceholder": "Download-URL für skill-ZIP eingeben",
"urlHint": "Download-Link des skill-Pakets eingeben",
"installButton": "Installieren",
"installing": "Installation läuft...",
"success": "Installation erfolgreich",
"successMessage": "skill {name} wurde erfolgreich installiert",
"failed": "Installation fehlgeschlagen",
"dragNotSupported": "Drag-and-drop wird derzeit nicht unterstützt. Bitte klicken Sie zur Auswahl"
"dragInvalid": "Inhalt nicht unterstützt. Ziehen Sie einen skill-Ordner oder eine einzelne .zip-Datei."
},
"delete": {
"title": "skill löschen",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1851,17 +1851,17 @@
"tabUrl": "URL",
"fromFolder": "Install from Folder",
"selectFolder": "Select Skill Folder",
"folderHint": "Click to select a skill folder",
"folderHint": "Click or drag a skill folder here",
"folderTip": "Import skills from other clients like ~/.claude/skills/",
"zipHint": "Click to select a ZIP file",
"zipHint": "Click or drag a ZIP file here",
"urlPlaceholder": "Enter skill ZIP download URL",
"urlHint": "Enter the download link for the skill package",
"installButton": "Install",
"installing": "Installing...",
"success": "Installation Successful",
"successMessage": "Skill {name} has been installed successfully",
"failed": "Installation Failed",
"dragNotSupported": "Drag not supported, please click to select"
"dragInvalid": "Unsupported drop. Drag a skill folder or a single .zip file."
},
"delete": {
"title": "Delete Skill",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/es-ES/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1791,17 +1791,17 @@
"tabUrl": "URL",
"fromFolder": "Instalar desde carpeta",
"selectFolder": "Seleccionar carpeta de Skill",
"folderHint": "Haz clic para seleccionar una carpeta de Skill",
"folderHint": "Haz clic o arrastra aquí una carpeta de Skill",
"folderTip": "Permite importar directamente desde otros clientes, como ~/.claude/skills/",
"zipHint": "Haga clic para seleccionar un archivo ZIP",
"zipHint": "Haz clic o arrastra aquí un archivo ZIP",
"urlPlaceholder": "Introduce la URL de descarga ZIP de la Skill",
"urlHint": "Introduce el enlace de descarga del paquete de Skill",
"installButton": "Instalar",
"installing": "Instalando...",
"success": "Instalación exitosa",
"successMessage": "La Skill {name} se ha instalado correctamente",
"failed": "Instalación fallida",
"dragNotSupported": "No se admite el arrastre, haga clic para seleccionar"
"dragInvalid": "Contenido no admitido. Arrastra una carpeta de Skill o un único archivo .zip."
},
"delete": {
"title": "Eliminar Skill",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/fa-IR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1825,9 +1825,9 @@
"emptyHint": "برای نصب skill جدید، روی دکمه «افزودن skill» کلیک کنید.",
"install": {
"description": "انتخاب روش نصب skill",
"dragNotSupported": "کشیدن و رها کردن فعلاً پشتیبانی نمی‌شود، لطفاً کلیک کنید تا انتخاب کنید",
"dragInvalid": "محتوای کشیده‌شده پشتیبانی نمی‌شود. یک پوشه skill یا یک فایل .zip را بکشید.",
"failed": "نصب ناموفق",
"folderHint": "کلیک کنید تا پوشه skill را انتخاب کنید",
"folderHint": "برای انتخاب پوشه skill کلیک کنید یا آن را به اینجا بکشید",
"folderTip": "پشتیبانی از وارد کردن مستقیم از ~/.claude/skills/ و سایر کلاینت‌ها",
"fromFolder": "نصب از پوشه",
"installButton": "نصب",
Expand All @@ -1841,7 +1841,7 @@
"title": "نصب مهارت",
"urlHint": "لینک دانلود بسته skill را وارد کنید",
"urlPlaceholder": "آدرس دانلود ZIP skill را وارد کنید",
"zipHint": "برای انتخاب فایل ZIP کلیک کنید"
"zipHint": "برای انتخاب فایل ZIP کلیک کنید یا آن را به اینجا بکشید"
},
"noResults": "مهارت مطابقی یافت نشد",
"openFolder": "باز کردن پوشه",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/fr-FR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1825,9 +1825,9 @@
"emptyHint": "Cliquez sur le bouton « Ajouter une compétence » pour installer une nouvelle compétence",
"install": {
"description": "Choisir le mode d’installation de la skill",
"dragNotSupported": "Le glisser-déposer n’est pas pris en charge pour le moment. Veuillez cliquer pour sélectionner.",
"dragInvalid": "Contenu non pris en charge. Glissez un dossier skill ou un seul fichier .zip.",
"failed": "Installation échouée",
"folderHint": "Cliquez pour sélectionner le dossier skill",
"folderHint": "Cliquez ou glissez un dossier skill ici",
"folderTip": "Supporte l’importation directe depuis ~/.claude/skills/ et autres clients",
"fromFolder": "Installer depuis le dossier",
"installButton": "Installation",
Expand All @@ -1841,7 +1841,7 @@
"title": "Installer skill",
"urlHint": "Entrez le lien de téléchargement du package skill",
"urlPlaceholder": "Saisir l'adresse de téléchargement du fichier ZIP skill",
"zipHint": "Cliquez pour sélectionner le fichier ZIP"
"zipHint": "Cliquez ou glissez un fichier ZIP ici"
},
"noResults": "Aucune compétence correspondante trouvée",
"openFolder": "Ouvrir le dossier",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/he-IL/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1825,9 +1825,9 @@
"emptyHint": "לחץ על כפתור „הוסף skill“ להתקנת skill חדש",
"install": {
"description": "בחר שיטת התקנת skill",
"dragNotSupported": "גרירה ושחרור זמנית לא נתמכות, נא ללחוץ לבחירה",
"dragInvalid": "התוכן אינו נתמך. גרור תיקיית skill או קובץ .zip יחיד.",
"failed": "התקנה נכשלה",
"folderHint": "לחץ כדי לבחור את תיקיית skill",
"folderHint": "לחץ או גרור לכאן תיקיית skill",
"folderTip": "תומך בייבוא ישיר מ־~/.claude/skills/ ומקליינטים אחרים וכדומה",
"fromFolder": "התקנה מתיקייה",
"installButton": "התקנה",
Expand All @@ -1841,7 +1841,7 @@
"title": "התקן skill",
"urlHint": "הזן את קישור ההורדה של חבילת ה-skill",
"urlPlaceholder": "הזן כתובת הורדת skill ZIP",
"zipHint": "לחץ כדי לבחור קובץ ZIP"
"zipHint": "לחץ או גרור לכאן קובץ ZIP"
},
"noResults": "לא נמצא כישור תואם",
"openFolder": "פתח תיקייה",
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/src/i18n/id-ID/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1791,17 +1791,17 @@
"tabUrl": "URL",
"fromFolder": "Instal dari folder",
"selectFolder": "Pilih folder skill",
"folderHint": "Klik untuk memilih folder skill",
"folderHint": "Klik atau seret folder skill ke sini",
"folderTip": "Mendukung impor langsung dari klien lain seperti ~/.claude/skills/",
"zipHint": "Klik untuk memilih file ZIP",
"zipHint": "Klik atau seret file ZIP ke sini",
"urlPlaceholder": "Masukkan skill ZIP alamat unduhan",
"urlHint": "Masukkan tautan unduhan untuk paket skill",
"installButton": "Instalasi",
"installing": "Memasang...",
"success": "Instalasi berhasil",
"successMessage": "skill {name} berhasil diinstal",
"failed": "Instalasi gagal",
"dragNotSupported": "Seret dan lepas belum didukung, silakan klik untuk memilih"
"dragInvalid": "Konten tidak didukung. Seret folder skill atau satu file .zip."
},
"delete": {
"title": "Hapus skill",
Expand Down
Loading