fix localization bugs

This commit is contained in:
Smartsheet-JB-Brown 2025-04-17 13:32:08 -07:00
parent 1853ebaa9c
commit 4f8799f169
6 changed files with 100 additions and 89 deletions

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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", () => {

View file

@ -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() {

View file

@ -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",
}, },
}, },
}, },