mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for claude code plugins
This commit is contained in:
parent
3ff23a2cb2
commit
7ddaa3a005
6 changed files with 403 additions and 106 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Modal, Form, Steps, Button, Checkbox } from "antd";
|
||||
import { Text, Title, Badge } from "@tremor/react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { enableClaudeCodePlugin, disableClaudeCodePlugin } from "../networking";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import { Plugin } from "./types";
|
||||
|
|
@ -22,6 +23,7 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
skillsList,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const [selectedSkills, setSelectedSkills] = useState<Set<string>>(new Set());
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -36,7 +38,7 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
|
||||
const handleNext = () => {
|
||||
if (selectedSkills.size === 0) {
|
||||
NotificationsManager.fromBackend("Please select at least one skill");
|
||||
NotificationsManager.fromBackend(t("claudeCodePluginsPage.makeSkillPublicForm.selectAtLeastOne"));
|
||||
return;
|
||||
}
|
||||
setCurrentStep(1);
|
||||
|
|
@ -69,7 +71,7 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
|
||||
const handleSubmit = async () => {
|
||||
if (selectedSkills.size === 0) {
|
||||
NotificationsManager.fromBackend("Please select at least one skill");
|
||||
NotificationsManager.fromBackend(t("claudeCodePluginsPage.makeSkillPublicForm.selectAtLeastOne"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -89,12 +91,14 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
}),
|
||||
);
|
||||
|
||||
NotificationsManager.success(`Skill Hub updated — ${selectedSkills.size} skill(s) published`);
|
||||
NotificationsManager.success(
|
||||
t("claudeCodePluginsPage.makeSkillPublicForm.hubUpdated", { count: selectedSkills.size }),
|
||||
);
|
||||
handleClose();
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
console.error("Error publishing skills:", error);
|
||||
NotificationsManager.fromBackend("Failed to update skills. Please try again.");
|
||||
NotificationsManager.fromBackend(t("claudeCodePluginsPage.makeSkillPublicForm.updateFailed"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -106,26 +110,24 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
const renderStep1 = () => (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Title>Select Skills to Publish</Title>
|
||||
<Title>{t("claudeCodePluginsPage.makeSkillPublicForm.selectSkillsTitle")}</Title>
|
||||
<Checkbox
|
||||
checked={allSelected}
|
||||
indeterminate={isIndeterminate}
|
||||
onChange={(e) => handleSelectAll(e.target.checked)}
|
||||
disabled={skillsList.length === 0}
|
||||
>
|
||||
Select All ({skillsList.length})
|
||||
{t("claudeCodePluginsPage.makeSkillPublicForm.selectAll", { count: skillsList.length })}
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
<Text className="text-sm text-gray-600">
|
||||
Selected skills will be visible to all users in the Skill Hub. Deselected skills will be unpublished.
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-600">{t("claudeCodePluginsPage.makeSkillPublicForm.visibilityNote")}</Text>
|
||||
|
||||
<div className="max-h-96 overflow-y-auto border rounded-lg p-4">
|
||||
<div className="space-y-3">
|
||||
{skillsList.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
<Text>No skills registered yet.</Text>
|
||||
<Text>{t("claudeCodePluginsPage.makeSkillPublicForm.noSkillsRegistered")}</Text>
|
||||
</div>
|
||||
) : (
|
||||
skillsList.map((skill) => (
|
||||
|
|
@ -139,7 +141,7 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
<Text className="font-medium font-mono text-sm">{skill.name}</Text>
|
||||
{skill.enabled && (
|
||||
<Badge color="green" size="xs">
|
||||
Public
|
||||
{t("claudeCodePluginsPage.makeSkillPublicForm.publicBadge")}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -161,7 +163,11 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
{selectedSkills.size > 0 && (
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||
<Text className="text-sm text-blue-800">
|
||||
<strong>{selectedSkills.size}</strong> skill{selectedSkills.size !== 1 ? "s" : ""} will be published
|
||||
<Trans
|
||||
i18nKey="claudeCodePluginsPage.makeSkillPublicForm.willBePublished"
|
||||
count={selectedSkills.size}
|
||||
components={{ strong: <strong /> }}
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -170,17 +176,16 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
|
||||
const renderStep2 = () => (
|
||||
<div className="space-y-4">
|
||||
<Title>Confirm Publish to Skill Hub</Title>
|
||||
<Title>{t("claudeCodePluginsPage.makeSkillPublicForm.confirmTitle")}</Title>
|
||||
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<Text className="text-sm text-yellow-800">
|
||||
<strong>Note:</strong> Published skills will be visible to all users in the Skill Hub tab. Skills not in the
|
||||
list below will be unpublished.
|
||||
<Trans i18nKey="claudeCodePluginsPage.makeSkillPublicForm.confirmNote" components={{ strong: <strong /> }} />
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Text className="font-medium">Skills to be published:</Text>
|
||||
<Text className="font-medium">{t("claudeCodePluginsPage.makeSkillPublicForm.skillsToBePublished")}</Text>
|
||||
<div className="max-h-48 overflow-y-auto border rounded-lg p-3">
|
||||
<div className="space-y-2">
|
||||
{Array.from(selectedSkills).map((name) => {
|
||||
|
|
@ -202,7 +207,11 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||||
<Text className="text-sm text-blue-800">
|
||||
Total: <strong>{selectedSkills.size}</strong> skill{selectedSkills.size !== 1 ? "s" : ""} will be published
|
||||
<Trans
|
||||
i18nKey="claudeCodePluginsPage.makeSkillPublicForm.totalWillBePublished"
|
||||
count={selectedSkills.size}
|
||||
components={{ strong: <strong /> }}
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -210,7 +219,7 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title="Publish to Skill Hub"
|
||||
title={t("claudeCodePluginsPage.makeSkillPublicForm.modalTitle")}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
footer={null}
|
||||
|
|
@ -219,25 +228,25 @@ const MakeSkillPublicForm: React.FC<MakeSkillPublicFormProps> = ({
|
|||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Steps current={currentStep} className="mb-6">
|
||||
<Step title="Select Skills" />
|
||||
<Step title="Confirm" />
|
||||
<Step title={t("claudeCodePluginsPage.makeSkillPublicForm.stepSelectSkills")} />
|
||||
<Step title={t("common.confirm")} />
|
||||
</Steps>
|
||||
|
||||
{currentStep === 0 ? renderStep1() : renderStep2()}
|
||||
|
||||
<div className="flex justify-between mt-6">
|
||||
<Button onClick={currentStep === 0 ? handleClose : () => setCurrentStep(0)}>
|
||||
{currentStep === 0 ? "Cancel" : "Previous"}
|
||||
{currentStep === 0 ? t("common.cancel") : t("common.previous")}
|
||||
</Button>
|
||||
<div className="flex space-x-2">
|
||||
{currentStep === 0 && (
|
||||
<Button onClick={handleNext} disabled={selectedSkills.size === 0}>
|
||||
Next
|
||||
{t("common.next")}
|
||||
</Button>
|
||||
)}
|
||||
{currentStep === 1 && (
|
||||
<Button onClick={handleSubmit} loading={loading}>
|
||||
Publish to Hub
|
||||
{t("claudeCodePluginsPage.makeSkillPublicForm.publishToHub")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { Modal, Form, Input, Select } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { Button } from "@tremor/react";
|
||||
import { registerClaudeCodePlugin } from "../networking";
|
||||
|
|
@ -101,6 +102,7 @@ function parseGitHubUrl(raw: string): ParsePreview | null {
|
|||
}
|
||||
|
||||
const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessToken, onSuccess }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [urlPreview, setUrlPreview] = useState<ParsePreview | null>(null);
|
||||
|
|
@ -120,32 +122,32 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
|
||||
const handleSubmit = async (values: any) => {
|
||||
if (!accessToken) {
|
||||
MessageManager.error("No access token available");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.noAccessToken"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!urlPreview) {
|
||||
MessageManager.error("Please enter a valid GitHub URL");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.enterValidGithubUrl"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validatePluginName(values.name)) {
|
||||
MessageManager.error("Skill name must be kebab-case (lowercase letters, numbers, and hyphens only)");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.invalidSkillName"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.version && !isValidSemanticVersion(values.version)) {
|
||||
MessageManager.error("Version must be in semantic versioning format (e.g., 1.0.0)");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.invalidVersion"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.authorEmail && !isValidEmail(values.authorEmail)) {
|
||||
MessageManager.error("Invalid email format");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.invalidEmail"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.homepage && !isValidUrl(values.homepage)) {
|
||||
MessageManager.error("Invalid homepage URL format");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.invalidHomepageUrl"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -170,14 +172,14 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
if (values.namespace) pluginData.namespace = values.namespace.trim();
|
||||
|
||||
await registerClaudeCodePlugin(accessToken, pluginData);
|
||||
MessageManager.success("Skill registered successfully");
|
||||
MessageManager.success(t("claudeCodePluginsPage.addPluginForm.registerSuccess"));
|
||||
form.resetFields();
|
||||
setUrlPreview(null);
|
||||
onSuccess();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error registering skill:", error);
|
||||
MessageManager.error("Failed to register skill");
|
||||
MessageManager.error(t("claudeCodePluginsPage.addPluginForm.registerFailed"));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
|
@ -190,14 +192,21 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
};
|
||||
|
||||
return (
|
||||
<Modal title="Add New Skill" open={visible} onCancel={handleCancel} footer={null} width={700} className="top-8">
|
||||
<Modal
|
||||
title={t("claudeCodePluginsPage.addPluginForm.modalTitle")}
|
||||
open={visible}
|
||||
onCancel={handleCancel}
|
||||
footer={null}
|
||||
width={700}
|
||||
className="top-8"
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSubmit} className="mt-4">
|
||||
{/* Smart URL Input */}
|
||||
<Form.Item
|
||||
label="GitHub URL"
|
||||
label={t("claudeCodePluginsPage.addPluginForm.githubUrlLabel")}
|
||||
name="skillUrl"
|
||||
rules={[{ required: true, message: "Please enter a GitHub URL" }]}
|
||||
tooltip="Paste a GitHub URL — repo, folder, or file link. E.g. github.com/org/repo or github.com/org/repo/tree/main/my-skill"
|
||||
rules={[{ required: true, message: t("claudeCodePluginsPage.addPluginForm.githubUrlRequired") }]}
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.githubUrlTooltip")}
|
||||
>
|
||||
<Input
|
||||
placeholder="https://github.com/org/repo/tree/main/my-skill"
|
||||
|
|
@ -209,22 +218,22 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
{/* Parsed preview */}
|
||||
{urlPreview && (
|
||||
<div className="mb-4 px-3 py-2 bg-blue-50 border border-blue-200 rounded-lg text-sm text-blue-700">
|
||||
Detected: {urlPreview.label}
|
||||
{t("claudeCodePluginsPage.addPluginForm.detected", { label: urlPreview.label })}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Skill Name */}
|
||||
<Form.Item
|
||||
label="Skill Name"
|
||||
label={t("claudeCodePluginsPage.addPluginForm.skillNameLabel")}
|
||||
name="name"
|
||||
rules={[
|
||||
{ required: true, message: "Please enter skill name" },
|
||||
{ required: true, message: t("claudeCodePluginsPage.addPluginForm.skillNameRequired") },
|
||||
{
|
||||
pattern: /^[a-z0-9-]+$/,
|
||||
message: "Name must be kebab-case (lowercase, numbers, hyphens only)",
|
||||
message: t("claudeCodePluginsPage.addPluginForm.skillNamePattern"),
|
||||
},
|
||||
]}
|
||||
tooltip="Unique identifier in kebab-case format (e.g., my-skill)"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.skillNameTooltip")}
|
||||
>
|
||||
<Input placeholder="my-skill" className="rounded-lg" />
|
||||
</Form.Item>
|
||||
|
|
@ -232,32 +241,45 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
{/* Domain and Namespace — side by side */}
|
||||
<div className="flex gap-4">
|
||||
<Form.Item
|
||||
label="Domain (Optional)"
|
||||
label={t("claudeCodePluginsPage.addPluginForm.domainLabel")}
|
||||
name="domain"
|
||||
tooltip="Top-level grouping in the Skill Hub (e.g., Productivity)"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.domainTooltip")}
|
||||
className="flex-1"
|
||||
>
|
||||
<Input placeholder="Productivity" className="rounded-lg" />
|
||||
<Input placeholder={t("claudeCodePluginsPage.addPluginForm.domainPlaceholder")} className="rounded-lg" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Namespace (Optional)"
|
||||
label={t("claudeCodePluginsPage.addPluginForm.namespaceLabel")}
|
||||
name="namespace"
|
||||
tooltip="Sub-grouping within domain (e.g., workflows)"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.namespaceTooltip")}
|
||||
className="flex-1"
|
||||
>
|
||||
<Input placeholder="workflows" className="rounded-lg" />
|
||||
<Input placeholder={t("claudeCodePluginsPage.addPluginForm.namespacePlaceholder")} className="rounded-lg" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<Form.Item label="Description (Optional)" name="description" tooltip="Brief description of what the skill does">
|
||||
<TextArea rows={3} placeholder="A skill that helps with..." maxLength={500} className="rounded-lg" />
|
||||
<Form.Item
|
||||
label={t("claudeCodePluginsPage.addPluginForm.descriptionLabel")}
|
||||
name="description"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.descriptionTooltip")}
|
||||
>
|
||||
<TextArea
|
||||
rows={3}
|
||||
placeholder={t("claudeCodePluginsPage.addPluginForm.descriptionPlaceholder")}
|
||||
maxLength={500}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* Category */}
|
||||
<Form.Item label="Category (Optional)" name="category" tooltip="Select a category or enter a custom one">
|
||||
<Form.Item
|
||||
label={t("claudeCodePluginsPage.addPluginForm.categoryLabel")}
|
||||
name="category"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.categoryTooltip")}
|
||||
>
|
||||
<Select
|
||||
placeholder="Select or type a category"
|
||||
placeholder={t("claudeCodePluginsPage.addPluginForm.categoryPlaceholder")}
|
||||
allowClear
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
|
|
@ -272,26 +294,38 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
</Form.Item>
|
||||
|
||||
{/* Keywords */}
|
||||
<Form.Item label="Keywords (Optional)" name="keywords" tooltip="Comma-separated list of keywords for search">
|
||||
<Input placeholder="search, web, api" className="rounded-lg" />
|
||||
<Form.Item
|
||||
label={t("claudeCodePluginsPage.addPluginForm.keywordsLabel")}
|
||||
name="keywords"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.keywordsTooltip")}
|
||||
>
|
||||
<Input placeholder={t("claudeCodePluginsPage.addPluginForm.keywordsPlaceholder")} className="rounded-lg" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Version */}
|
||||
<Form.Item label="Version (Optional)" name="version" tooltip="Semantic version (e.g., 1.0.0)">
|
||||
<Form.Item
|
||||
label={t("claudeCodePluginsPage.addPluginForm.versionLabel")}
|
||||
name="version"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.versionTooltip")}
|
||||
>
|
||||
<Input placeholder="1.0.0" className="rounded-lg" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Author Name */}
|
||||
<Form.Item label="Author Name (Optional)" name="authorName" tooltip="Name of the skill author or organization">
|
||||
<Input placeholder="Your Name or Organization" className="rounded-lg" />
|
||||
<Form.Item
|
||||
label={t("claudeCodePluginsPage.addPluginForm.authorNameLabel")}
|
||||
name="authorName"
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.authorNameTooltip")}
|
||||
>
|
||||
<Input placeholder={t("claudeCodePluginsPage.addPluginForm.authorNamePlaceholder")} className="rounded-lg" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Author Email */}
|
||||
<Form.Item
|
||||
label="Author Email (Optional)"
|
||||
label={t("claudeCodePluginsPage.addPluginForm.authorEmailLabel")}
|
||||
name="authorEmail"
|
||||
rules={[{ type: "email", message: "Please enter a valid email" }]}
|
||||
tooltip="Contact email for the skill author"
|
||||
rules={[{ type: "email", message: t("claudeCodePluginsPage.addPluginForm.authorEmailRule") }]}
|
||||
tooltip={t("claudeCodePluginsPage.addPluginForm.authorEmailTooltip")}
|
||||
>
|
||||
<Input type="email" placeholder="author@example.com" className="rounded-lg" />
|
||||
</Form.Item>
|
||||
|
|
@ -300,10 +334,12 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
|
|||
<Form.Item className="mb-0 mt-6">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" onClick={handleCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type="submit" loading={isSubmitting}>
|
||||
{isSubmitting ? "Adding..." : "Add Skill"}
|
||||
{isSubmitting
|
||||
? t("claudeCodePluginsPage.addPluginForm.adding")
|
||||
: t("claudeCodePluginsPage.addPluginForm.addSkill")}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { Badge, Button, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "@tremor/react";
|
||||
import { Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import { getCategoryBadgeColor } from "./helpers";
|
||||
import { Plugin } from "./types";
|
||||
|
|
@ -32,6 +33,7 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
isAdmin,
|
||||
onPluginClick,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [sorting, setSorting] = useState<SortingState>([{ id: "created_at", desc: true }]);
|
||||
|
||||
const formatDate = (dateString?: string) => {
|
||||
|
|
@ -42,12 +44,12 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
NotificationsManager.success("Copied to clipboard!");
|
||||
NotificationsManager.success(t("claudeCodePluginsPage.pluginTable.copiedToClipboard"));
|
||||
};
|
||||
|
||||
const columns: ColumnDef<Plugin>[] = [
|
||||
{
|
||||
header: "Skill Name",
|
||||
header: t("claudeCodePluginsPage.pluginTable.colSkillName"),
|
||||
accessorKey: "name",
|
||||
cell: ({ row }) => {
|
||||
const plugin = row.original;
|
||||
|
|
@ -64,7 +66,7 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
{name}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy Plugin ID">
|
||||
<Tooltip title={t("claudeCodePluginsPage.pluginTable.copyPluginId")}>
|
||||
<CopyOutlined
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -78,18 +80,18 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Version",
|
||||
header: t("claudeCodePluginsPage.pluginTable.colVersion"),
|
||||
accessorKey: "version",
|
||||
cell: ({ row }) => {
|
||||
const version = row.original.version || "N/A";
|
||||
const version = row.original.version || t("claudeCodePluginsPage.pluginTable.versionNA");
|
||||
return <span className="text-xs text-gray-600">{version}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Description",
|
||||
header: t("common.description"),
|
||||
accessorKey: "description",
|
||||
cell: ({ row }) => {
|
||||
const description = row.original.description || "No description";
|
||||
const description = row.original.description || t("claudeCodePluginsPage.pluginTable.noDescription");
|
||||
return (
|
||||
<Tooltip title={description}>
|
||||
<span className="text-xs text-gray-600 block max-w-[300px] truncate">{description}</span>
|
||||
|
|
@ -98,14 +100,14 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Category",
|
||||
header: t("claudeCodePluginsPage.pluginTable.colCategory"),
|
||||
accessorKey: "category",
|
||||
cell: ({ row }) => {
|
||||
const category = row.original.category;
|
||||
if (!category) {
|
||||
return (
|
||||
<Badge color="gray" className="text-xs font-normal" size="xs">
|
||||
Uncategorized
|
||||
{t("claudeCodePluginsPage.pluginTable.uncategorized")}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
|
@ -118,19 +120,19 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
header: "Public",
|
||||
header: t("claudeCodePluginsPage.pluginTable.colPublic"),
|
||||
accessorKey: "enabled",
|
||||
cell: ({ row }) => {
|
||||
const plugin = row.original;
|
||||
return (
|
||||
<Badge color={plugin.enabled ? "green" : "gray"} className="text-xs font-normal" size="xs">
|
||||
{plugin.enabled ? "Yes" : "No"}
|
||||
{plugin.enabled ? t("common.yes") : t("common.no")}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created At",
|
||||
header: t("common.createdAt"),
|
||||
accessorKey: "created_at",
|
||||
cell: ({ row }) => {
|
||||
const plugin = row.original;
|
||||
|
|
@ -144,7 +146,7 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
header: "Actions",
|
||||
header: t("common.actions"),
|
||||
id: "actions",
|
||||
enableSorting: false,
|
||||
cell: ({ row }: any) => {
|
||||
|
|
@ -152,7 +154,7 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip title="Delete skill">
|
||||
<Tooltip title={t("claudeCodePluginsPage.pluginTable.deleteSkill")}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
|
|
@ -227,7 +229,7 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>Loading...</p>
|
||||
<p>{t("common.loading")}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -256,7 +258,7 @@ const PluginTable: React.FC<PluginTableProps> = ({
|
|||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-8 text-center">
|
||||
<div className="text-center text-gray-500">
|
||||
<p>No skills found. Add one to get started.</p>
|
||||
<p>{t("claudeCodePluginsPage.pluginTable.noSkillsFound")}</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { ArrowLeftOutlined, CopyOutlined, CheckOutlined, LinkOutlined } from "@ant-design/icons";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { formatInstallCommand } from "./helpers";
|
||||
import { Plugin } from "./types";
|
||||
|
||||
|
|
@ -12,6 +13,7 @@ interface SkillDetailProps {
|
|||
}
|
||||
|
||||
const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const [copiedKey, setCopiedKey] = useState<string | null>(null);
|
||||
|
||||
|
|
@ -32,17 +34,30 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
const installCommand = formatInstallCommand(skill);
|
||||
|
||||
const detailRows = [
|
||||
...(skill.category ? [{ property: "Category", value: skill.category }] : []),
|
||||
...(skill.domain ? [{ property: "Domain", value: skill.domain }] : []),
|
||||
...(skill.namespace ? [{ property: "Namespace", value: skill.namespace }] : []),
|
||||
...(skill.version ? [{ property: "Version", value: skill.version }] : []),
|
||||
...(skill.author?.name ? [{ property: "Author", value: skill.author.name }] : []),
|
||||
...(skill.created_at ? [{ property: "Added", value: new Date(skill.created_at).toLocaleDateString() }] : []),
|
||||
...(skill.category
|
||||
? [{ property: t("claudeCodePluginsPage.skillDetail.propCategory"), value: skill.category }]
|
||||
: []),
|
||||
...(skill.domain ? [{ property: t("claudeCodePluginsPage.skillDetail.propDomain"), value: skill.domain }] : []),
|
||||
...(skill.namespace
|
||||
? [{ property: t("claudeCodePluginsPage.skillDetail.propNamespace"), value: skill.namespace }]
|
||||
: []),
|
||||
...(skill.version ? [{ property: t("claudeCodePluginsPage.skillDetail.propVersion"), value: skill.version }] : []),
|
||||
...(skill.author?.name
|
||||
? [{ property: t("claudeCodePluginsPage.skillDetail.propAuthor"), value: skill.author.name }]
|
||||
: []),
|
||||
...(skill.created_at
|
||||
? [
|
||||
{
|
||||
property: t("claudeCodePluginsPage.skillDetail.propAdded"),
|
||||
value: new Date(skill.created_at).toLocaleDateString(),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const tabs = [
|
||||
{ key: "overview", label: "Overview" },
|
||||
{ key: "usage", label: "How to Use" },
|
||||
{ key: "overview", label: t("claudeCodePluginsPage.skillDetail.tabOverview") },
|
||||
{ key: "usage", label: t("claudeCodePluginsPage.skillDetail.tabUsage") },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
@ -61,7 +76,7 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
}}
|
||||
>
|
||||
<ArrowLeftOutlined style={{ fontSize: 11 }} />
|
||||
<span>Skills</span>
|
||||
<span>{t("claudeCodePluginsPage.skillDetail.backLink")}</span>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
|
|
@ -100,13 +115,17 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
<div style={{ display: "flex", gap: 64 }}>
|
||||
{/* Left column */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 4px 0" }}>Skill Details</h2>
|
||||
<p style={{ fontSize: 13, color: "#5f6368", margin: "0 0 16px 0" }}>Metadata registered with this skill</p>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 4px 0" }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.skillDetailsHeading")}
|
||||
</h2>
|
||||
<p style={{ fontSize: 13, color: "#5f6368", margin: "0 0 16px 0" }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.skillDetailsSubtitle")}
|
||||
</p>
|
||||
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: "1px solid #dadce0" }}>
|
||||
<th style={{ textAlign: "left", padding: "12px 0", color: "#5f6368", fontWeight: 500, width: 160 }}>
|
||||
Property
|
||||
{t("claudeCodePluginsPage.skillDetail.tableColProperty")}
|
||||
</th>
|
||||
<th style={{ textAlign: "left", padding: "12px 0", color: "#5f6368", fontWeight: 500 }}>
|
||||
{skill.name}
|
||||
|
|
@ -127,7 +146,9 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
{/* Right sidebar */}
|
||||
<div style={{ width: 240, flexShrink: 0 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>Status</div>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.sidebarStatus")}
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
|
|
@ -138,13 +159,17 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{skill.enabled ? "Public" : "Draft"}
|
||||
{skill.enabled
|
||||
? t("claudeCodePluginsPage.skillDetail.statusPublic")
|
||||
: t("claudeCodePluginsPage.skillDetail.statusDraft")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{sourceUrl && (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>Source</div>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.sidebarSource")}
|
||||
</div>
|
||||
<a
|
||||
href={sourceUrl}
|
||||
target="_blank"
|
||||
|
|
@ -166,7 +191,9 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
|
||||
{skill.keywords && skill.keywords.length > 0 && (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 8 }}>Tags</div>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 8 }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.sidebarTags")}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
|
||||
{skill.keywords.map((kw) => (
|
||||
<span
|
||||
|
|
@ -188,7 +215,9 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
)}
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>Skill ID</div>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.sidebarSkillId")}
|
||||
</div>
|
||||
<div style={{ fontSize: 12, fontFamily: "monospace", color: "#3c4043", wordBreak: "break-all" }}>
|
||||
{skill.id}
|
||||
</div>
|
||||
|
|
@ -200,9 +229,11 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
{/* How to Use tab */}
|
||||
{activeTab === "usage" && (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 8px 0" }}>Using this skill</h2>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 8px 0" }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.usingThisSkill")}
|
||||
</h2>
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 24px 0", lineHeight: 1.6 }}>
|
||||
Once your proxy is set as a marketplace, enable this skill in Claude Code with one command:
|
||||
{t("claudeCodePluginsPage.skillDetail.usageInstruction")}
|
||||
</p>
|
||||
|
||||
{/* Install command */}
|
||||
|
|
@ -224,7 +255,9 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
borderBottom: "1px solid #dadce0",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>Run in Claude Code</span>
|
||||
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>
|
||||
{t("claudeCodePluginsPage.skillDetail.runInClaudeCode")}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => copyToClipboard(installCommand, "install")}
|
||||
style={{
|
||||
|
|
@ -240,7 +273,7 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
}}
|
||||
>
|
||||
{copiedKey === "install" ? <CheckOutlined /> : <CopyOutlined />}
|
||||
{copiedKey === "install" ? "Copied" : "Copy"}
|
||||
{copiedKey === "install" ? t("common.copied") : t("common.copy")}
|
||||
</button>
|
||||
</div>
|
||||
<pre
|
||||
|
|
@ -258,9 +291,9 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
</div>
|
||||
|
||||
<p style={{ fontSize: 13, color: "#5f6368", lineHeight: 1.6, margin: 0 }}>
|
||||
Don't have the marketplace configured yet?{" "}
|
||||
{t("claudeCodePluginsPage.skillDetail.noMarketplace")}{" "}
|
||||
<span onClick={() => setActiveTab("setup")} style={{ color: "#1a73e8", cursor: "pointer" }}>
|
||||
See one-time setup →
|
||||
{t("claudeCodePluginsPage.skillDetail.seeSetup")}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -270,14 +303,17 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
{activeTab === "setup" && (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 8px 0" }}>
|
||||
One-time marketplace setup
|
||||
{t("claudeCodePluginsPage.skillDetail.setupHeading")}
|
||||
</h2>
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 24px 0", lineHeight: 1.6 }}>
|
||||
Add this to{" "}
|
||||
<code style={{ fontSize: 13, backgroundColor: "#f1f3f4", padding: "1px 6px", borderRadius: 4 }}>
|
||||
~/.claude/settings.json
|
||||
</code>{" "}
|
||||
to point Claude Code at your proxy:
|
||||
<Trans
|
||||
i18nKey="claudeCodePluginsPage.skillDetail.setupInstruction"
|
||||
components={{
|
||||
code: (
|
||||
<code style={{ fontSize: 13, backgroundColor: "#f1f3f4", padding: "1px 6px", borderRadius: 4 }} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -326,7 +362,7 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
}}
|
||||
>
|
||||
{copiedKey === "settings" ? <CheckOutlined /> : <CopyOutlined />}
|
||||
{copiedKey === "settings" ? "Copied" : "Copy"}
|
||||
{copiedKey === "settings" ? t("common.copied") : t("common.copy")}
|
||||
</button>
|
||||
</div>
|
||||
<pre
|
||||
|
|
|
|||
|
|
@ -1138,6 +1138,113 @@
|
|||
"deleteSuccess": "Skill \"{{name}}\" deleted successfully",
|
||||
"deleteFailed": "Failed to delete skill"
|
||||
},
|
||||
"claudeCodePluginsPage": {
|
||||
"makeSkillPublicForm": {
|
||||
"selectAtLeastOne": "Please select at least one skill",
|
||||
"hubUpdated_one": "Skill Hub updated — {{count}} skill published",
|
||||
"hubUpdated_other": "Skill Hub updated — {{count}} skills published",
|
||||
"updateFailed": "Failed to update skills. Please try again.",
|
||||
"selectSkillsTitle": "Select Skills to Publish",
|
||||
"selectAll": "Select All ({{count}})",
|
||||
"visibilityNote": "Selected skills will be visible to all users in the Skill Hub. Deselected skills will be unpublished.",
|
||||
"noSkillsRegistered": "No skills registered yet.",
|
||||
"publicBadge": "Public",
|
||||
"willBePublished_one": "<strong>{{count}}</strong> skill will be published",
|
||||
"willBePublished_other": "<strong>{{count}}</strong> skills will be published",
|
||||
"confirmTitle": "Confirm Publish to Skill Hub",
|
||||
"confirmNote": "<strong>Note:</strong> Published skills will be visible to all users in the Skill Hub tab. Skills not in the list below will be unpublished.",
|
||||
"skillsToBePublished": "Skills to be published:",
|
||||
"totalWillBePublished_one": "Total: <strong>{{count}}</strong> skill will be published",
|
||||
"totalWillBePublished_other": "Total: <strong>{{count}}</strong> skills will be published",
|
||||
"modalTitle": "Publish to Skill Hub",
|
||||
"stepSelectSkills": "Select Skills",
|
||||
"publishToHub": "Publish to Hub"
|
||||
},
|
||||
"addPluginForm": {
|
||||
"noAccessToken": "No access token available",
|
||||
"enterValidGithubUrl": "Please enter a valid GitHub URL",
|
||||
"invalidSkillName": "Skill name must be kebab-case (lowercase letters, numbers, and hyphens only)",
|
||||
"invalidVersion": "Version must be in semantic versioning format (e.g., 1.0.0)",
|
||||
"invalidEmail": "Invalid email format",
|
||||
"invalidHomepageUrl": "Invalid homepage URL format",
|
||||
"registerSuccess": "Skill registered successfully",
|
||||
"registerFailed": "Failed to register skill",
|
||||
"modalTitle": "Add New Skill",
|
||||
"githubUrlLabel": "GitHub URL",
|
||||
"githubUrlRequired": "Please enter a GitHub URL",
|
||||
"githubUrlTooltip": "Paste a GitHub URL — repo, folder, or file link. E.g. github.com/org/repo or github.com/org/repo/tree/main/my-skill",
|
||||
"detected": "Detected: {{label}}",
|
||||
"skillNameLabel": "Skill Name",
|
||||
"skillNameRequired": "Please enter skill name",
|
||||
"skillNamePattern": "Name must be kebab-case (lowercase, numbers, hyphens only)",
|
||||
"skillNameTooltip": "Unique identifier in kebab-case format (e.g., my-skill)",
|
||||
"domainLabel": "Domain (Optional)",
|
||||
"domainTooltip": "Top-level grouping in the Skill Hub (e.g., Productivity)",
|
||||
"domainPlaceholder": "Productivity",
|
||||
"namespaceLabel": "Namespace (Optional)",
|
||||
"namespaceTooltip": "Sub-grouping within domain (e.g., workflows)",
|
||||
"namespacePlaceholder": "workflows",
|
||||
"descriptionLabel": "Description (Optional)",
|
||||
"descriptionTooltip": "Brief description of what the skill does",
|
||||
"descriptionPlaceholder": "A skill that helps with...",
|
||||
"categoryLabel": "Category (Optional)",
|
||||
"categoryTooltip": "Select a category or enter a custom one",
|
||||
"categoryPlaceholder": "Select or type a category",
|
||||
"keywordsLabel": "Keywords (Optional)",
|
||||
"keywordsTooltip": "Comma-separated list of keywords for search",
|
||||
"keywordsPlaceholder": "search, web, api",
|
||||
"versionLabel": "Version (Optional)",
|
||||
"versionTooltip": "Semantic version (e.g., 1.0.0)",
|
||||
"authorNameLabel": "Author Name (Optional)",
|
||||
"authorNameTooltip": "Name of the skill author or organization",
|
||||
"authorNamePlaceholder": "Your Name or Organization",
|
||||
"authorEmailLabel": "Author Email (Optional)",
|
||||
"authorEmailRule": "Please enter a valid email",
|
||||
"authorEmailTooltip": "Contact email for the skill author",
|
||||
"adding": "Adding...",
|
||||
"addSkill": "Add Skill"
|
||||
},
|
||||
"pluginTable": {
|
||||
"copiedToClipboard": "Copied to clipboard!",
|
||||
"colSkillName": "Skill Name",
|
||||
"copyPluginId": "Copy Plugin ID",
|
||||
"colVersion": "Version",
|
||||
"versionNA": "N/A",
|
||||
"noDescription": "No description",
|
||||
"colCategory": "Category",
|
||||
"uncategorized": "Uncategorized",
|
||||
"colPublic": "Public",
|
||||
"deleteSkill": "Delete skill",
|
||||
"noSkillsFound": "No skills found. Add one to get started."
|
||||
},
|
||||
"skillDetail": {
|
||||
"backLink": "Skills",
|
||||
"tabOverview": "Overview",
|
||||
"tabUsage": "How to Use",
|
||||
"propCategory": "Category",
|
||||
"propDomain": "Domain",
|
||||
"propNamespace": "Namespace",
|
||||
"propVersion": "Version",
|
||||
"propAuthor": "Author",
|
||||
"propAdded": "Added",
|
||||
"skillDetailsHeading": "Skill Details",
|
||||
"skillDetailsSubtitle": "Metadata registered with this skill",
|
||||
"tableColProperty": "Property",
|
||||
"sidebarStatus": "Status",
|
||||
"statusPublic": "Public",
|
||||
"statusDraft": "Draft",
|
||||
"sidebarSource": "Source",
|
||||
"sidebarTags": "Tags",
|
||||
"sidebarSkillId": "Skill ID",
|
||||
"usingThisSkill": "Using this skill",
|
||||
"usageInstruction": "Once your proxy is set as a marketplace, enable this skill in Claude Code with one command:",
|
||||
"runInClaudeCode": "Run in Claude Code",
|
||||
"noMarketplace": "Don't have the marketplace configured yet?",
|
||||
"seeSetup": "See one-time setup →",
|
||||
"setupHeading": "One-time marketplace setup",
|
||||
"setupInstruction": "Add this to <code>~/.claude/settings.json</code> to point Claude Code at your proxy:"
|
||||
}
|
||||
},
|
||||
"cloudzeroExportModal": {
|
||||
"exportDataTitle": "Export Data",
|
||||
"exportDestination": "Export Destination",
|
||||
|
|
|
|||
|
|
@ -1138,6 +1138,113 @@
|
|||
"deleteSuccess": "技能「{{name}}」已成功删除",
|
||||
"deleteFailed": "删除技能失败"
|
||||
},
|
||||
"claudeCodePluginsPage": {
|
||||
"makeSkillPublicForm": {
|
||||
"selectAtLeastOne": "请至少选择一个技能",
|
||||
"hubUpdated_one": "技能中心已更新 — 已发布 {{count}} 个技能",
|
||||
"hubUpdated_other": "技能中心已更新 — 已发布 {{count}} 个技能",
|
||||
"updateFailed": "更新技能失败,请重试。",
|
||||
"selectSkillsTitle": "选择要发布的技能",
|
||||
"selectAll": "全选 ({{count}})",
|
||||
"visibilityNote": "选中的技能将在技能中心向所有用户展示,取消选中的技能将被取消发布。",
|
||||
"noSkillsRegistered": "暂无已注册的技能。",
|
||||
"publicBadge": "公开",
|
||||
"willBePublished_one": "<strong>{{count}}</strong> 个技能将被发布",
|
||||
"willBePublished_other": "<strong>{{count}}</strong> 个技能将被发布",
|
||||
"confirmTitle": "确认发布到技能中心",
|
||||
"confirmNote": "<strong>注意:</strong>已发布的技能将在技能中心标签页向所有用户展示,不在以下列表中的技能将被取消发布。",
|
||||
"skillsToBePublished": "将要发布的技能:",
|
||||
"totalWillBePublished_one": "共 <strong>{{count}}</strong> 个技能将被发布",
|
||||
"totalWillBePublished_other": "共 <strong>{{count}}</strong> 个技能将被发布",
|
||||
"modalTitle": "发布到技能中心",
|
||||
"stepSelectSkills": "选择技能",
|
||||
"publishToHub": "发布到中心"
|
||||
},
|
||||
"addPluginForm": {
|
||||
"noAccessToken": "No access token available",
|
||||
"enterValidGithubUrl": "请输入有效的 GitHub URL",
|
||||
"invalidSkillName": "技能名称必须为 kebab-case 格式(仅限小写字母、数字和连字符)",
|
||||
"invalidVersion": "版本号必须符合语义化版本格式(如 1.0.0)",
|
||||
"invalidEmail": "邮箱格式无效",
|
||||
"invalidHomepageUrl": "主页 URL 格式无效",
|
||||
"registerSuccess": "技能注册成功",
|
||||
"registerFailed": "技能注册失败",
|
||||
"modalTitle": "添加新技能",
|
||||
"githubUrlLabel": "GitHub URL",
|
||||
"githubUrlRequired": "请输入 GitHub URL",
|
||||
"githubUrlTooltip": "粘贴 GitHub URL——仓库、文件夹或文件链接,例如 github.com/org/repo 或 github.com/org/repo/tree/main/my-skill",
|
||||
"detected": "已识别:{{label}}",
|
||||
"skillNameLabel": "技能名称",
|
||||
"skillNameRequired": "请输入技能名称",
|
||||
"skillNamePattern": "名称必须为 kebab-case 格式(仅限小写字母、数字和连字符)",
|
||||
"skillNameTooltip": "kebab-case 格式的唯一标识符(如 my-skill)",
|
||||
"domainLabel": "域(可选)",
|
||||
"domainTooltip": "技能中心的顶级分组(如 Productivity)",
|
||||
"domainPlaceholder": "Productivity",
|
||||
"namespaceLabel": "命名空间(可选)",
|
||||
"namespaceTooltip": "域内的子分组(如 workflows)",
|
||||
"namespacePlaceholder": "workflows",
|
||||
"descriptionLabel": "描述(可选)",
|
||||
"descriptionTooltip": "技能功能的简要说明",
|
||||
"descriptionPlaceholder": "一个可以帮助……的技能",
|
||||
"categoryLabel": "分类(可选)",
|
||||
"categoryTooltip": "选择分类或输入自定义分类",
|
||||
"categoryPlaceholder": "选择或输入分类",
|
||||
"keywordsLabel": "关键词(可选)",
|
||||
"keywordsTooltip": "用于搜索的逗号分隔关键词列表",
|
||||
"keywordsPlaceholder": "search, web, api",
|
||||
"versionLabel": "版本(可选)",
|
||||
"versionTooltip": "语义化版本号(如 1.0.0)",
|
||||
"authorNameLabel": "作者名称(可选)",
|
||||
"authorNameTooltip": "技能作者或组织的名称",
|
||||
"authorNamePlaceholder": "您的姓名或组织",
|
||||
"authorEmailLabel": "作者邮箱(可选)",
|
||||
"authorEmailRule": "请输入有效的邮箱地址",
|
||||
"authorEmailTooltip": "技能作者的联系邮箱",
|
||||
"adding": "添加中...",
|
||||
"addSkill": "添加技能"
|
||||
},
|
||||
"pluginTable": {
|
||||
"copiedToClipboard": "已复制到剪贴板!",
|
||||
"colSkillName": "技能名称",
|
||||
"copyPluginId": "复制插件 ID",
|
||||
"colVersion": "版本",
|
||||
"versionNA": "N/A",
|
||||
"noDescription": "暂无描述",
|
||||
"colCategory": "分类",
|
||||
"uncategorized": "未分类",
|
||||
"colPublic": "是否公开",
|
||||
"deleteSkill": "删除技能",
|
||||
"noSkillsFound": "未找到技能,点击添加以开始使用。"
|
||||
},
|
||||
"skillDetail": {
|
||||
"backLink": "技能",
|
||||
"tabOverview": "概览",
|
||||
"tabUsage": "使用方法",
|
||||
"propCategory": "分类",
|
||||
"propDomain": "域",
|
||||
"propNamespace": "命名空间",
|
||||
"propVersion": "版本",
|
||||
"propAuthor": "作者",
|
||||
"propAdded": "添加时间",
|
||||
"skillDetailsHeading": "技能详情",
|
||||
"skillDetailsSubtitle": "该技能注册的元数据",
|
||||
"tableColProperty": "属性",
|
||||
"sidebarStatus": "状态",
|
||||
"statusPublic": "公开",
|
||||
"statusDraft": "草稿",
|
||||
"sidebarSource": "来源",
|
||||
"sidebarTags": "标签",
|
||||
"sidebarSkillId": "技能 ID",
|
||||
"usingThisSkill": "使用此技能",
|
||||
"usageInstruction": "将代理设置为 marketplace 后,通过以下命令在 Claude Code 中启用此技能:",
|
||||
"runInClaudeCode": "在 Claude Code 中运行",
|
||||
"noMarketplace": "还未配置 marketplace?",
|
||||
"seeSetup": "查看一次性配置 →",
|
||||
"setupHeading": "一次性 marketplace 配置",
|
||||
"setupInstruction": "将以下内容添加到 <code>~/.claude/settings.json</code>,将 Claude Code 指向您的代理:"
|
||||
}
|
||||
},
|
||||
"cloudzeroExportModal": {
|
||||
"exportDataTitle": "导出数据",
|
||||
"exportDestination": "导出目标",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue