diff --git a/docs/assets/screenshots/windows-zh-localization.png b/docs/assets/screenshots/windows-zh-localization.png new file mode 100644 index 000000000..4a16862e2 Binary files /dev/null and b/docs/assets/screenshots/windows-zh-localization.png differ diff --git a/windows/tauri/src/extensions/hooks/use-extension-install-prompt.ts b/windows/tauri/src/extensions/hooks/use-extension-install-prompt.ts index 12e98af72..51e73dc74 100644 --- a/windows/tauri/src/extensions/hooks/use-extension-install-prompt.ts +++ b/windows/tauri/src/extensions/hooks/use-extension-install-prompt.ts @@ -1,6 +1,7 @@ import { useEffect, useRef } from "react"; import { useBufferStore } from "@/features/editor/stores/buffer.store"; import { useToast } from "@/features/layout/contexts/toast-context"; +import { useTranslation } from "@/i18n/locale-provider"; import { useExtensionStore } from "../registry/extension-store"; interface ExtensionInstallNeededEvent { @@ -13,6 +14,7 @@ interface ExtensionInstallNeededEvent { const activePrompts = new Map(); export const useExtensionInstallPrompt = () => { + const { t } = useTranslation(); const { showToast, dismissToast, updateToast, hasToast } = useToast(); const { installExtension } = useExtensionStore.use.actions(); const dismissedExtensions = useRef>(new Set()); @@ -40,16 +42,16 @@ export const useExtensionInstallPrompt = () => { } const toastId = showToast({ - message: `${extensionName} extension not installed. Install it to enable language support?`, + message: t("extensions.installPrompt", { name: extensionName }), type: "info", duration: 0, // Don't auto-dismiss action: { - label: "Install", + label: t("extensions.install"), onClick: async () => { try { // Update toast to show installing status updateToast(toastId, { - message: `Installing ${extensionName}...`, + message: t("extensions.installing", { name: extensionName }), action: undefined, // Remove action button while installing }); @@ -58,7 +60,7 @@ export const useExtensionInstallPrompt = () => { // Show success updateToast(toastId, { - message: `${extensionName} installed successfully!`, + message: t("extensions.installSuccess", { name: extensionName }), type: "success", }); @@ -81,14 +83,18 @@ export const useExtensionInstallPrompt = () => { }, 3000); } catch (error) { // Show error - const errorMessage = error instanceof Error ? error.message : "Installation failed"; + const errorMessage = + error instanceof Error ? error.message : t("extensions.installFailedGeneric"); console.error(`Failed to install ${extensionName}:`, error); updateToast(toastId, { - message: `Failed to install ${extensionName}: ${errorMessage}`, + message: t("extensions.installFailedWithMessage", { + name: extensionName, + message: errorMessage, + }), type: "error", action: { - label: "Retry", + label: t("ui.retry"), onClick: () => { // Retry installation dismissToast(toastId); @@ -131,5 +137,5 @@ export const useExtensionInstallPrompt = () => { window.removeEventListener("extension-install-needed", handleInstallNeeded); window.removeEventListener("toast-dismissed", handleToastDismiss); }; - }, [showToast, dismissToast, updateToast, installExtension, hasToast]); + }, [t, showToast, dismissToast, updateToast, installExtension, hasToast]); }; diff --git a/windows/tauri/src/extensions/icon-themes/components/themed-file-icon.tsx b/windows/tauri/src/extensions/icon-themes/components/themed-file-icon.tsx index 669f6fe35..f5898e1f2 100644 --- a/windows/tauri/src/extensions/icon-themes/components/themed-file-icon.tsx +++ b/windows/tauri/src/extensions/icon-themes/components/themed-file-icon.tsx @@ -2,6 +2,7 @@ import DOMPurify from "dompurify"; import { cloneElement, isValidElement, useMemo, useSyncExternalStore } from "react"; import { themeRegistry } from "@/extensions/themes/theme-registry"; import { getDefaultSetting, useSettingsStore } from "@/features/settings/stores/settings.store"; +import { useTranslation } from "@/i18n/locale-provider"; import { cn } from "@/utils/cn"; import { iconThemeRegistry } from "../icon-theme-registry"; @@ -27,6 +28,7 @@ export function ThemedFileIcon({ isSymlink = false, className = "text-subtle-foreground", }: ThemedFileIconProps) { + const { t } = useTranslation(); const iconThemeId = useSettingsStore((state) => state.settings.iconTheme); useSyncExternalStore( (callback) => iconThemeRegistry.onRegistryChange(callback), @@ -95,9 +97,9 @@ export function ThemedFileIcon({ viewBox="0 0 16 16" className="-bottom-0.5 -right-0.5 themed-file-icon-badge absolute text-primary" role="img" - aria-label="Symlink" + aria-label={t("ui.symlink")} > - Symlink + {t("ui.symlink")} {dialog.title} } + render={ + diff --git a/windows/tauri/src/extensions/ui/components/extensions-sidebar.tsx b/windows/tauri/src/extensions/ui/components/extensions-sidebar.tsx index db87c99ee..32f4a2e3a 100644 --- a/windows/tauri/src/extensions/ui/components/extensions-sidebar.tsx +++ b/windows/tauri/src/extensions/ui/components/extensions-sidebar.tsx @@ -55,6 +55,7 @@ import type { AgentConfig } from "@/features/ai/types/acp.types"; import type { AIChatSkill, MarketplaceSkill } from "@/features/ai/types/skills.types"; import { useToast } from "@/features/layout/contexts/toast-context"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; +import { useTranslation } from "@/i18n/locale-provider"; import { Alert, AlertDescription } from "@/ui/alert"; import Badge from "@/ui/badge"; import { Button } from "@/ui/button"; @@ -99,6 +100,8 @@ interface UnifiedExtension { isActive?: boolean; } +type Translator = (key: string, values?: Record) => string; + interface AppearanceOption { id: string; name: string; @@ -106,15 +109,15 @@ interface AppearanceOption { } const FILTER_TABS = [ - { id: "all", label: "All" }, - { id: "language", label: "Languages", icon: TextT }, - { id: "theme", label: "Themes", icon: PaintBrush }, - { id: "icon-theme", label: "Icon Themes", icon: Package }, - { id: "database", label: "Databases", icon: Database }, - { id: "ai", label: "AI", icon: Sparkles }, - { id: "integration", label: "Integrations", icon: PlugsConnected }, - { id: "skill", label: "Skills", icon: Brain }, - { id: "agent", label: "Agents", icon: Robot }, + { id: "all", labelKey: "extensions.all" }, + { id: "language", labelKey: "extensions.languages", icon: TextT }, + { id: "theme", labelKey: "extensions.themes", icon: PaintBrush }, + { id: "icon-theme", labelKey: "extensions.iconThemes", icon: Package }, + { id: "database", labelKey: "extensions.databases", icon: Database }, + { id: "ai", labelKey: "extensions.ai", icon: Sparkles }, + { id: "integration", labelKey: "extensions.integrations", icon: PlugsConnected }, + { id: "skill", labelKey: "extensions.skills", icon: Brain }, + { id: "agent", labelKey: "extensions.agents", icon: Robot }, ] as const; type ExtensionTabId = (typeof FILTER_TABS)[number]["id"]; @@ -212,48 +215,33 @@ function getErrorMessage(error: unknown, fallback = "Unknown error"): string { return String(error || fallback); } -const getCategoryLabel = (category: UnifiedExtension["category"]) => { - switch (category) { - case "language": - return "Language"; - case "theme": - return "Theme"; - case "icon-theme": - return "Icon Theme"; - case "database": - return "Database"; - case "ai": - return "AI"; - case "integration": - return "Integration"; - case "skill": - return "Skill"; - case "agent": - return "Agent"; - default: - return category; - } +const getCategoryLabel = (category: UnifiedExtension["category"], t: Translator) => { + return t(`extensions.category.${category}`); }; -function getPrimaryActionLabel(extension: UnifiedExtension): string { +function getPrimaryActionLabel(extension: UnifiedExtension, t: Translator): string { if (isAppearanceExtension(extension)) { if (extension.isInstalled) { - if (!extension.isEnabled) return "Activate"; - return extension.isActive ? "Current" : "Use"; + if (!extension.isEnabled) return t("extensions.activate"); + return extension.isActive ? t("extensions.current") : t("extensions.use"); } - return "Install"; + return t("extensions.install"); } if (extension.category === "skill") { - return extension.isInstalled ? "Remove" : "Add"; + return extension.isInstalled ? t("extensions.remove") : t("extensions.add"); } if (extension.category === "agent") { - return extension.isInstalled ? "Uninstall" : "Install"; + return extension.isInstalled ? t("extensions.uninstall") : t("extensions.install"); } - return extension.isInstalled ? (extension.isEnabled ? "Deactivate" : "Activate") : "Install"; + return extension.isInstalled + ? extension.isEnabled + ? t("extensions.deactivate") + : t("extensions.activate") + : t("extensions.install"); } function isAppearanceExtension(extension: UnifiedExtension): boolean { @@ -456,12 +444,13 @@ const ExtensionRow = ({ hasUpdate?: boolean; hasRuntimeIssue?: boolean; }) => { - const primaryActionLabel = getPrimaryActionLabel(extension); + const { t } = useTranslation(); + const primaryActionLabel = getPrimaryActionLabel(extension, t); const isUnavailableAgent = extension.category === "agent" && !extension.isInstalled && extension.canInstall === false; const actionContent = isInstalling ? ( - + ) : hasRuntimeIssue && onUpdate ? ( ) : isUnavailableAgent ? ( - ) : extension.isInstalled ? ( @@ -547,6 +536,7 @@ const ExtensionRow = ({ }; export const ExtensionsSidebar = () => { + const { t } = useTranslation(); const settings = useSettingsStore( useShallow((state) => ({ aiSkills: state.settings.aiSkills, @@ -614,7 +604,9 @@ export const ExtensionsSidebar = () => { id: `agent:${contribution.id}`, name: agent?.name ?? contribution.name, description: - agent?.description ?? contribution.description ?? "ACP-compatible coding agent", + agent?.description ?? + contribution.description ?? + t("extensions.agentFallbackDescription"), category: "agent", isInstalled: agent?.installed ?? false, isEnabled: agent?.installed ?? false, @@ -837,7 +829,8 @@ export const ExtensionsSidebar = () => { allExtensions.push({ id: theme.id, name: theme.name, - description: theme.description || `${theme.category} theme`, + description: + theme.description || t("extensions.themeFallbackDescription", { category: theme.category }), category: "theme", isInstalled: true, isEnabled: true, @@ -863,7 +856,9 @@ export const ExtensionsSidebar = () => { allExtensions.push({ id: iconTheme.id, name: iconTheme.name, - description: iconTheme.description || `${iconTheme.name} icon theme`, + description: + iconTheme.description || + t("extensions.iconThemeFallbackDescription", { name: iconTheme.name }), category: "icon-theme", isInstalled: true, isEnabled: true, @@ -893,12 +888,15 @@ export const ExtensionsSidebar = () => { allExtensions.push({ id: skill.id, name: skill.title, - description: skill.description || preview || "Reusable AI chat instructions", + description: skill.description || preview || t("extensions.reusableSkillDescription"), category: "skill", isInstalled: true, isEnabled: true, - version: skill.version || (skill.source === "marketplace" ? undefined : "Local"), - publisher: skill.author || (skill.source === "marketplace" ? "Marketplace" : "You"), + version: + skill.version || (skill.source === "marketplace" ? undefined : t("extensions.local")), + publisher: + skill.author || + (skill.source === "marketplace" ? t("extensions.marketplace") : t("extensions.you")), isMarketplace: skill.source === "marketplace", icon: getCatalogIconUrl(skill.title, skill.author, "codex"), skill, @@ -941,12 +939,12 @@ export const ExtensionsSidebar = () => { allExtensions.push({ id: `agent:${agent.id}`, name: agent.name, - description: agent.description ?? "ACP-compatible coding agent", + description: agent.description ?? t("extensions.agentFallbackDescription"), category: "agent", isInstalled: agent.installed, isEnabled: agent.installed, extensions: [agent.binaryName], - publisher: "Marketplace", + publisher: t("extensions.marketplace"), isMarketplace: true, agentId: agent.id, icon: resolveManifestIcon(agent.icon ?? undefined, agent.id, agent.name, agent.binaryName), @@ -963,6 +961,7 @@ export const ExtensionsSidebar = () => { settings.aiSkills, settings.iconTheme, settings.theme, + t, ]); useEffect(() => { @@ -997,15 +996,18 @@ export const ExtensionsSidebar = () => { ); showToast({ message: updatedSkill.localOverride - ? `${extension.name} updated, local override kept` - : `${extension.name} updated successfully`, + ? t("extensions.updatedWithLocalOverride", { name: extension.name }) + : t("extensions.updatedSuccessfully", { name: extension.name }), type: "success", duration: 3000, }); } catch (error) { console.error(`Failed to update ${extension.name}:`, error); showToast({ - message: `Failed to update ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.updateFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1016,14 +1018,17 @@ export const ExtensionsSidebar = () => { try { await updateExtension(extension.id); showToast({ - message: `${extension.name} updated successfully`, + message: t("extensions.updatedSuccessfully", { name: extension.name }), type: "success", duration: 3000, }); } catch (error) { console.error(`Failed to update ${extension.name}:`, error); showToast({ - message: `Failed to update ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.updateFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1041,14 +1046,17 @@ export const ExtensionsSidebar = () => { ), ); showToast({ - message: `${extension.name} reset to marketplace version`, + message: t("extensions.resetToMarketplaceSuccess", { name: extension.name }), type: "success", duration: 3000, }); } catch (error) { console.error(`Failed to reset ${extension.name}:`, error); showToast({ - message: `Failed to reset ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.resetFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1069,14 +1077,19 @@ export const ExtensionsSidebar = () => { } await updateSetting(settingKey, nextSelectionId); showToast({ - message: `${getAppearanceOptionLabel(extension, nextSelectionId)} selected`, + message: t("extensions.selected", { + name: getAppearanceOptionLabel(extension, nextSelectionId), + }), type: "success", duration: 2500, }); } catch (error) { console.error(`Failed to use ${extension.name}:`, error); showToast({ - message: `Failed to use ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.useFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1092,14 +1105,17 @@ export const ExtensionsSidebar = () => { try { await enableExtension(extension.id); showToast({ - message: `${extension.name} activated`, + message: t("extensions.activated", { name: extension.name }), type: "success", duration: 2500, }); } catch (error) { console.error(`Failed to activate ${extension.name}:`, error); showToast({ - message: `Failed to activate ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.activateFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1115,14 +1131,17 @@ export const ExtensionsSidebar = () => { try { await disableExtension(extension.id); showToast({ - message: `${extension.name} deactivated`, + message: t("extensions.deactivated", { name: extension.name }), type: "success", duration: 2500, }); } catch (error) { console.error(`Failed to deactivate ${extension.name}:`, error); showToast({ - message: `Failed to deactivate ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.deactivateFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1134,7 +1153,7 @@ export const ExtensionsSidebar = () => { if (extension.category === "agent") { if (!extension.isInstalled && extension.canInstall === false) { showToast({ - message: `${extension.name} cannot be installed automatically`, + message: t("extensions.cannotInstallAutomatically", { name: extension.name }), type: "error", duration: 5000, }); @@ -1159,11 +1178,11 @@ export const ExtensionsSidebar = () => { showToast({ message: extension.isInstalled ? managedUninstallLeftGlobalBinary - ? `${extension.name} managed install removed` - : `${extension.name} uninstalled successfully` - : `${extension.name} installed successfully`, + ? t("extensions.managedInstallRemoved", { name: extension.name }) + : t("extensions.uninstalledSuccessfully", { name: extension.name }) + : t("extensions.installedSuccessfully", { name: extension.name }), description: managedUninstallLeftGlobalBinary - ? "A global installation is still detected on your PATH." + ? t("extensions.globalInstallStillDetected") : undefined, type: managedUninstallLeftGlobalBinary ? "info" : "success", duration: managedUninstallLeftGlobalBinary ? 5000 : 3000, @@ -1174,9 +1193,14 @@ export const ExtensionsSidebar = () => { error, ); showToast({ - message: `Failed to ${extension.isInstalled ? "uninstall" : "install"} ${extension.name}: ${getErrorMessage( - error, - )}`, + message: extension.isInstalled + ? t("extensions.uninstallFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }) + : t("extensions.installFailed", { + message: `${extension.name}: ${getErrorMessage(error, t("extensions.unknownError"))}`, + }), type: "error", duration: 5000, }); @@ -1201,7 +1225,7 @@ export const ExtensionsSidebar = () => { ), ); showToast({ - message: `${extension.name} removed successfully`, + message: t("extensions.removedSuccessfully", { name: extension.name }), type: "success", duration: 3000, }); @@ -1217,14 +1241,17 @@ export const ExtensionsSidebar = () => { ...settings.aiSkills, ]); showToast({ - message: `${extension.name} added successfully`, + message: t("extensions.addedSuccessfully", { name: extension.name }), type: "success", duration: 3000, }); } catch (error) { console.error(`Failed to update ${extension.name}:`, error); showToast({ - message: `Failed to update ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.updateActionFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1254,7 +1281,9 @@ export const ExtensionsSidebar = () => { await enableExtension(extension.id); } showToast({ - message: `${extension.name} ${extension.isEnabled ? "deactivated" : "activated"}`, + message: extension.isEnabled + ? t("extensions.deactivated", { name: extension.name }) + : t("extensions.activated", { name: extension.name }), type: "success", duration: 2500, }); @@ -1264,7 +1293,15 @@ export const ExtensionsSidebar = () => { error, ); showToast({ - message: `Failed to ${extension.isEnabled ? "deactivate" : "activate"} ${extension.name}: ${getErrorMessage(error)}`, + message: extension.isEnabled + ? t("extensions.deactivateFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }) + : t("extensions.activateFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1277,14 +1314,16 @@ export const ExtensionsSidebar = () => { try { await installExtension(extension.id); showToast({ - message: `${extension.name} installed successfully`, + message: t("extensions.installedSuccessfully", { name: extension.name }), type: "success", duration: 3000, }); } catch (error) { console.error(`Failed to install ${extension.name}:`, error); showToast({ - message: `Failed to install ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.installFailed", { + message: `${extension.name}: ${getErrorMessage(error, t("extensions.unknownError"))}`, + }), type: "error", duration: 5000, }); @@ -1308,14 +1347,17 @@ export const ExtensionsSidebar = () => { try { await uninstallExtension(extension.id); showToast({ - message: `${extension.name} uninstalled successfully`, + message: t("extensions.uninstalledSuccessfully", { name: extension.name }), type: "success", duration: 3000, }); } catch (error) { console.error(`Failed to uninstall ${extension.name}:`, error); showToast({ - message: `Failed to uninstall ${extension.name}: ${getErrorMessage(error)}`, + message: t("extensions.uninstallFailed", { + name: extension.name, + message: getErrorMessage(error, t("extensions.unknownError")), + }), type: "error", duration: 5000, }); @@ -1404,12 +1446,12 @@ export const ExtensionsSidebar = () => { const isUnavailableAgent = extension.category === "agent" && !extension.isInstalled && extension.canInstall === false; const isAppearance = isAppearanceExtension(extension); - const primaryActionLabel = getPrimaryActionLabel(extension); + const primaryActionLabel = getPrimaryActionLabel(extension, t); if (extension.isBundled) { items.push({ id: "built-in", - label: "Built-in", + label: t("extensions.builtIn"), icon: , disabled: true, onClick: () => {}, @@ -1422,7 +1464,7 @@ export const ExtensionsSidebar = () => { if (!extension.isEnabled) { items.push({ id: "activate", - label: "Activate", + label: t("extensions.activate"), icon: , disabled: isInstalling, onClick: () => { @@ -1432,7 +1474,7 @@ export const ExtensionsSidebar = () => { } else { items.push({ id: "deactivate", - label: "Deactivate", + label: t("extensions.deactivate"), icon: , disabled: isInstalling, onClick: () => { @@ -1458,7 +1500,9 @@ export const ExtensionsSidebar = () => { const isCurrent = currentSelection === option.id; items.push({ id: `use-${option.id}`, - label: isCurrent ? `Current: ${option.name}` : `Use ${option.name}`, + label: isCurrent + ? t("extensions.currentName", { name: option.name }) + : t("extensions.useName", { name: option.name }), icon: ( ), @@ -1471,7 +1515,7 @@ export const ExtensionsSidebar = () => { } else if (extension.isEnabled) { items.push({ id: extension.isActive ? "active" : "use", - label: extension.isActive ? "Current" : "Use", + label: extension.isActive ? t("extensions.current") : t("extensions.use"), icon: , disabled: extension.isActive || isInstalling, onClick: () => { @@ -1482,7 +1526,7 @@ export const ExtensionsSidebar = () => { } else { items.push({ id: extension.isEnabled ? "deactivate" : "activate", - label: extension.isEnabled ? "Deactivate" : "Activate", + label: extension.isEnabled ? t("extensions.deactivate") : t("extensions.activate"), icon: extension.isEnabled ? ( ) : ( @@ -1499,7 +1543,7 @@ export const ExtensionsSidebar = () => { if ((hasUpdate || hasRuntimeIssue) && extension.isInstalled) { items.push({ id: "update", - label: hasRuntimeIssue ? "Reinstall" : "Update", + label: hasRuntimeIssue ? t("extensions.reinstall") : t("extensions.update"), icon: , disabled: isInstalling, onClick: () => { @@ -1511,7 +1555,7 @@ export const ExtensionsSidebar = () => { if (hasLocalOverride) { items.push({ id: "reset", - label: "Reset to Marketplace Version", + label: t("extensions.resetToMarketplaceVersion"), icon: , disabled: isInstalling, onClick: () => { @@ -1548,7 +1592,7 @@ export const ExtensionsSidebar = () => { } else if (extension.isMarketplace) { items.push({ id: "uninstall", - label: "Uninstall", + label: t("extensions.uninstall"), icon: , disabled: isInstalling, className: "text-destructive hover:text-destructive", @@ -1559,7 +1603,7 @@ export const ExtensionsSidebar = () => { } return items; - }, [extensionContextMenu.data, extensionsWithUpdates, installingAgentIds, availableExtensions]); + }, [availableExtensions, extensionContextMenu.data, extensionsWithUpdates, installingAgentIds, t]); return (
@@ -1568,17 +1612,19 @@ export const ExtensionsSidebar = () => {
-

Extensions

+

+ {t("extensions.title")} +

- {extensions.length} available + {t("extensions.availableCount", { count: extensions.length })} · - {installedCount} installed + {t("extensions.installedCount", { count: installedCount })} {updateCount > 0 ? ( <> · - {updateCount} update{updateCount === 1 ? "" : "s"} + {t("extensions.updatesCount", { count: updateCount })} ) : null} @@ -1592,7 +1638,7 @@ export const ExtensionsSidebar = () => { value={searchQuery} onChange={setSearchQuery} leftIcon={Search} - placeholder="Search extensions" + placeholder={t("extensions.searchPlaceholder")} size="md" containerClassName="min-w-0 flex-1 sm:w-80 sm:flex-none" className="h-9 bg-surface/45" @@ -1600,7 +1646,7 @@ export const ExtensionsSidebar = () => { {settings.extensionsActiveTab === "skill" ? ( ) : null}
@@ -1626,7 +1672,7 @@ export const ExtensionsSidebar = () => { onClick={() => void updateSetting("extensionsActiveTab", tab.id as ExtensionTabId)} > {Icon ? : null} - {tab.label} + {t(tab.labelKey)} { {settings.extensionsActiveTab === "skill" && isLoadingSkills ? (
- +
) : null} {settings.extensionsActiveTab === "agent" && isLoadingAgents ? (
- +
) : null} {filteredExtensions.length === 0 ? ( - + ) : (
{filteredExtensions.map((extension) => { @@ -1697,7 +1743,9 @@ export const ExtensionsSidebar = () => {
{selectedExtension.publisher ? ( - By {selectedExtension.publisher} + + {t("extensions.byPublisher", { publisher: selectedExtension.publisher })} + ) : null} {selectedExtension.version ? v{selectedExtension.version} : null}
@@ -1706,31 +1754,31 @@ export const ExtensionsSidebar = () => {
- {getCategoryLabel(selectedExtension.category)} + {getCategoryLabel(selectedExtension.category, t)} {selectedExtension.isInstalled ? ( - Installed + {t("extensions.installed")} ) : null} {selectedExtension.isInstalled && !selectedExtension.isEnabled ? ( - Disabled + {t("extensions.disabled")} ) : null} {hasExtensionUpdate(selectedExtension) ? ( - Update + {t("extensions.update")} ) : null} {selectedExtension.isActive ? ( - Active + {t("extensions.active")} ) : null} {selectedExtension.isBundled ? ( - Built-in + {t("extensions.builtIn")} ) : null}
@@ -1751,7 +1799,9 @@ export const ExtensionsSidebar = () => { selectedExtension.appearanceOptions?.length ? (
- {selectedExtension.category === "theme" ? "Themes" : "Icon themes"} + {selectedExtension.category === "theme" + ? t("extensions.themes") + : t("extensions.iconThemes")}
{selectedExtension.appearanceOptions.map((option) => { @@ -1785,10 +1835,10 @@ export const ExtensionsSidebar = () => { > {isCurrent - ? "Current" + ? t("extensions.current") : selectedExtension.isEnabled - ? "Use" - : "Activate and use"} + ? t("extensions.use") + : t("extensions.activateAndUse")}
); @@ -1842,7 +1892,7 @@ export const ExtensionsSidebar = () => { ) : ( )} - {getPrimaryActionLabel(selectedExtension)} + {getPrimaryActionLabel(selectedExtension, t)} ) : null} {selectedExtension.isMarketplace && @@ -1856,7 +1906,7 @@ export const ExtensionsSidebar = () => { disabled={isExtensionInstalling(selectedExtension)} > - Uninstall + {t("extensions.uninstall")} ) : null} {hasExtensionUpdate(selectedExtension) && selectedExtension.isInstalled ? ( @@ -1866,7 +1916,7 @@ export const ExtensionsSidebar = () => { disabled={isExtensionInstalling(selectedExtension)} > - Update + {t("extensions.update")} ) : null} {canDeactivateAppearanceExtension(selectedExtension) ? ( @@ -1876,7 +1926,7 @@ export const ExtensionsSidebar = () => { onClick={() => void handleDeactivateExtension(selectedExtension)} > - Deactivate + {t("extensions.deactivate")} ) : null} {selectedExtension.skill && hasSkillLocalOverride(selectedExtension.skill) ? ( @@ -1885,19 +1935,21 @@ export const ExtensionsSidebar = () => { onClick={() => void handleResetSkillOverride(selectedExtension)} > - Reset + {t("settings.keyboard.reset")} ) : null}
-
Contributions
+
+ {t("extensions.contributions")} +
{(selectedExtension.contributionSummary?.length ? selectedExtension.contributionSummary : selectedExtension.extensions ? selectedExtension.extensions - : [getCategoryLabel(selectedExtension.category)] + : [getCategoryLabel(selectedExtension.category, t)] ).map((item) => ( {item} @@ -1907,7 +1959,7 @@ export const ExtensionsSidebar = () => {
) : ( - + )}
diff --git a/windows/tauri/src/extensions/ui/components/external-extension-view.tsx b/windows/tauri/src/extensions/ui/components/external-extension-view.tsx index 48d20c3ad..d69189b0c 100644 --- a/windows/tauri/src/extensions/ui/components/external-extension-view.tsx +++ b/windows/tauri/src/extensions/ui/components/external-extension-view.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useState } from "react"; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "@/ui/empty"; +import { useTranslation } from "@/i18n/locale-provider"; import { Spinner } from "@/ui/spinner"; import { uiExtensionHost } from "../services/ui-extension-host"; import { useUIExtensionStore } from "../stores/ui-extension-store"; @@ -12,6 +13,7 @@ interface ExternalExtensionViewProps { } export function ExternalExtensionView({ extensionId, viewId }: ExternalExtensionViewProps) { + const { t } = useTranslation(); const revision = useUIExtensionStore((state) => state.viewRevisions.get(viewId) ?? 0); const [node, setNode] = useState(null); const [error, setError] = useState(null); @@ -43,14 +45,14 @@ export function ExternalExtensionView({ extensionId, viewId }: ExternalExtension return ( - Extension error + {t("extensions.extensionError")} {error} ); } if (!node) { - return ; + return ; } return ; } diff --git a/windows/tauri/src/extensions/ui/components/pro-gate.tsx b/windows/tauri/src/extensions/ui/components/pro-gate.tsx index 8f3b1677a..61c2d4e58 100644 --- a/windows/tauri/src/extensions/ui/components/pro-gate.tsx +++ b/windows/tauri/src/extensions/ui/components/pro-gate.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { LockIcon as Lock } from "@/ui/icons"; import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/ui/empty"; +import { useTranslation } from "@/i18n/locale-provider"; import { useProFeature } from "../hooks/use-pro-feature"; import { ProBadge } from "./pro-badge"; @@ -10,6 +11,7 @@ interface ProGateProps { } export function ProGate({ children, fallback }: ProGateProps) { + const { t } = useTranslation(); const { hasHostedAi } = useProFeature(); if (hasHostedAi) { @@ -27,10 +29,10 @@ export function ProGate({ children, fallback }: ProGateProps) { - Pro Feature + {t("extensions.proFeature")} - Upgrade to Pro to unlock this feature. + {t("extensions.upgradeToPro")} ); diff --git a/windows/tauri/src/extensions/v0/components/v0-design-system-command.tsx b/windows/tauri/src/extensions/v0/components/v0-design-system-command.tsx index 6f8bdcc91..a34b76c0b 100644 --- a/windows/tauri/src/extensions/v0/components/v0-design-system-command.tsx +++ b/windows/tauri/src/extensions/v0/components/v0-design-system-command.tsx @@ -20,6 +20,7 @@ import { type V0DesignSystemSuggestion, } from "@/extensions/v0/lib/v0-design-systems"; import type { V0DesignSystemProfile } from "@/extensions/v0/types/v0-design-system.types"; +import { useTranslation } from "@/i18n/locale-provider"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; import Badge from "@/ui/badge"; import { @@ -57,17 +58,17 @@ type DesignSystemRow = const NO_DESIGN_SYSTEM_ROW: DesignSystemRow = { kind: "none", id: "", - name: "No design system", - description: "Use v0 defaults", + name: "", + description: "", registryUrl: "", }; -function getNameFromRegistryUrl(registryUrl: string): string { +function getNameFromRegistryUrl(registryUrl: string, fallbackName: string): string { try { const parsed = new URL(registryUrl); return parsed.hostname.replace(/^www\./, ""); } catch { - return registryUrl.replace(/^https?:\/\//, "").replace(/\/.*$/, "") || "Design system"; + return registryUrl.replace(/^https?:\/\//, "").replace(/\/.*$/, "") || fallbackName; } } @@ -115,6 +116,7 @@ export function V0DesignSystemCommandContent({ onBack, onClose, }: V0DesignSystemCommandContentProps) { + const { t } = useTranslation(); const settings = useSettingsStore( useShallow((state) => ({ activeV0DesignSystemId: state.settings.activeV0DesignSystemId, @@ -155,11 +157,15 @@ export function V0DesignSystemCommandContent({ const rows = useMemo( () => [ - NO_DESIGN_SYSTEM_ROW, + { + ...NO_DESIGN_SYSTEM_ROW, + name: t("v0DesignSystem.noneName"), + description: t("v0DesignSystem.noneDescription"), + }, ...settings.v0DesignSystems.map((profile) => ({ ...profile, kind: "profile" as const })), ...visibleSuggestions.map((suggestion) => ({ ...suggestion, kind: "suggestion" as const })), ], - [settings.v0DesignSystems, visibleSuggestions], + [settings.v0DesignSystems, t, visibleSuggestions], ); const filteredRows = useMemo( @@ -214,7 +220,7 @@ export function V0DesignSystemCommandContent({ headers: { Accept: "application/json" }, }); if (!response.ok) { - throw new Error(`Registry directory returned ${response.status}`); + throw new Error(t("v0DesignSystem.registryDirectoryStatus", { status: response.status })); } const directory = await response.json(); @@ -223,10 +229,10 @@ export function V0DesignSystemCommandContent({ } catch (error) { setDirectoryStatus("error"); setDirectoryError( - error instanceof Error ? error.message : "Could not load public registries", + error instanceof Error ? error.message : t("v0DesignSystem.loadPublicFailed"), ); } - }, []); + }, [t]); useEffect(() => { if (!isActive || directoryStatus !== "idle") return; @@ -315,11 +321,12 @@ export function V0DesignSystemCommandContent({ const saveProfile = useCallback(async () => { const registryUrl = registryUrlInput.trim(); if (!registryUrl) { - setFormError("Registry URL is required."); + setFormError(t("v0DesignSystem.registryRequired")); return; } - const name = nameInput.trim() || getNameFromRegistryUrl(registryUrl); + const name = + nameInput.trim() || getNameFromRegistryUrl(registryUrl, t("v0DesignSystem.fallbackName")); const id = getUniqueProfileId(settings.v0DesignSystems, name, registryUrl); const fallbackProfile: V0DesignSystemProfile = { id, @@ -344,6 +351,7 @@ export function V0DesignSystemCommandContent({ persistProfile, registryUrlInput, settings.v0DesignSystems, + t, ]); const removeSelectedProfile = useCallback(() => { @@ -411,13 +419,13 @@ export function V0DesignSystemCommandContent({ setMode("list"); requestAnimationFrame(() => searchInputRef.current?.focus()); }} - aria-label="Back to v0 design systems" + aria-label={t("v0DesignSystem.backToDesignSystems")} >
- Add v0 design system + {t("v0DesignSystem.addTitle")}
@@ -436,14 +444,14 @@ export function V0DesignSystemCommandContent({ value={nameInput} onChange={(event) => setNameInput(event.currentTarget.value)} onKeyDown={handleFormKeyDown} - placeholder="Name" + placeholder={t("v0DesignSystem.namePlaceholder")} size="xs" /> setDescriptionInput(event.currentTarget.value)} onKeyDown={handleFormKeyDown} - placeholder="Notes" + placeholder={t("v0DesignSystem.notesPlaceholder")} size="xs" /> {formError &&
{formError}
} @@ -455,9 +463,11 @@ export function V0DesignSystemCommandContent({ onClick={() => void saveProfile()} disabled={Boolean(savingRegistryUrl)} > - {savingRegistryUrl ? "Saving..." : "Save and use"} + {savingRegistryUrl ? t("ui.saving") : t("v0DesignSystem.saveAndUse")} + + setMode("list")}> + {t("ui.cancel")} - setMode("list")}>Cancel ); @@ -467,7 +477,11 @@ export function V0DesignSystemCommandContent({ <>
- + @@ -476,17 +490,21 @@ export function V0DesignSystemCommandContent({ value={query} onChange={setQuery} onKeyDown={handleListKeyDown} - placeholder="Search v0 design systems..." + placeholder={t("v0DesignSystem.searchPlaceholder")} className="flex-1" /> void loadDirectorySuggestions()} - tooltip="Refresh public registries" + tooltip={t("v0DesignSystem.refreshPublicRegistries")} > - +
@@ -494,7 +512,7 @@ export function V0DesignSystemCommandContent({ {filteredRows.length === 0 ? ( - No design systems found + {t("v0DesignSystem.noFound")} ) : ( filteredRows.map((row, index) => { const isCurrent = row.id === settings.activeV0DesignSystemId; @@ -523,19 +541,19 @@ export function V0DesignSystemCommandContent({
{isAdding ? ( - adding + {t("v0DesignSystem.adding")} ) : isCurrent ? ( - active + {t("v0DesignSystem.active")} ) : row.kind === "profile" ? ( - saved + {t("v0DesignSystem.saved")} ) : row.kind === "suggestion" ? ( - add + {t("v0DesignSystem.add")} ) : null} @@ -547,25 +565,25 @@ export function V0DesignSystemCommandContent({ - Add registry + {t("v0DesignSystem.addRegistry")} void loadDirectorySuggestions()}> - Refresh + {t("ui.refresh")} - Remove selected + {t("v0DesignSystem.removeSelected")} {directoryStatus === "loading" - ? "Loading..." + ? t("ui.loading") : directoryStatus === "error" ? directoryError - : `${visibleSuggestions.length} public`} + : t("v0DesignSystem.publicCount", { count: visibleSuggestions.length })} diff --git a/windows/tauri/src/extensions/viewers/csv/csv-preview.tsx b/windows/tauri/src/extensions/viewers/csv/csv-preview.tsx index 0eac31088..630e711ac 100644 --- a/windows/tauri/src/extensions/viewers/csv/csv-preview.tsx +++ b/windows/tauri/src/extensions/viewers/csv/csv-preview.tsx @@ -4,6 +4,7 @@ import { useBufferStore } from "@/features/editor/stores/buffer.store"; import { useEditorSettingsStore } from "@/features/editor/stores/settings.store"; import { hasTextContent } from "@/features/panes/types/pane-content.types"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; +import { useTranslation } from "@/i18n/locale-provider"; import { Button } from "@/ui/button"; import Select from "@/ui/select"; import { TableView } from "./csv-table-view"; @@ -27,6 +28,7 @@ function autodetectDelimiter(text: string): Delim { } export function CsvPreview() { + const { t } = useTranslation(); const sourceContent = useBufferStore((state) => { const activeBuffer = state.activeBufferId ? state.buffers.find((buffer) => buffer.id === state.activeBufferId) @@ -94,22 +96,22 @@ export function CsvPreview() { htmlFor="csv-delimiter" className="font-sans mr-1 text-subtle-foreground ui-text-sm" > - Delimiter + {t("csv.delimiter")} setSearchTerm(event.target.value)} onKeyDown={(event) => { @@ -236,7 +239,7 @@ function ContextSelectorDropdownContent({ size="xs" leftIcon={Search} className="w-full" - aria-label="Search context" + aria-label={t("ai.searchContext")} />
{shouldShowContextResults ? ( @@ -251,7 +254,7 @@ function ContextSelectorDropdownContent({ setSelectedContextIndex(filteredContextBuffers.length + index) } onResultsChange={setVisibleFileResults} - emptyLabel="No matching context found" + emptyLabel={t("ai.noMatchingContextFound")} compact showSearchInput={false} listClassName="max-h-66" @@ -259,7 +262,7 @@ function ContextSelectorDropdownContent({ filteredContextBuffers.length > 0 ? ( <>
- Open tabs + {t("ai.openTabs")}
{filteredContextBuffers.map((buffer) => { const index = filteredContextBuffers.indexOf(buffer); @@ -293,12 +296,12 @@ function ContextSelectorDropdownContent({ {buffer.name} - {getBufferContextDescription(buffer)} + {getBufferContextDescription(buffer, t)} {isSelected && ( - added + {t("ai.added")} )} diff --git a/windows/tauri/src/features/ai/components/selectors/model-selector.tsx b/windows/tauri/src/features/ai/components/selectors/model-selector.tsx index 42eb77d91..53201a57e 100644 --- a/windows/tauri/src/features/ai/components/selectors/model-selector.tsx +++ b/windows/tauri/src/features/ai/components/selectors/model-selector.tsx @@ -2,6 +2,7 @@ import { LockIcon as Lock, WarningCircleIcon as WarningCircle } from "@/ui/icons import { useAIModelOptions } from "@/features/ai/hooks/use-ai-model-options"; import { ProBadge } from "@/extensions/ui/components/pro-badge"; import { Alert, AlertDescription } from "@/ui/alert"; +import { useTranslation } from "@/i18n/locale-provider"; import Select from "@/ui/select"; import { cn } from "@/utils/cn"; @@ -30,6 +31,7 @@ export function ModelSelector({ onOpenChange, tooltip, }: ModelSelectorProps) { + const { t } = useTranslation(); const isComposer = appearance === "composer"; const { availableModels, currentModelName, hasHostedAi, isCustomProvider, modelFetchError } = useAIModelOptions(providerId, modelId, onChange); @@ -50,13 +52,13 @@ export function ModelSelector({ }; })} placeholder={currentModelName} - aria-label="Select AI model" + aria-label={t("ai.selectAiModel")} searchable searchableTrigger={isComposer ? "input" : "menu"} openDirection={isComposer ? "up" : "down"} allowCustomValue={isCustomProvider} - customValueLabel={(customValue) => `Use ${customValue}`} - emptyLabel={isCustomProvider ? "Type a model name and press Enter" : "No models found"} + customValueLabel={(customValue) => t("ai.useCustomValue", { value: customValue })} + emptyLabel={isCustomProvider ? t("ai.typeModelName") : t("ai.noModelsFound")} hideChevron={isComposer} size="xs" variant={isComposer ? "ghost" : "default"} diff --git a/windows/tauri/src/features/ai/components/selectors/provider-selector.tsx b/windows/tauri/src/features/ai/components/selectors/provider-selector.tsx index 730e30a3e..372303317 100644 --- a/windows/tauri/src/features/ai/components/selectors/provider-selector.tsx +++ b/windows/tauri/src/features/ai/components/selectors/provider-selector.tsx @@ -3,6 +3,7 @@ import { useAvailableProviders, useProviderById, } from "@/features/ai/hooks/use-available-providers"; +import { useTranslation } from "@/i18n/locale-provider"; import Select from "@/ui/select"; import { cn } from "@/utils/cn"; @@ -29,6 +30,7 @@ export function ProviderSelector({ onOpenChange, tooltip, }: ProviderSelectorProps) { + const { t } = useTranslation(); const providers = useAvailableProviders(); const currentProvider = useProviderById(providerId); const isComposer = appearance === "composer"; @@ -49,8 +51,8 @@ export function ProviderSelector({ /> ), }))} - placeholder={currentProvider?.name || providerId || "Select provider"} - aria-label="Select AI provider" + placeholder={currentProvider?.name || providerId || t("ai.selectProvider")} + aria-label={t("ai.selectAiProvider")} searchable searchableTrigger={isComposer ? "input" : "menu"} hideChevron={isComposer} diff --git a/windows/tauri/src/features/ai/components/skills/skills-command.tsx b/windows/tauri/src/features/ai/components/skills/skills-command.tsx index cd8d46e33..427dafd6c 100644 --- a/windows/tauri/src/features/ai/components/skills/skills-command.tsx +++ b/windows/tauri/src/features/ai/components/skills/skills-command.tsx @@ -26,6 +26,7 @@ import { import { fuzzyScore } from "@/features/global-search/utils/fuzzy-search"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; import { useSettingsSyncStore } from "@/features/settings/stores/settings-sync.store"; +import { useTranslation } from "@/i18n/locale-provider"; import type { AIChatSkill, MarketplaceSkill } from "@/features/ai/types/skills.types"; import { Button } from "@/ui/button"; import Command, { @@ -59,11 +60,11 @@ function createSkillId() { return `skill-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; } -function getSyncLabel(enabled: boolean, status: string) { - if (!enabled) return "Not synced"; - if (status === "syncing") return "Syncing"; - if (status === "error") return "Sync paused"; - return "Synced"; +function getSyncLabel(enabled: boolean, status: string, t: (key: string) => string) { + if (!enabled) return t("ai.notSynced"); + if (status === "syncing") return t("ai.syncing"); + if (status === "error") return t("ai.syncPaused"); + return t("ai.synced"); } function getSyncIcon(enabled: boolean, status: string) { @@ -79,6 +80,7 @@ export function SkillsCommand({ onSelectSkill, initialView = "list", }: SkillsCommandProps) { + const { t } = useTranslation(); const inputRef = useRef(null); const titleInputRef = useRef(null); const resultsRef = useRef(null); @@ -352,39 +354,39 @@ export function SkillsCommand({ ref={inputRef} value={query} onChange={setQuery} - placeholder={view === "browse" ? "Search available skills..." : "Search skills..."} + placeholder={view === "browse" ? t("ai.searchAvailableSkills") : t("ai.searchSkills")} /> {view === "list" ? ( - New + {t("ai.new")} ) : ( setView("list")}> - My skills + {t("ai.mySkills")} )} - Browse + {t("ai.browse")} {view === "browse" ? ( isLoadingMarketplace ? ( - Loading available skills... + {t("ai.loadingAvailableSkills")} ) : marketplaceSkills.length === 0 ? (
-
No published skills yet
+
{t("ai.noPublishedSkillsYet")}
- Published skills will appear here once the Lithe skills registry is available. + {t("ai.publishedSkillsWillAppear")}
) : filteredMarketplaceSkills.length === 0 ? ( - No available skills match "{query}" + {t("ai.noAvailableSkillsMatch", { query })} ) : ( filteredMarketplaceSkills.map((skill, index) => { const isSelected = selectedIndex === index; @@ -432,7 +434,7 @@ export function SkillsCommand({ } }} > - {isInstalled ? "Added" : "Add"} + {isInstalled ? t("ai.added") : t("ai.add")} } /> @@ -440,9 +442,9 @@ export function SkillsCommand({ }) ) ) : skills.length === 0 ? ( - No skills yet + {t("ai.noSkillsYet")} ) : filteredSkills.length === 0 ? ( - No skills match "{query}" + {t("ai.noSkillsMatch", { query })} ) : ( filteredSkills.map((skill, index) => { const isSelected = selectedIndex === index; @@ -464,10 +466,10 @@ export function SkillsCommand({ accessory={ <> {skill.source === "marketplace" ? ( - Marketplace + {t("ai.marketplace")} ) : null} {hasLocalOverride ? ( - Local override + {t("ai.localOverride")} ) : null} } @@ -481,8 +483,8 @@ export function SkillsCommand({ openSkillEditor(skill); }} className="opacity-0 focus:opacity-100 group-hover:opacity-100" - tooltip="Edit skill" - aria-label={`Edit ${skill.title}`} + tooltip={t("ai.editSkill")} + aria-label={t("ai.editNamedSkill", { title: skill.title })} size={isComposerAttached ? "icon-xs" : "icon"} > @@ -495,8 +497,8 @@ export function SkillsCommand({ void handleDelete(skill.id); }} className="opacity-0 hover:bg-destructive/10 hover:text-destructive focus:opacity-100 group-hover:opacity-100" - tooltip="Delete skill" - aria-label={`Delete ${skill.title}`} + tooltip={t("ai.deleteSkill")} + aria-label={t("ai.deleteNamedSkill", { title: skill.title })} size={isComposerAttached ? "icon-xs" : "icon"} > @@ -512,7 +514,7 @@ export function SkillsCommand({ - {getSyncLabel(syncEnabled, syncStatus)} + {getSyncLabel(syncEnabled, syncStatus, t)} @@ -521,7 +523,7 @@ export function SkillsCommand({
- {editingSkillId ? "Edit skill" : "New skill"} + {editingSkillId ? t("ai.editSkill") : t("ai.newSkill")}
{(() => { const editingSkill = skills.find((skill) => skill.id === editingSkillId); @@ -529,8 +531,9 @@ export function SkillsCommand({ return (
- Marketplace skill - {hasSkillLocalOverride(editingSkill) ? " with local override" : ""} + {hasSkillLocalOverride(editingSkill) + ? t("ai.marketplaceSkillWithLocalOverride") + : t("ai.marketplaceSkill")}
); })()} @@ -543,14 +546,14 @@ export function SkillsCommand({ className="font-sans ui-text-base text-subtle-foreground" htmlFor="ai-skill-title" > - Title + {t("ai.title")} setTitle(event.target.value)} - placeholder="Code review checklist" + placeholder={t("ai.skillTitlePlaceholder")} maxLength={120} size="sm" /> @@ -561,13 +564,13 @@ export function SkillsCommand({ className="font-sans ui-text-base text-subtle-foreground" htmlFor="ai-skill-content" > - Markdown + {t("ai.markdown")}