From 7ddaa3a00512c38dfa7a2413fc47920f700ba173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B9=E9=9B=AA?= <2b@yorha.xyz> Date: Thu, 11 Jun 2026 05:28:18 +0900 Subject: [PATCH] feat(ui): i18n for claude code plugins --- .../MakeSkillPublicForm.tsx | 55 +++++---- .../claude_code_plugins/add_plugin_form.tsx | 110 ++++++++++++------ .../claude_code_plugins/plugin_table.tsx | 34 +++--- .../claude_code_plugins/skill_detail.tsx | 96 ++++++++++----- ui/litellm-dashboard/src/locales/en.json | 107 +++++++++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 107 +++++++++++++++++ 6 files changed, 403 insertions(+), 106 deletions(-) diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/MakeSkillPublicForm.tsx b/ui/litellm-dashboard/src/components/claude_code_plugins/MakeSkillPublicForm.tsx index e2467b51461..2035f83827c 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/MakeSkillPublicForm.tsx +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/MakeSkillPublicForm.tsx @@ -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 = ({ skillsList, onSuccess, }) => { + const { t } = useTranslation(); const [currentStep, setCurrentStep] = useState(0); const [selectedSkills, setSelectedSkills] = useState>(new Set()); const [loading, setLoading] = useState(false); @@ -36,7 +38,7 @@ const MakeSkillPublicForm: React.FC = ({ 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 = ({ 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 = ({ }), ); - 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 = ({ const renderStep1 = () => (
- Select Skills to Publish + {t("claudeCodePluginsPage.makeSkillPublicForm.selectSkillsTitle")} handleSelectAll(e.target.checked)} disabled={skillsList.length === 0} > - Select All ({skillsList.length}) + {t("claudeCodePluginsPage.makeSkillPublicForm.selectAll", { count: skillsList.length })}
- - Selected skills will be visible to all users in the Skill Hub. Deselected skills will be unpublished. - + {t("claudeCodePluginsPage.makeSkillPublicForm.visibilityNote")}
{skillsList.length === 0 ? (
- No skills registered yet. + {t("claudeCodePluginsPage.makeSkillPublicForm.noSkillsRegistered")}
) : ( skillsList.map((skill) => ( @@ -139,7 +141,7 @@ const MakeSkillPublicForm: React.FC = ({ {skill.name} {skill.enabled && ( - Public + {t("claudeCodePluginsPage.makeSkillPublicForm.publicBadge")} )}
@@ -161,7 +163,11 @@ const MakeSkillPublicForm: React.FC = ({ {selectedSkills.size > 0 && (
- {selectedSkills.size} skill{selectedSkills.size !== 1 ? "s" : ""} will be published + }} + />
)} @@ -170,17 +176,16 @@ const MakeSkillPublicForm: React.FC = ({ const renderStep2 = () => (
- Confirm Publish to Skill Hub + {t("claudeCodePluginsPage.makeSkillPublicForm.confirmTitle")}
- Note: Published skills will be visible to all users in the Skill Hub tab. Skills not in the - list below will be unpublished. + }} />
- Skills to be published: + {t("claudeCodePluginsPage.makeSkillPublicForm.skillsToBePublished")}
{Array.from(selectedSkills).map((name) => { @@ -202,7 +207,11 @@ const MakeSkillPublicForm: React.FC = ({
- Total: {selectedSkills.size} skill{selectedSkills.size !== 1 ? "s" : ""} will be published + }} + />
@@ -210,7 +219,7 @@ const MakeSkillPublicForm: React.FC = ({ return ( = ({ >
- - + + {currentStep === 0 ? renderStep1() : renderStep2()}
{currentStep === 0 && ( )} {currentStep === 1 && ( )}
diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx b/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx index f90261ae3a3..9bb3c036526 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx @@ -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 = ({ visible, onClose, accessToken, onSuccess }) => { + const { t } = useTranslation(); const [form] = Form.useForm(); const [isSubmitting, setIsSubmitting] = useState(false); const [urlPreview, setUrlPreview] = useState(null); @@ -120,32 +122,32 @@ const AddPluginForm: React.FC = ({ 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 = ({ 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 = ({ visible, onClose, accessT }; return ( - + {/* Smart URL Input */} = ({ visible, onClose, accessT {/* Parsed preview */} {urlPreview && (
- Detected: {urlPreview.label} + {t("claudeCodePluginsPage.addPluginForm.detected", { label: urlPreview.label })}
)} {/* Skill Name */} @@ -232,32 +241,45 @@ const AddPluginForm: React.FC = ({ visible, onClose, accessT {/* Domain and Namespace — side by side */}
- + - +
{/* Description */} - -