mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix localization bugs
This commit is contained in:
parent
1853ebaa9c
commit
4f8799f169
6 changed files with 100 additions and 89 deletions
|
|
@ -6,12 +6,13 @@ import { PackageManagerSource } from "../../../../src/services/package-manager/t
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "cmdk"
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "cmdk"
|
||||||
import { PackageManagerItemCard } from "./components/PackageManagerItemCard"
|
import { PackageManagerItemCard } from "./components/PackageManagerItemCard"
|
||||||
import { useStateManager } from "./useStateManager"
|
import { useStateManager } from "./useStateManager"
|
||||||
|
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||||
|
|
||||||
interface PackageManagerViewProps {
|
interface PackageManagerViewProps {
|
||||||
onDone?: () => void
|
onDone?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
|
const { t } = useAppTranslation()
|
||||||
const [state, manager] = useStateManager()
|
const [state, manager] = useStateManager()
|
||||||
|
|
||||||
const [tagSearch, setTagSearch] = useState("")
|
const [tagSearch, setTagSearch] = useState("")
|
||||||
|
|
@ -31,7 +32,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
<Tab>
|
<Tab>
|
||||||
<TabHeader className="flex justify-between items-center sticky top-0 z-10 bg-vscode-editor-background border-b border-vscode-panel-border">
|
<TabHeader className="flex justify-between items-center sticky top-0 z-10 bg-vscode-editor-background border-b border-vscode-panel-border">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<h3 className="text-vscode-foreground m-0">Package Manager</h3>
|
<h3 className="text-vscode-foreground m-0">{t("package-manager:title")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -41,7 +42,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground",
|
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground",
|
||||||
)}
|
)}
|
||||||
onClick={() => manager.transition({ type: "SET_ACTIVE_TAB", payload: { tab: "browse" } })}>
|
onClick={() => manager.transition({ type: "SET_ACTIVE_TAB", payload: { tab: "browse" } })}>
|
||||||
Browse
|
{t("package-manager:tabs.browse")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant={state.activeTab === "sources" ? "default" : "secondary"}
|
variant={state.activeTab === "sources" ? "default" : "secondary"}
|
||||||
|
|
@ -50,7 +51,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground",
|
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground",
|
||||||
)}
|
)}
|
||||||
onClick={() => manager.transition({ type: "SET_ACTIVE_TAB", payload: { tab: "sources" } })}>
|
onClick={() => manager.transition({ type: "SET_ACTIVE_TAB", payload: { tab: "sources" } })}>
|
||||||
Sources
|
{t("package-manager:tabs.sources")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</TabHeader>
|
</TabHeader>
|
||||||
|
|
@ -61,7 +62,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search package manager items..."
|
placeholder={t("package-manager:filters.search.placeholder")}
|
||||||
value={state.filters.search}
|
value={state.filters.search}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
manager.transition({
|
manager.transition({
|
||||||
|
|
@ -75,7 +76,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
<div className="flex flex-wrap justify-between gap-2">
|
<div className="flex flex-wrap justify-between gap-2">
|
||||||
<div className="whitespace-nowrap">
|
<div className="whitespace-nowrap">
|
||||||
<label htmlFor="type-filter" className="mr-2">
|
<label htmlFor="type-filter" className="mr-2">
|
||||||
Filter by type:
|
{t("package-manager:filters.type.label")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="type-filter"
|
id="type-filter"
|
||||||
|
|
@ -87,16 +88,18 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="p-1 bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded">
|
className="p-1 bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded">
|
||||||
<option value="">All types</option>
|
<option value="">{t("package-manager:filters.type.all")}</option>
|
||||||
<option value="mode">Mode</option>
|
<option value="mode">{t("package-manager:filters.type.mode")}</option>
|
||||||
<option value="mcp server">MCP Server</option>
|
<option value="mcp server">
|
||||||
<option value="prompt">Prompt</option>
|
{t("package-manager:filters.type.mcp server")}
|
||||||
<option value="package">Package</option>
|
</option>
|
||||||
|
<option value="prompt">{t("package-manager:filters.type.prompt")}</option>
|
||||||
|
<option value="package">{t("package-manager:filters.type.package")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="whitespace-nowrap">
|
<div className="whitespace-nowrap">
|
||||||
<label className="mr-2">Sort by:</label>
|
<label className="mr-2">{t("package-manager:filters.sort.label")}</label>
|
||||||
<select
|
<select
|
||||||
value={state.sortConfig.by}
|
value={state.sortConfig.by}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
@ -106,8 +109,10 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="p-1 bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded mr-2">
|
className="p-1 bg-vscode-dropdown-background text-vscode-dropdown-foreground border border-vscode-dropdown-border rounded mr-2">
|
||||||
<option value="name">Name</option>
|
<option value="name">{t("package-manager:filters.sort.name")}</option>
|
||||||
<option value="lastUpdated">Last Updated</option>
|
<option value="lastUpdated">
|
||||||
|
{t("package-manager:filters.sort.lastUpdated")}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
@ -130,9 +135,13 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label className="mr-2">Filter by tags:</label>
|
<label className="mr-2">
|
||||||
|
{t("package-manager:filters.tags.label")}
|
||||||
|
</label>
|
||||||
<span className="text-xs text-vscode-descriptionForeground">
|
<span className="text-xs text-vscode-descriptionForeground">
|
||||||
({allTags.length} available)
|
{t("package-manager:filters.tags.available", {
|
||||||
|
count: allTags.length,
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{state.filters.tags.length > 0 && (
|
{state.filters.tags.length > 0 && (
|
||||||
|
|
@ -144,13 +153,15 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="p-1 bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground rounded text-xs">
|
className="p-1 bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground rounded text-xs">
|
||||||
Clear tags ({state.filters.tags.length})
|
{t("package-manager:filters.tags.clear", {
|
||||||
|
count: state.filters.tags.length,
|
||||||
|
})}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Command className="rounded-lg border border-vscode-dropdown-border">
|
<Command className="rounded-lg border border-vscode-dropdown-border">
|
||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder="Type to search and select tags..."
|
placeholder={t("package-manager:filters.tags.placeholder")}
|
||||||
value={tagSearch}
|
value={tagSearch}
|
||||||
onValueChange={setTagSearch}
|
onValueChange={setTagSearch}
|
||||||
onFocus={() => setIsTagInputActive(true)}
|
onFocus={() => setIsTagInputActive(true)}
|
||||||
|
|
@ -164,7 +175,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
{(isTagInputActive || tagSearch) && (
|
{(isTagInputActive || tagSearch) && (
|
||||||
<CommandList className="max-h-[200px] overflow-y-auto bg-vscode-dropdown-background">
|
<CommandList className="max-h-[200px] overflow-y-auto bg-vscode-dropdown-background">
|
||||||
<CommandEmpty className="p-2 text-sm text-vscode-descriptionForeground">
|
<CommandEmpty className="p-2 text-sm text-vscode-descriptionForeground">
|
||||||
No matching tags found
|
{t("package-manager:filters.tags.noResults")}
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{allTags
|
{allTags
|
||||||
|
|
@ -222,8 +233,10 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
</Command>
|
</Command>
|
||||||
<div className="text-xs text-vscode-descriptionForeground mt-1">
|
<div className="text-xs text-vscode-descriptionForeground mt-1">
|
||||||
{state.filters.tags.length > 0
|
{state.filters.tags.length > 0
|
||||||
? `Showing items with any of the selected tags (${state.filters.tags.length} selected)`
|
? t("package-manager:filters.tags.selected", {
|
||||||
: "Click tags to filter items"}
|
count: state.filters.tags.length,
|
||||||
|
})
|
||||||
|
: t("package-manager:filters.tags.clickToFilter")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -243,7 +256,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-64 text-vscode-descriptionForeground">
|
<div className="flex flex-col items-center justify-center h-64 text-vscode-descriptionForeground">
|
||||||
<p>Loading items...</p>
|
<p>{t("package-manager:items.refresh.refreshing")}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +265,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-64 text-vscode-descriptionForeground">
|
<div className="flex flex-col items-center justify-center h-64 text-vscode-descriptionForeground">
|
||||||
<p>No package manager items found</p>
|
<p>{t("package-manager:items.empty.noItems")}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -261,9 +274,7 @@ const PackageManagerView: React.FC<PackageManagerViewProps> = ({ onDone }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-vscode-descriptionForeground mb-4">
|
<p className="text-vscode-descriptionForeground mb-4">
|
||||||
{state.filters.type || state.filters.search || state.filters.tags.length > 0
|
{t("package-manager:items.count", { count: items.length })}
|
||||||
? `${items.length} items found (filtered)`
|
|
||||||
: `${items.length} ${items.length === 1 ? "item" : "items"} total`}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-1 gap-4 pb-4">
|
<div className="grid grid-cols-1 gap-4 pb-4">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
|
|
@ -313,48 +324,49 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
onRefreshSource,
|
onRefreshSource,
|
||||||
onSourcesChange,
|
onSourcesChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useAppTranslation()
|
||||||
const [newSourceUrl, setNewSourceUrl] = useState("")
|
const [newSourceUrl, setNewSourceUrl] = useState("")
|
||||||
const [newSourceName, setNewSourceName] = useState("")
|
const [newSourceName, setNewSourceName] = useState("")
|
||||||
const [error, setError] = useState("")
|
const [error, setError] = useState("")
|
||||||
|
|
||||||
const handleAddSource = () => {
|
const handleAddSource = () => {
|
||||||
if (!newSourceUrl) {
|
if (!newSourceUrl) {
|
||||||
setError("URL cannot be empty")
|
setError(t("package-manager:sources.errors.emptyUrl"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new URL(newSourceUrl)
|
new URL(newSourceUrl)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError("Invalid URL format")
|
setError(t("package-manager:sources.errors.invalidUrl"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonVisibleCharRegex = /[^\S ]/
|
const nonVisibleCharRegex = /[^\S ]/
|
||||||
if (nonVisibleCharRegex.test(newSourceUrl)) {
|
if (nonVisibleCharRegex.test(newSourceUrl)) {
|
||||||
setError("URL contains non-visible characters other than spaces")
|
setError(t("package-manager:sources.errors.nonVisibleChars"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidGitRepositoryUrl(newSourceUrl)) {
|
if (!isValidGitRepositoryUrl(newSourceUrl)) {
|
||||||
setError("URL must be a valid Git repository URL (e.g., https://github.com/username/repo)")
|
setError(t("package-manager:sources.errors.invalidGitUrl"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedNewUrl = newSourceUrl.toLowerCase().replace(/\s+/g, "")
|
const normalizedNewUrl = newSourceUrl.toLowerCase().replace(/\s+/g, "")
|
||||||
if (sources.some((source) => source.url.toLowerCase().replace(/\s+/g, "") === normalizedNewUrl)) {
|
if (sources.some((source) => source.url.toLowerCase().replace(/\s+/g, "") === normalizedNewUrl)) {
|
||||||
setError("This URL is already in the list (case and whitespace insensitive match)")
|
setError(t("package-manager:sources.errors.duplicateUrl"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newSourceName) {
|
if (newSourceName) {
|
||||||
if (newSourceName.length > 20) {
|
if (newSourceName.length > 20) {
|
||||||
setError("Name must be 20 characters or less")
|
setError(t("package-manager:sources.errors.nameTooLong"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonVisibleCharRegex.test(newSourceName)) {
|
if (nonVisibleCharRegex.test(newSourceName)) {
|
||||||
setError("Name contains non-visible characters other than spaces")
|
setError(t("package-manager:sources.errors.nonVisibleCharsName"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,14 +376,14 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
(source) => source.name && source.name.toLowerCase().replace(/\s+/g, "") === normalizedNewName,
|
(source) => source.name && source.name.toLowerCase().replace(/\s+/g, "") === normalizedNewName,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
setError("This name is already in use (case and whitespace insensitive match)")
|
setError(t("package-manager:sources.errors.duplicateName"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_SOURCES = 10
|
const MAX_SOURCES = 10
|
||||||
if (sources.length >= MAX_SOURCES) {
|
if (sources.length >= MAX_SOURCES) {
|
||||||
setError(`Maximum of ${MAX_SOURCES} sources allowed`)
|
setError(t("package-manager:sources.errors.maxSources", { max: MAX_SOURCES }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,18 +413,15 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-vscode-foreground mb-2">Configure Package Manager Sources</h4>
|
<h4 className="text-vscode-foreground mb-2">{t("package-manager:sources.title")}</h4>
|
||||||
<p className="text-vscode-descriptionForeground mb-4">
|
<p className="text-vscode-descriptionForeground mb-4">{t("package-manager:sources.description")}</p>
|
||||||
Add Git repositories that contain package manager items. These repositories will be fetched when
|
|
||||||
browsing the package manager.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h5 className="text-vscode-foreground mb-2">Add New Source</h5>
|
<h5 className="text-vscode-foreground mb-2">{t("package-manager:sources.add.title")}</h5>
|
||||||
<div className="flex flex-col gap-2 mb-2">
|
<div className="flex flex-col gap-2 mb-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Git repository URL (e.g., https://github.com/username/repo)"
|
placeholder={t("package-manager:sources.add.urlPlaceholder")}
|
||||||
value={newSourceUrl}
|
value={newSourceUrl}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setNewSourceUrl(e.target.value)
|
setNewSourceUrl(e.target.value)
|
||||||
|
|
@ -421,12 +430,11 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
className="p-2 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded"
|
className="p-2 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-vscode-descriptionForeground mt-1 mb-2">
|
<p className="text-xs text-vscode-descriptionForeground mt-1 mb-2">
|
||||||
Supported formats: HTTPS (https://github.com/username/repo), SSH
|
{t("package-manager:sources.add.urlFormats")}
|
||||||
(git@github.com:username/repo.git), or Git protocol (git://github.com/username/repo.git)
|
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Display name (optional, max 20 chars)"
|
placeholder={t("package-manager:sources.add.namePlaceholder")}
|
||||||
value={newSourceName}
|
value={newSourceName}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setNewSourceName(e.target.value.slice(0, 20))
|
setNewSourceName(e.target.value.slice(0, 20))
|
||||||
|
|
@ -439,15 +447,17 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
{error && <p className="text-red-500 mb-2">{error}</p>}
|
{error && <p className="text-red-500 mb-2">{error}</p>}
|
||||||
<Button onClick={handleAddSource}>
|
<Button onClick={handleAddSource}>
|
||||||
<span className="codicon codicon-add mr-2"></span>
|
<span className="codicon codicon-add mr-2"></span>
|
||||||
Add Source
|
{t("package-manager:sources.add.button")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<h5 className="text-vscode-foreground mb-2">
|
<h5 className="text-vscode-foreground mb-2">
|
||||||
Current Sources{" "}
|
{t("package-manager:sources.current.title")}{" "}
|
||||||
<span className="text-vscode-descriptionForeground text-sm">({sources.length}/10 max)</span>
|
<span className="text-vscode-descriptionForeground text-sm">
|
||||||
|
{t("package-manager:sources.current.count", { current: sources.length, max: 10 })}
|
||||||
|
</span>
|
||||||
</h5>
|
</h5>
|
||||||
{sources.length === 0 ? (
|
{sources.length === 0 ? (
|
||||||
<p className="text-vscode-descriptionForeground">No sources configured. Add a source to get started.</p>
|
<p className="text-vscode-descriptionForeground">{t("package-manager:sources.current.empty")}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-2">
|
<div className="grid grid-cols-1 gap-2">
|
||||||
{sources.map((source, index) => (
|
{sources.map((source, index) => (
|
||||||
|
|
@ -477,7 +487,7 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => onRefreshSource(source.url)}
|
onClick={() => onRefreshSource(source.url)}
|
||||||
title="Refresh this source"
|
title={t("package-manager:sources.current.refresh")}
|
||||||
className="text-vscode-foreground"
|
className="text-vscode-foreground"
|
||||||
disabled={refreshingUrls.includes(source.url)}>
|
disabled={refreshingUrls.includes(source.url)}>
|
||||||
<span
|
<span
|
||||||
|
|
@ -487,6 +497,7 @@ const PackageManagerSourcesConfig: React.FC<PackageManagerSourcesConfigProps> =
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => handleRemoveSource(index)}
|
onClick={() => handleRemoveSource(index)}
|
||||||
|
title={t("package-manager:sources.current.remove")}
|
||||||
className="text-red-500">
|
className="text-red-500">
|
||||||
<span className="codicon codicon-trash"></span>
|
<span className="codicon codicon-trash"></span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,15 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
||||||
const getTypeLabel = (type: string) => {
|
const getTypeLabel = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "mode":
|
case "mode":
|
||||||
return t("package_manager:item_card.type_mode")
|
return t("package-manager:filters.type.mode")
|
||||||
case "mcp server":
|
case "mcp server":
|
||||||
return t("package_manager:item_card.type_mcp_server")
|
return t("package-manager:filters.type.mcp server")
|
||||||
case "prompt":
|
case "prompt":
|
||||||
return t("package_manager:item_card.type_prompt")
|
return t("package-manager:filters.type.prompt")
|
||||||
case "package":
|
case "package":
|
||||||
return t("package_manager:item_card.type_package")
|
return t("package-manager:filters.type.package")
|
||||||
default:
|
default:
|
||||||
return t("package_manager:item_card.type_other")
|
return t("package-manager:filters.type.all")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
||||||
url: item.authorUrl,
|
url: item.authorUrl,
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
{t("package_manager:item_card.by_author", { author: item.author })}
|
{t("package-manager:items.card.by", { author: item.author })}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
|
|
@ -125,13 +125,13 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
||||||
url: item.authorUrl,
|
url: item.authorUrl,
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
{t("package_manager:item_card.authors_profile")}
|
{t("package-manager:items.card.viewSource")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
) : item.author ? (
|
) : item.author ? (
|
||||||
<p className="text-sm text-vscode-descriptionForeground">
|
<p className="text-sm text-vscode-descriptionForeground">
|
||||||
{t("package_manager:item_card.by_author", { author: item.author })}
|
{t("package-manager:items.card.by", { author: item.author })}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -168,8 +168,8 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
filters.tags.includes(tag)
|
filters.tags.includes(tag)
|
||||||
? t("package_manager:item_card.remove_tag_filter", { tag })
|
? t("package-manager:filters.tags.clear", { count: tag })
|
||||||
: t("package_manager:item_card.filter_by_tag", { tag })
|
: t("package-manager:filters.tags.clickToFilter")
|
||||||
}>
|
}>
|
||||||
{tag}
|
{tag}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -202,23 +202,21 @@ export const PackageManagerItemCard: React.FC<PackageManagerItemCardProps> = ({
|
||||||
aria-label={
|
aria-label={
|
||||||
item.sourceUrl && isValidUrl(item.sourceUrl)
|
item.sourceUrl && isValidUrl(item.sourceUrl)
|
||||||
? ""
|
? ""
|
||||||
: item.sourceName || t("package_manager:item_card.source")
|
: item.sourceName || t("package-manager:items.card.viewSource")
|
||||||
}>
|
}>
|
||||||
<span
|
<span
|
||||||
className={`codicon codicon-link-external${!item.sourceUrl || !isValidUrl(item.sourceUrl) ? " mr-2" : ""}`}></span>
|
className={`codicon codicon-link-external${!item.sourceUrl || !isValidUrl(item.sourceUrl) ? " mr-2" : ""}`}></span>
|
||||||
{(!item.sourceUrl || !isValidUrl(item.sourceUrl)) &&
|
{(!item.sourceUrl || !isValidUrl(item.sourceUrl)) &&
|
||||||
(item.sourceName || t("package_manager:item_card.source"))}
|
(item.sourceName || t("package-manager:items.card.viewSource"))}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{groupedItems && (
|
{groupedItems && (
|
||||||
<ExpandableSection
|
<ExpandableSection
|
||||||
title={t("package_manager:item_card.component_details")}
|
title={t("package-manager:items.card.externalComponents", { count: 0 })}
|
||||||
badge={(() => {
|
badge={(() => {
|
||||||
const matchCount = item.items?.filter((subItem) => subItem.matchInfo?.matched).length ?? 0
|
const matchCount = item.items?.filter((subItem) => subItem.matchInfo?.matched).length ?? 0
|
||||||
return matchCount > 0
|
return matchCount > 0 ? t("package-manager:items.count", { count: matchCount }) : undefined
|
||||||
? t("package_manager:item_card.match_count", { count: matchCount })
|
|
||||||
: undefined
|
|
||||||
})()}
|
})()}
|
||||||
defaultExpanded={item.items?.some((subItem) => subItem.matchInfo?.matched) ?? false}>
|
defaultExpanded={item.items?.some((subItem) => subItem.matchInfo?.matched) ?? false}>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
|
||||||
|
|
@ -22,15 +22,15 @@ export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className })
|
||||||
const getTypeLabel = (type: string) => {
|
const getTypeLabel = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "mode":
|
case "mode":
|
||||||
return t("package_manager:type_group.modes")
|
return t("package-manager:type-group.modes")
|
||||||
case "mcp server":
|
case "mcp server":
|
||||||
return t("package_manager:type_group.mcp_servers")
|
return t("package-manager:type-group.mcp-servers")
|
||||||
case "prompt":
|
case "prompt":
|
||||||
return t("package_manager:type_group.prompts")
|
return t("package-manager:type-group.prompts")
|
||||||
case "package":
|
case "package":
|
||||||
return t("package_manager:type_group.packages")
|
return t("package-manager:type-group.packages")
|
||||||
default:
|
default:
|
||||||
return t("package_manager:type_group.generic_type", {
|
return t("package-manager:type-group.generic-type", {
|
||||||
type: type.charAt(0).toUpperCase() + type.slice(1),
|
type: type.charAt(0).toUpperCase() + type.slice(1),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +60,7 @@ export const TypeGroup: React.FC<TypeGroupProps> = ({ type, items, className })
|
||||||
)}
|
)}
|
||||||
{item.matchInfo?.matched && (
|
{item.matchInfo?.matched && (
|
||||||
<span className="ml-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
|
<span className="ml-2 text-xs bg-vscode-badge-background text-vscode-badge-foreground px-1 py-0.5 rounded">
|
||||||
{t("package_manager:type_group.match")}
|
{t("package-manager:type-group.match")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ describe("PackageManagerItemCard", () => {
|
||||||
expect(screen.getByText("Test Package")).toBeInTheDocument()
|
expect(screen.getByText("Test Package")).toBeInTheDocument()
|
||||||
expect(screen.getByText("A test package")).toBeInTheDocument()
|
expect(screen.getByText("A test package")).toBeInTheDocument()
|
||||||
expect(screen.getByText("by Test Author")).toBeInTheDocument()
|
expect(screen.getByText("by Test Author")).toBeInTheDocument()
|
||||||
expect(screen.getByText("Package")).toBeInTheDocument()
|
expect(screen.getByText(/Package/i)).toBeInTheDocument() // Using case-insensitive regex since translations might vary in case
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should render tags", () => {
|
it("should render tags", () => {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ i18next.use(initReactI18next).init({
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // React already escapes by default
|
escapeValue: false, // React already escapes by default
|
||||||
},
|
},
|
||||||
|
defaultNS: "package-manager",
|
||||||
|
ns: ["package-manager"],
|
||||||
})
|
})
|
||||||
|
|
||||||
export function loadTranslations() {
|
export function loadTranslations() {
|
||||||
|
|
|
||||||
|
|
@ -19,25 +19,25 @@ i18next.use(initReactI18next).init({
|
||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
en: {
|
en: {
|
||||||
package_manager: {
|
"package-manager": {
|
||||||
// Type group translations
|
// Type group translations
|
||||||
"type_group.mcp_servers": "MCP Servers",
|
"type-group.mcp-servers": "MCP Servers",
|
||||||
"type_group.modes": "Modes",
|
"type-group.modes": "Modes",
|
||||||
"type_group.prompts": "Prompts",
|
"type-group.prompts": "Prompts",
|
||||||
"type_group.packages": "Packages",
|
"type-group.packages": "Packages",
|
||||||
"type_group.match": "Match",
|
"type-group.match": "Match",
|
||||||
"type_group.generic_type": "{{type}}s",
|
"type-group.generic-type": "{{type}}s",
|
||||||
|
|
||||||
// Item card translations
|
// Item card translations
|
||||||
"item_card.by_author": "by {{author}}",
|
"item-card.by-author": "by {{author}}",
|
||||||
"item_card.type_package": "Package",
|
"item-card.type-package": "Package",
|
||||||
"item_card.type_mode": "Mode",
|
"item-card.type-mode": "Mode",
|
||||||
"item_card.type_mcp_server": "MCP Server",
|
"item-card.type-mcp-server": "MCP Server",
|
||||||
"item_card.type_prompt": "Prompt",
|
"item-card.type-prompt": "Prompt",
|
||||||
"item_card.source": "Source",
|
"item-card.source": "Source",
|
||||||
"item_card.component_details": "Component Details",
|
"item-card.component-details": "Component Details",
|
||||||
"item_card.filter_by_tag": "Filter by tag",
|
"item-card.filter-by-tag": "Filter by tag",
|
||||||
"item_card.by": "by",
|
"item-card.by": "by",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue