feat(i18n): add translations for model configuration UI (#368)

- Add ~40 new translation keys for model-config-dialog and model-selector
- Support English, Chinese, and Japanese translations
- Replace all hardcoded strings with dictionary lookups
This commit is contained in:
Dayuan Jiang
2025-12-23 11:42:27 +09:00
committed by GitHub
parent 98625dd72a
commit 8cb7494d16
5 changed files with 301 additions and 68 deletions

View File

@@ -52,6 +52,7 @@ import {
} from "@/components/ui/select"
import { useDictionary } from "@/hooks/use-dictionary"
import type { UseModelConfigReturn } from "@/hooks/use-model-config"
import { formatMessage } from "@/lib/i18n/utils"
import type { ProviderConfig, ProviderName } from "@/lib/types/model-config"
import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config"
import { cn } from "@/lib/utils"
@@ -107,10 +108,12 @@ function ValidationButton({
status,
onClick,
disabled,
dict,
}: {
status: ValidationStatus
onClick: () => void
disabled: boolean
dict: ReturnType<typeof useDictionary>
}) {
return (
<Button
@@ -129,10 +132,10 @@ function ValidationButton({
) : status === "success" ? (
<>
<Check className="h-4 w-4 mr-1.5" />
Verified
{dict.modelConfig.verified}
</>
) : (
"Test"
dict.modelConfig.test
)}
</Button>
)
@@ -406,7 +409,7 @@ export function ModelConfigDialog({
<div className="w-56 flex-shrink-0 flex flex-col border-r bg-muted/20">
<div className="px-4 py-3 border-b">
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
Providers
{dict.modelConfig.providers}
</span>
</div>
@@ -418,7 +421,7 @@ export function ModelConfigDialog({
<Plus className="h-5 w-5 text-muted-foreground" />
</div>
<p className="text-xs text-muted-foreground">
Add a provider to get started
{dict.modelConfig.addProviderHint}
</p>
</div>
) : (
@@ -484,7 +487,11 @@ export function ModelConfigDialog({
>
<SelectTrigger className="h-9 bg-background hover:bg-accent">
<Plus className="h-4 w-4 mr-2 text-muted-foreground" />
<SelectValue placeholder="Add Provider" />
<SelectValue
placeholder={
dict.modelConfig.addProvider
}
/>
</SelectTrigger>
<SelectContent>
{availableProviders.map((p) => (
@@ -552,15 +559,27 @@ export function ModelConfigDialog({
<p className="text-xs text-muted-foreground">
{selectedProvider.models
.length === 0
? "No models configured"
: `${selectedProvider.models.length} model${selectedProvider.models.length > 1 ? "s" : ""} configured`}
? dict.modelConfig
.noModelsConfigured
: formatMessage(
dict.modelConfig
.modelsConfiguredCount,
{
count: selectedProvider
.models
.length,
},
)}
</p>
</div>
{selectedProvider.validated && (
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<Check className="h-3.5 w-3.5" />
<span className="text-xs font-medium">
Verified
{
dict.modelConfig
.verified
}
</span>
</div>
)}
@@ -570,7 +589,12 @@ export function ModelConfigDialog({
<div className="space-y-4">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<Settings2 className="h-4 w-4" />
<span>Configuration</span>
<span>
{
dict.modelConfig
.configuration
}
</span>
</div>
<div className="rounded-xl border bg-card p-4 space-y-4">
@@ -581,7 +605,10 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5"
>
<Tag className="h-3.5 w-3.5 text-muted-foreground" />
Display Name
{
dict.modelConfig
.displayName
}
</Label>
<Input
id="provider-name"
@@ -616,8 +643,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5"
>
<Key className="h-3.5 w-3.5 text-muted-foreground" />
AWS Access Key
ID
{
dict
.modelConfig
.awsAccessKeyId
}
</Label>
<Input
id="aws-access-key-id"
@@ -649,8 +679,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5"
>
<Key className="h-3.5 w-3.5 text-muted-foreground" />
AWS Secret
Access Key
{
dict
.modelConfig
.awsSecretAccessKey
}
</Label>
<div className="relative">
<Input
@@ -674,7 +707,11 @@ export function ModelConfigDialog({
.value,
)
}
placeholder="Enter your secret access key"
placeholder={
dict
.modelConfig
.enterSecretKey
}
className="h-9 pr-10 font-mono text-xs"
/>
<button
@@ -707,7 +744,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5"
>
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
AWS Region
{
dict
.modelConfig
.awsRegion
}
</Label>
<Select
value={
@@ -724,7 +765,13 @@ export function ModelConfigDialog({
}
>
<SelectTrigger className="h-9 font-mono text-xs hover:bg-accent">
<SelectValue placeholder="Select region" />
<SelectValue
placeholder={
dict
.modelConfig
.selectRegion
}
/>
</SelectTrigger>
<SelectContent className="max-h-64">
<SelectItem value="us-east-1">
@@ -819,10 +866,16 @@ export function ModelConfigDialog({
"success" ? (
<>
<Check className="h-4 w-4 mr-1.5" />
Verified
{
dict
.modelConfig
.verified
}
</>
) : (
"Test"
dict
.modelConfig
.test
)}
</Button>
{validationStatus ===
@@ -846,7 +899,11 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5"
>
<Key className="h-3.5 w-3.5 text-muted-foreground" />
API Key
{
dict
.modelConfig
.apiKey
}
</Label>
<div className="flex gap-2">
<div className="relative flex-1">
@@ -870,7 +927,11 @@ export function ModelConfigDialog({
.value,
)
}
placeholder="Enter your API key"
placeholder={
dict
.modelConfig
.enterApiKey
}
className="h-9 pr-10 font-mono text-xs"
/>
<button
@@ -924,10 +985,16 @@ export function ModelConfigDialog({
"success" ? (
<>
<Check className="h-4 w-4 mr-1.5" />
Verified
{
dict
.modelConfig
.verified
}
</>
) : (
"Test"
dict
.modelConfig
.test
)}
</Button>
</div>
@@ -950,9 +1017,17 @@ export function ModelConfigDialog({
className="text-xs font-medium flex items-center gap-1.5"
>
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
Base URL
{
dict
.modelConfig
.baseUrl
}
<span className="text-muted-foreground font-normal">
(optional)
{
dict
.modelConfig
.optional
}
</span>
</Label>
<Input
@@ -974,7 +1049,9 @@ export function ModelConfigDialog({
.provider
]
.defaultBaseUrl ||
"Custom endpoint URL"
dict
.modelConfig
.customEndpoint
}
className="h-9 font-mono text-xs"
/>
@@ -989,12 +1066,20 @@ export function ModelConfigDialog({
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<Sparkles className="h-4 w-4" />
<span>Models</span>
<span>
{
dict.modelConfig
.models
}
</span>
</div>
<div className="flex items-center gap-2">
<div className="relative">
<Input
placeholder="Custom model ID..."
placeholder={
dict.modelConfig
.customModelId
}
value={
customModelInput
}
@@ -1088,8 +1173,12 @@ export function ModelConfigDialog({
<span className="text-xs">
{availableSuggestions.length ===
0
? "All added"
: "Suggested"}
? dict
.modelConfig
.allAdded
: dict
.modelConfig
.suggested}
</span>
</SelectTrigger>
<SelectContent className="max-h-72">
@@ -1124,7 +1213,10 @@ export function ModelConfigDialog({
<Sparkles className="h-5 w-5 text-muted-foreground" />
</div>
<p className="text-sm text-muted-foreground">
No models configured
{
dict.modelConfig
.noModelsConfigured
}
</p>
</div>
) : (
@@ -1291,7 +1383,9 @@ export function ModelConfigDialog({
!newModelId
) {
showError(
"Model ID cannot be empty",
dict
.modelConfig
.modelIdEmpty,
)
return
}
@@ -1319,7 +1413,9 @@ export function ModelConfigDialog({
)
) {
showError(
"This model ID already exists",
dict
.modelConfig
.modelIdExists,
)
return
}
@@ -1383,7 +1479,10 @@ export function ModelConfigDialog({
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
>
<Trash2 className="h-4 w-4 mr-2" />
Delete Provider
{
dict.modelConfig
.deleteProvider
}
</Button>
</div>
</div>
@@ -1395,11 +1494,10 @@ export function ModelConfigDialog({
<Server className="h-8 w-8 text-primary/60" />
</div>
<h3 className="font-semibold mb-1">
Configure AI Providers
{dict.modelConfig.configureProviders}
</h3>
<p className="text-sm text-muted-foreground max-w-xs">
Select a provider from the list or add a new
one to configure API keys and models
{dict.modelConfig.selectProviderHint}
</p>
</div>
)}
@@ -1410,7 +1508,7 @@ export function ModelConfigDialog({
<div className="px-6 py-3 border-t bg-muted/20">
<p className="text-xs text-muted-foreground text-center flex items-center justify-center gap-1.5">
<Key className="h-3 w-3" />
API keys are stored locally in your browser
{dict.modelConfig.apiKeyStored}
</p>
</div>
</DialogContent>
@@ -1429,19 +1527,16 @@ export function ModelConfigDialog({
<AlertCircle className="h-6 w-6 text-destructive" />
</div>
<AlertDialogTitle className="text-center">
Delete Provider
{dict.modelConfig.deleteProvider}
</AlertDialogTitle>
<AlertDialogDescription className="text-center">
Are you sure you want to delete{" "}
<span className="font-medium text-foreground">
{selectedProvider
{formatMessage(dict.modelConfig.deleteConfirmDesc, {
name: selectedProvider
? selectedProvider.name ||
PROVIDER_INFO[selectedProvider.provider]
.label
: "this provider"}
</span>
? This will remove all configured models and cannot
be undone.
: "this provider",
})}
</AlertDialogDescription>
</AlertDialogHeader>
{selectedProvider &&
@@ -1451,11 +1546,16 @@ export function ModelConfigDialog({
htmlFor="delete-confirm"
className="text-sm text-muted-foreground"
>
Type &quot;
{selectedProvider.name ||
PROVIDER_INFO[selectedProvider.provider]
.label}
&quot; to confirm
{formatMessage(
dict.modelConfig.typeToConfirm,
{
name:
selectedProvider.name ||
PROVIDER_INFO[
selectedProvider.provider
].label,
},
)}
</Label>
<Input
id="delete-confirm"
@@ -1463,13 +1563,17 @@ export function ModelConfigDialog({
onChange={(e) =>
setDeleteConfirmText(e.target.value)
}
placeholder="Type provider name..."
placeholder={
dict.modelConfig.typeProviderName
}
className="h-9"
/>
</div>
)}
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogCancel>
{dict.modelConfig.cancel}
</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteProvider}
disabled={
@@ -1482,7 +1586,7 @@ export function ModelConfigDialog({
}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50"
>
Delete
{dict.modelConfig.delete}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -16,6 +16,7 @@ import {
ModelSelectorTrigger,
} from "@/components/ai-elements/model-selector"
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
import { useDictionary } from "@/hooks/use-dictionary"
import type { FlattenedModel } from "@/lib/types/model-config"
import { cn } from "@/lib/utils"
@@ -67,6 +68,7 @@ export function ModelSelector({
onConfigure,
disabled = false,
}: ModelSelectorProps) {
const dict = useDictionary()
const [open, setOpen] = useState(false)
// Only show validated models in the selector
const validatedModels = useMemo(
@@ -96,8 +98,8 @@ export function ModelSelector({
}
const tooltipContent = selectedModel
? `${selectedModel.modelId} (click to change)`
: "Using server default model (click to change)"
? `${selectedModel.modelId} ${dict.modelConfig.clickToChange}`
: `${dict.modelConfig.usingServerDefault} ${dict.modelConfig.clickToChange}`
return (
<ModelSelectorRoot open={open} onOpenChange={setOpen}>
@@ -111,22 +113,26 @@ export function ModelSelector({
>
<Bot className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
<span className="text-xs truncate">
{selectedModel ? selectedModel.modelId : "Default"}
{selectedModel
? selectedModel.modelId
: dict.modelConfig.default}
</span>
<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
</ButtonWithTooltip>
</ModelSelectorTrigger>
<ModelSelectorContent title="Select Model">
<ModelSelectorInput placeholder="Search models..." />
<ModelSelectorContent title={dict.modelConfig.selectModel}>
<ModelSelectorInput
placeholder={dict.modelConfig.searchModels}
/>
<ModelSelectorList>
<ModelSelectorEmpty>
{validatedModels.length === 0 && models.length > 0
? "No verified models. Test your models first."
: "No models found."}
? dict.modelConfig.noVerifiedModels
: dict.modelConfig.noModelsFound}
</ModelSelectorEmpty>
{/* Server Default Option */}
<ModelSelectorGroup heading="Default">
<ModelSelectorGroup heading={dict.modelConfig.default}>
<ModelSelectorItem
value="__server_default__"
onSelect={handleSelect}
@@ -145,7 +151,7 @@ export function ModelSelector({
/>
<Server className="mr-2 h-4 w-4 text-muted-foreground" />
<ModelSelectorName>
Server Default
{dict.modelConfig.serverDefault}
</ModelSelectorName>
</ModelSelectorItem>
</ModelSelectorGroup>
@@ -201,13 +207,13 @@ export function ModelSelector({
>
<Settings2 className="mr-2 h-4 w-4" />
<ModelSelectorName>
Configure Models...
{dict.modelConfig.configureModels}
</ModelSelectorName>
</ModelSelectorItem>
</ModelSelectorGroup>
{/* Info text */}
<div className="px-3 py-2 text-xs text-muted-foreground border-t">
Only verified models are shown
{dict.modelConfig.onlyVerifiedShown}
</div>
</ModelSelectorList>
</ModelSelectorContent>