diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx index 87d689e3162..c1a89f4cdad 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx @@ -1,5 +1,6 @@ "use client"; +import type { TFunction } from "i18next"; import React from "react"; import { useTranslation } from "react-i18next"; @@ -7,11 +8,12 @@ import type { AutoRouterDeployment } from "@/app/(dashboard)/hooks/models/useMod import { hydrateTierLabels } from "@/components/add_model/build_complexity_router_config"; import { TIER_KEYS, - effectiveTierLabel, + effectiveTierDisplayLabel, type ComplexityTierLabels, type ComplexityTiers, } from "@/components/add_model/ComplexityRouterConfig"; import { normalizeTierModels } from "@/components/add_model/complexity_router_tiers"; +import i18n from "@/i18n/i18n"; import { chartColorValue, DEFAULT_COLOR_CYCLE, DonutChart } from "@/components/shared/charts"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -35,8 +37,11 @@ const asRecord = (value: unknown): Record => { const isComplexityTier = (tier: string): tier is keyof ComplexityTiers => (TIER_KEYS as readonly string[]).includes(tier); -export const tierDisplayLabel = (tier: string, tierLabels: ComplexityTierLabels | undefined): string => - isComplexityTier(tier) ? effectiveTierLabel(tier, tierLabels) : tier; +export const tierDisplayLabel = ( + tier: string, + tierLabels: ComplexityTierLabels | undefined, + t: TFunction = i18n.t.bind(i18n), +): string => (isComplexityTier(tier) ? effectiveTierDisplayLabel(tier, tierLabels, t) : tier); const CONFIG_KEY_BY_ROUTER_TYPE: Record> = { complexity: "complexity_router_config", @@ -93,7 +98,7 @@ const TierTurnsChart: React.FC = ({ view, autoRouters }) => const tierLabels = tierLabelsFor(group.router_name, group.router_type, autoRouters); const total = entries.reduce((sum, [, turns]) => sum + turns, 0); const slices = entries.map(([tier, turns]) => ({ - tier: tierDisplayLabel(tier, tierLabels), + tier: tierDisplayLabel(tier, tierLabels, t), turns, models: tierModelsFor(tier, group.router_name, group.router_type, autoRouters), })); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx index 3687ef963c0..df433afd2b9 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTable.tsx @@ -38,10 +38,10 @@ export const WILDCARD_MODEL_GROUP_VALUE = "wildcard"; const MODEL_TABLE_BODY_HEIGHT = 600; const ALL_PROXY_MODELS_LABEL: ParseKeys = "modelsAndEndpoints.allModelsTable.allProxyModels"; -const FILTER_LABELS: Record = { - [MODEL_NAME_COLUMN_ID]: "Public Model Name", - [ACCESS_GROUPS_COLUMN_ID]: "Model Access Group", - [ROUTING_STATUS_FILTER_ID]: "Status", +const FILTER_LABELS: Record = { + [MODEL_NAME_COLUMN_ID]: "modelsAndEndpoints.allModelsTable.filterLabels.publicModelName", + [ACCESS_GROUPS_COLUMN_ID]: "modelsAndEndpoints.allModelsTable.filterLabels.modelAccessGroup", + [ROUTING_STATUS_FILTER_ID]: "modelsAndEndpoints.allModelsTable.filterLabels.status", }; const ROUTING_STATUS_FILTER_VALUES = { @@ -241,7 +241,9 @@ export function AllModelsTable({ onOpenFilters={() => setFiltersOpen(true)} onRefresh={onRefresh} isRefreshing={isRefreshing} - filterLabels={FILTER_LABELS} + filterLabels={Object.fromEntries( + Object.entries(FILTER_LABELS).map(([columnId, labelKey]) => [columnId, t(labelKey)]), + )} formatFilterValue={formatFilterValue} > ({ value: mode.value, label: t(mode.labelKey) }))} value={(control.value as string | undefined) ?? null} onValueChange={(value: string | null) => { control.onChange(value); @@ -326,7 +326,7 @@ const AddModelForm: React.FC = ({ {TEST_MODES.map((mode) => ( - {mode.label} + {t(mode.labelKey)} ))} diff --git a/ui/litellm-dashboard/src/components/add_model/AutoRouterSimpleSetup.tsx b/ui/litellm-dashboard/src/components/add_model/AutoRouterSimpleSetup.tsx index 04ee03f7572..092bf41c444 100644 --- a/ui/litellm-dashboard/src/components/add_model/AutoRouterSimpleSetup.tsx +++ b/ui/litellm-dashboard/src/components/add_model/AutoRouterSimpleSetup.tsx @@ -83,7 +83,7 @@ export default function AutoRouterSimpleSetup({ aria-pressed={goal === key} onClick={() => onGoalChange(key as RouterGoal)} > - {label} + {t(label)} ))} diff --git a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx index ea70d499099..6f514785bb9 100644 --- a/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ClassificationMethodConfig.tsx @@ -42,7 +42,7 @@ import { DEFAULT_CLASSIFIER_TIMEOUT_MS, DEFAULT_CLASSIFICATION_RUBRIC, ClassificationRubric, - effectiveTierLabel, + effectiveTierDisplayLabel, heuristicScoringRole, heuristicTuningType, usesHeuristicV2, @@ -119,25 +119,25 @@ const HowClassificationWorks: React.FC<{ value: ComplexityRouterConfigValue }> = {scorerRuns && ranges && (
  • - {effectiveTierLabel("SIMPLE", value.tier_labels)}:{" "} + {effectiveTierDisplayLabel("SIMPLE", value.tier_labels, t)}:{" "} {t("addModel.classificationMethodConfig.howItWorks.scoreBelow", { max: ranges.simpleMedium })}
  • - {effectiveTierLabel("MEDIUM", value.tier_labels)}:{" "} + {effectiveTierDisplayLabel("MEDIUM", value.tier_labels, t)}:{" "} {t("addModel.classificationMethodConfig.howItWorks.scoreBetween", { min: ranges.simpleMedium, max: ranges.mediumComplex, })}
  • - {effectiveTierLabel("COMPLEX", value.tier_labels)}:{" "} + {effectiveTierDisplayLabel("COMPLEX", value.tier_labels, t)}:{" "} {t("addModel.classificationMethodConfig.howItWorks.scoreBetween", { min: ranges.mediumComplex, max: ranges.complexReasoning, })}
  • - {effectiveTierLabel("REASONING", value.tier_labels)}:{" "} + {effectiveTierDisplayLabel("REASONING", value.tier_labels, t)}:{" "} {t("addModel.classificationMethodConfig.howItWorks.scoreAbove", { min: ranges.complexReasoning, floor: ranges.reasoningOverrideFloor, @@ -506,7 +506,7 @@ const ClassificationMethodConfig: React.FC = ({ {HEURISTIC_FIRST_MAX_TIER_KEYS.map((tier) => ( - {effectiveTierLabel(tier, value.tier_labels)} + {effectiveTierDisplayLabel(tier, value.tier_labels, t)} ))} diff --git a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx index e757813783d..e99a8c6e949 100644 --- a/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ComplexityRouterConfig.tsx @@ -104,36 +104,27 @@ export const DEFAULT_CLASSIFICATION_RUBRIC: ClassificationRubric = "legacy"; */ export const NEW_CLASSIFIER_CLASSIFICATION_RUBRIC: ClassificationRubric = "agentic"; -export const CLASSIFICATION_RUBRIC_DESCRIPTIONS: Record = - { - legacy: { - label: "Legacy (uncalibrated)", - description: - "The rubric as it shipped before calibration examples, with no worked examples at all. Routers created " + - "before this setting existed use it, so their tier decisions and spend are unchanged. It over-routes " + - "ordinary engineering to the most expensive tier.", - }, - agentic: { - label: "Agentic", - description: - "Anchors routine installs, builds, multi-file edits, and standard debugging at " + - "Medium, so ordinary engineering does not route to your most expensive tier. Suits agent, terminal, and " + - "coding-assistant traffic, and mixed traffic.", - }, - chat: { - label: "Chat", - description: - "Drops the engineering examples, for a router serving only conversational traffic that never sees those " + - "requests.", - }, - business: { - label: "Business", - description: - "Business and sales examples plus business-oriented tier definitions: routine drafting and summarizing " + - "stay at Medium, data-determined analysis is Complex, and only decisions under conflicting tradeoffs " + - "reach Reasoning. Suits sales, support, and go-to-market traffic.", - }, - }; +export const CLASSIFICATION_RUBRIC_DESCRIPTIONS: Record< + ClassificationRubric, + { labelKey: ParseKeys; descriptionKey: ParseKeys } +> = { + legacy: { + labelKey: "addModel.complexityRouterConfig.rubrics.legacy.label", + descriptionKey: "addModel.complexityRouterConfig.rubrics.legacy.description", + }, + agentic: { + labelKey: "addModel.complexityRouterConfig.rubrics.agentic.label", + descriptionKey: "addModel.complexityRouterConfig.rubrics.agentic.description", + }, + chat: { + labelKey: "addModel.complexityRouterConfig.rubrics.chat.label", + descriptionKey: "addModel.complexityRouterConfig.rubrics.chat.description", + }, + business: { + labelKey: "addModel.complexityRouterConfig.rubrics.business.label", + descriptionKey: "addModel.complexityRouterConfig.rubrics.business.description", + }, +}; export const CLASSIFICATION_RUBRIC_KEYS = Object.keys(CLASSIFICATION_RUBRIC_DESCRIPTIONS) as ClassificationRubric[]; @@ -204,7 +195,9 @@ const rowOrigin = (row: TierRow, editing: boolean, t: TFunction = i18n.t.bind(i1 : t("addModel.complexityRouterConfig.rowOrigin.custom"); }; -const builtInTierInfo = (rowId: string): { label: string; description: ParseKeys; examples: ParseKeys } | undefined => { +const builtInTierInfo = ( + rowId: string, +): { labelKey: ParseKeys; description: ParseKeys; examples: ParseKeys } | undefined => { const builtIn = ALL_BUILT_IN_TIERS.find((tier) => tier === rowId); return builtIn ? TIER_DESCRIPTIONS[builtIn] : undefined; }; @@ -531,30 +524,35 @@ interface ComplexityRouterConfigProps { export const TIER_DESCRIPTIONS: Record< keyof ComplexityTiers, - { label: string; description: ParseKeys; examples: ParseKeys } + { label: string; labelKey: ParseKeys; description: ParseKeys; examples: ParseKeys } > = { NON_REASONING: { label: "Non-reasoning", + labelKey: "addModel.complexityRouterConfig.tiers.nonReasoning.label", description: "addModel.complexityRouterConfig.tiers.nonReasoning.description", examples: "addModel.complexityRouterConfig.tiers.nonReasoning.examples", }, SIMPLE: { label: "Simple", + labelKey: "addModel.complexityRouterConfig.tiers.simple.label", description: "addModel.complexityRouterConfig.tiers.simple.description", examples: "addModel.complexityRouterConfig.tiers.simple.examples", }, MEDIUM: { label: "Medium", + labelKey: "addModel.complexityRouterConfig.tiers.medium.label", description: "addModel.complexityRouterConfig.tiers.medium.description", examples: "addModel.complexityRouterConfig.tiers.medium.examples", }, COMPLEX: { label: "Complex", + labelKey: "addModel.complexityRouterConfig.tiers.complex.label", description: "addModel.complexityRouterConfig.tiers.complex.description", examples: "addModel.complexityRouterConfig.tiers.complex.examples", }, REASONING: { label: "Reasoning", + labelKey: "addModel.complexityRouterConfig.tiers.reasoning.label", description: "addModel.complexityRouterConfig.tiers.reasoning.description", examples: "addModel.complexityRouterConfig.tiers.reasoning.examples", }, @@ -565,6 +563,12 @@ export const TIER_KEYS = Object.keys(TIER_DESCRIPTIONS) as Array tierLabels?.[tier]?.trim() || TIER_DESCRIPTIONS[tier].label; +export const effectiveTierDisplayLabel = ( + tier: keyof ComplexityTiers, + tierLabels: ComplexityTierLabels | undefined, + t: TFunction = i18n.t.bind(i18n), +): string => tierLabels?.[tier]?.trim() || t(TIER_DESCRIPTIONS[tier].labelKey); + export const DEFAULT_HEURISTIC_FIRST_MAX_TIER = "SIMPLE"; /** What the Hybrid radio starts at. Required by that type, so the form always has a value to send. */ @@ -797,10 +801,10 @@ const ComplexityRouterConfig: React.FC = ({ handleTierLabelChange(row.id as keyof ComplexityTiers, event.target.value) } placeholder={t("addModel.complexityRouterConfig.displayName.placeholder", { - label: tierInfo.label, + label: t(tierInfo.labelKey), })} aria-label={t("addModel.complexityRouterConfig.displayName.aria", { - label: tierInfo.label, + label: t(tierInfo.labelKey), })} /> {value.tier_labels?.[row.id as keyof ComplexityTiers] && ( @@ -808,7 +812,7 @@ const ComplexityRouterConfig: React.FC = ({ handleTierLabelChange(row.id as keyof ComplexityTiers, "")} > diff --git a/ui/litellm-dashboard/src/components/add_model/CompressionControls.tsx b/ui/litellm-dashboard/src/components/add_model/CompressionControls.tsx index 2c8ef000e61..6df8c3ad13d 100644 --- a/ui/litellm-dashboard/src/components/add_model/CompressionControls.tsx +++ b/ui/litellm-dashboard/src/components/add_model/CompressionControls.tsx @@ -3,6 +3,7 @@ import { SearchSelect, SearchSelectOption } from "@/components/shared/SearchSele import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Info } from "lucide-react"; +import type { ParseKeys } from "i18next"; import React from "react"; import { useTranslation } from "react-i18next"; import { useGuardrails } from "@/app/(dashboard)/hooks/guardrails/useGuardrails"; @@ -17,7 +18,7 @@ interface CompressionControlsProps { onChange: (state: AutoRouterCompressionState) => void; } -const NONE_OPTION: SearchSelectOption = { label: "None (no compression)", value: NO_COMPRESSION }; +const NONE_OPTION_LABEL: ParseKeys = "addModel.compressionControls.noneOption"; const CompressionControls: React.FC = ({ value, onChange }) => { const { t } = useTranslation(); @@ -30,7 +31,7 @@ const CompressionControls: React.FC = ({ value, onChan const compressionOptions: SearchSelectOption[] = (data?.guardrails ?? []) .filter((g) => isCompressionGuardrailProvider(g.litellm_params?.guardrail)) .map((g) => ({ label: g.guardrail_name, value: g.guardrail_name })); - const options: SearchSelectOption[] = [NONE_OPTION, ...compressionOptions]; + const options: SearchSelectOption[] = [{ label: t(NONE_OPTION_LABEL), value: NO_COMPRESSION }, ...compressionOptions]; return (
    diff --git a/ui/litellm-dashboard/src/components/add_model/CustomDimensionRows.tsx b/ui/litellm-dashboard/src/components/add_model/CustomDimensionRows.tsx index 2dfd5c3ce10..ebb5af749bd 100644 --- a/ui/litellm-dashboard/src/components/add_model/CustomDimensionRows.tsx +++ b/ui/litellm-dashboard/src/components/add_model/CustomDimensionRows.tsx @@ -10,8 +10,8 @@ import { Slider } from "@/components/ui/slider"; import type { CustomDimensionRow } from "./custom_dimensions"; const SCORING_MODES = [ - { value: "binary", label: "Binary" }, - { value: "match_count", label: "Match count" }, + { value: "binary", labelKey: "addModel.customDimensionRows.scoringModes.binary" }, + { value: "match_count", labelKey: "addModel.customDimensionRows.scoringModes.matchCount" }, ] as const; interface Props { @@ -109,7 +109,7 @@ export default function CustomDimensionRows({ rows, disabled, onChange, onWeight
    ({ value: rubric, - label: description.label, + label: t(description.labelKey), }))} value={classificationRubric ?? tierSource.classificationRubric} onValueChange={(rubric: ClassificationRubric | null) => rubric && setRubricDraft(rubric)} @@ -210,13 +210,13 @@ const OpeningPromptEditor: React.FC = ({ > {Object.entries(CLASSIFICATION_RUBRIC_DESCRIPTIONS).map(([rubric, description]) => ( - {description.label} + {t(description.labelKey)} ))}

    - {tierSource.rubricRestriction ?? draftRubricSummary?.description} + {tierSource.rubricRestriction ?? (draftRubricSummary && t(draftRubricSummary.descriptionKey))}

    )} diff --git a/ui/litellm-dashboard/src/components/add_model/TierRestrictions.tsx b/ui/litellm-dashboard/src/components/add_model/TierRestrictions.tsx index 4383bea5972..78551f6a398 100644 --- a/ui/litellm-dashboard/src/components/add_model/TierRestrictions.tsx +++ b/ui/litellm-dashboard/src/components/add_model/TierRestrictions.tsx @@ -8,7 +8,8 @@ export const restrictedBy = ( key: keyof typeof CUSTOM_TIER_RESTRICTIONS, t: TFunction = i18n.t.bind(i18n), ): TierRestriction | undefined => { - if (value.custom_tier_set) return CUSTOM_TIER_RESTRICTIONS[key]; + if (value.custom_tier_set) + return { omit: CUSTOM_TIER_RESTRICTIONS[key].omit, reason: t(CUSTOM_TIER_RESTRICTIONS[key].reason) }; if (value.classifier_type === "llm_v2" && key === "adaptive") return { omit: ["adaptive", "adaptive_weights", "adaptive_eligible", "tier_distance_penalty"], diff --git a/ui/litellm-dashboard/src/components/add_model/add_model_modes.tsx b/ui/litellm-dashboard/src/components/add_model/add_model_modes.tsx index 63def76db8f..ef651b9f7dc 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_model_modes.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_model_modes.tsx @@ -1,20 +1,22 @@ +import type { ParseKeys } from "i18next"; + // Define the available test modes -export const TEST_MODES = [ - { value: "chat", label: "Chat - /chat/completions" }, - { value: "completion", label: "Completion - /completions" }, - { value: "embedding", label: "Embedding - /embeddings" }, - { value: "audio_speech", label: "Audio Speech - /audio/speech" }, - { value: "audio_transcription", label: "Audio Transcription - /audio/transcriptions" }, - { value: "image_generation", label: "Image Generation - /images/generations" }, - { value: "image_edit", label: "Image Edit - /images/edits" }, - { value: "video_generation", label: "Video Generation - /videos" }, - { value: "rerank", label: "Rerank - /rerank" }, - { value: "realtime", label: "Realtime - /realtime" }, - { value: "batch", label: "Batch - /batch" }, - { value: "anthropic_messages", label: "Anthropic Messages - /v1/messages" }, - { value: "ocr", label: "OCR - /ocr" }, - { value: "decisions", label: "Decisions - /v1/decisions" }, - { value: "extraction", label: "Extraction - /v1/extraction" }, +export const TEST_MODES: readonly { value: string; labelKey: ParseKeys }[] = [ + { value: "chat", labelKey: "addModel.addModelModes.chat" }, + { value: "completion", labelKey: "addModel.addModelModes.completion" }, + { value: "embedding", labelKey: "addModel.addModelModes.embedding" }, + { value: "audio_speech", labelKey: "addModel.addModelModes.audioSpeech" }, + { value: "audio_transcription", labelKey: "addModel.addModelModes.audioTranscription" }, + { value: "image_generation", labelKey: "addModel.addModelModes.imageGeneration" }, + { value: "image_edit", labelKey: "addModel.addModelModes.imageEdit" }, + { value: "video_generation", labelKey: "addModel.addModelModes.videoGeneration" }, + { value: "rerank", labelKey: "addModel.addModelModes.rerank" }, + { value: "realtime", labelKey: "addModel.addModelModes.realtime" }, + { value: "batch", labelKey: "addModel.addModelModes.batch" }, + { value: "anthropic_messages", labelKey: "addModel.addModelModes.anthropicMessages" }, + { value: "ocr", labelKey: "addModel.addModelModes.ocr" }, + { value: "decisions", labelKey: "addModel.addModelModes.decisions" }, + { value: "extraction", labelKey: "addModel.addModelModes.extraction" }, ]; // Define the available auto router routing strategies diff --git a/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx b/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx index 28cf847587e..83ac6ff465e 100644 --- a/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx +++ b/ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx @@ -58,8 +58,8 @@ const USAGE_COST_FIELDS = [ const REVALIDATED_WHEN_PTU_COUNT_CHANGES = [PTU_RATE_FIELD, PTU_START_FIELD, ...USAGE_COST_FIELDS]; const PRICING_MODEL_ITEMS = [ - { value: "per_token", label: "Per Million Tokens" }, - { value: "per_second", label: "Per Second" }, + { value: "per_token", labelKey: "addModel.advancedSettings.pricingModels.perToken" }, + { value: "per_second", labelKey: "addModel.advancedSettings.pricingModels.perSecond" }, ] as const; const validateNumber = (_: unknown, value: unknown) => { @@ -320,7 +320,7 @@ const AdvancedSettings: React.FC = ({ > {(control) => ( + ({ value: item.value, label: t(item.labelKey) }))} value={point.role ?? null} onValueChange={(selected) => replaceAt(index, { ...point, role: (selected as CacheControlRole | null) ?? undefined }) @@ -119,7 +122,7 @@ const CacheControlInjectionPoints: React.FC = {t("addModel.cacheControlSettings.noRole")} {ROLE_ITEMS.map((item) => ( - {item.label} + {t(item.labelKey)} ))} diff --git a/ui/litellm-dashboard/src/components/add_model/heuristic_scoring_knobs.ts b/ui/litellm-dashboard/src/components/add_model/heuristic_scoring_knobs.ts index 1249323aa39..185785fd120 100644 --- a/ui/litellm-dashboard/src/components/add_model/heuristic_scoring_knobs.ts +++ b/ui/litellm-dashboard/src/components/add_model/heuristic_scoring_knobs.ts @@ -1,3 +1,4 @@ +import type { ParseKeys } from "i18next"; import i18n from "@/i18n/i18n"; import { serializeCustomDimensions, type CustomDimensionRow } from "./custom_dimensions"; import { heuristicScoringRoleFor, type ClassifierFallback } from "./ComplexityRouterConfig"; @@ -43,17 +44,18 @@ export const scorerKnobPayload = (knobs: ScorerKnobInputs) => { * shipped weights come from the proxy (GET /public/complexity_router/scorer_defaults), so a dimension * added backend-side still renders, under its raw key until it is given a label here. */ -export const DIMENSION_LABELS: Record = { - codePresence: "Code presence", - reasoningMarkers: "Reasoning markers", - technicalTerms: "Technical terms", - tokenCount: "Token count", - simpleIndicators: "Simple indicators", - multiStepPatterns: "Multi-step patterns", - questionComplexity: "Question complexity", +export const DIMENSION_LABELS: Record = { + codePresence: "addModel.heuristicScoringKnobs.dimensions.codePresence", + reasoningMarkers: "addModel.heuristicScoringKnobs.dimensions.reasoningMarkers", + technicalTerms: "addModel.heuristicScoringKnobs.dimensions.technicalTerms", + tokenCount: "addModel.heuristicScoringKnobs.dimensions.tokenCount", + simpleIndicators: "addModel.heuristicScoringKnobs.dimensions.simpleIndicators", + multiStepPatterns: "addModel.heuristicScoringKnobs.dimensions.multiStepPatterns", + questionComplexity: "addModel.heuristicScoringKnobs.dimensions.questionComplexity", }; -export const dimensionLabel = (key: string): string => DIMENSION_LABELS[key] ?? key; +export const dimensionLabel = (key: string): string => + Object.hasOwn(DIMENSION_LABELS, key) ? i18n.t(DIMENSION_LABELS[key]) : key; const asRecord = (raw: unknown): Record | undefined => typeof raw === "object" && raw !== null && !Array.isArray(raw) ? (raw as Record) : undefined; diff --git a/ui/litellm-dashboard/src/components/add_model/provider_auth_types.ts b/ui/litellm-dashboard/src/components/add_model/provider_auth_types.ts index 0bf98407bfa..77a08c45393 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_auth_types.ts +++ b/ui/litellm-dashboard/src/components/add_model/provider_auth_types.ts @@ -1,9 +1,10 @@ +import type { ParseKeys } from "i18next"; import { Providers } from "../provider_info_helpers"; export interface ProviderAuthType { readonly id: string; - readonly label: string; - readonly description: string; + readonly labelKey: ParseKeys; + readonly descriptionKey: ParseKeys; readonly fieldKeys: readonly string[]; readonly requiredFieldKeys: readonly string[]; readonly fixedValues?: Readonly>; @@ -20,18 +21,16 @@ export const PROVIDER_AUTH_TYPES: Partial = ({ )} -

    {selectedAuthType.description}

    +

    {t(selectedAuthType.descriptionKey)}

    )} {credentialOnlySelected ? ( diff --git a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts index a9ea085854c..092dc8a73c5 100644 --- a/ui/litellm-dashboard/src/components/add_model/tier_rows.ts +++ b/ui/litellm-dashboard/src/components/add_model/tier_rows.ts @@ -1,4 +1,4 @@ -import type { TFunction } from "i18next"; +import type { ParseKeys, TFunction } from "i18next"; import i18n from "@/i18n/i18n"; import { isForecastClassifier } from "./forecast_classifier_config"; import type { ClassifierType, ComplexityTiers } from "./ComplexityRouterConfig"; @@ -126,29 +126,27 @@ export interface TierRestriction { export const CUSTOM_TIER_RESTRICTIONS = { displayNames: { omit: ["tier_labels"], - reason: "Display names rename the built-in tiers, which your tier set replaces. Name each tier directly", + reason: "addModel.tierRows.restrictions.displayNames", }, escalation: { omit: ["escalation_keywords"], - reason: "Escalation bumps a request along the built-in tier ladder, which your tier set replaces", + reason: "addModel.tierRows.restrictions.escalation", }, stallEscalation: { omit: ["stall_escalation_enabled", "stall_escalation_window", "stall_escalation_repeat_threshold"], - reason: "Stall escalation bumps a request along the built-in tier ladder, which your tier set replaces", + reason: "addModel.tierRows.restrictions.stallEscalation", }, adaptive: { omit: ["adaptive", "adaptive_weights", "tier_distance_penalty", "adaptive_eligible"], - reason: "Adaptive routing scores models along the built-in tier ladder, which your tier set replaces", + reason: "addModel.tierRows.restrictions.adaptive", }, sessionAffinity: { omit: [], - reason: "Session pinning escalates along the built-in tier ladder, which your tier set replaces", + reason: "addModel.tierRows.restrictions.sessionAffinity", }, heuristicClassifier: { omit: ["heuristic_first_max_tier", "hybrid_boundary_margin", "local_heuristic"], - reason: - "The heuristic scorer only produces the built-in tiers, so an edited set needs the LLM or JEV classifier. " + - "Heuristic first and hybrid are out for the same reason: their local scorer decides the traffic it is sure of", + reason: "addModel.tierRows.restrictions.heuristicClassifier", }, heuristicScoring: { omit: [ @@ -159,17 +157,17 @@ export const CUSTOM_TIER_RESTRICTIONS = { "reasoning_override_min_score", "custom_technical_keywords", ], - reason: "The heuristic scorer never runs under an edited tier set, so its inputs have no effect", + reason: "addModel.tierRows.restrictions.heuristicScoring", }, classificationRubric: { omit: [], - reason: "The preset calibration examples are written against the built-in tiers, which your tier set replaces", + reason: "addModel.tierRows.restrictions.classificationRubric", }, classifierFallback: { omit: ["classifier_fallback"], - reason: "Fallback Tier is where an edited tier set routes when the classifier fails", + reason: "addModel.tierRows.restrictions.classifierFallback", }, -} as const satisfies Record; +} as const satisfies Record; export const CUSTOM_TIER_OMITTED_KEYS: readonly string[] = Object.values(CUSTOM_TIER_RESTRICTIONS).flatMap( (restriction) => restriction.omit, diff --git a/ui/litellm-dashboard/src/components/model_add/CredentialModal.tsx b/ui/litellm-dashboard/src/components/model_add/CredentialModal.tsx index 26c0ca07b07..a7e1d8546ef 100644 --- a/ui/litellm-dashboard/src/components/model_add/CredentialModal.tsx +++ b/ui/litellm-dashboard/src/components/model_add/CredentialModal.tsx @@ -1,3 +1,4 @@ +import type { ParseKeys } from "i18next"; import { Input } from "@/components/ui/input"; import { SearchSelect, type SearchSelectOption } from "@/components/shared/SearchSelect"; import { SimpleTooltip } from "@/components/ui/tooltip"; @@ -54,9 +55,9 @@ const AUTH_METHOD_SELECT_ID = "credential_auth_method"; const API_KEY_FIELDS: readonly string[] = ["api_key"]; const NO_HIDDEN_FIELDS: readonly string[] = []; -const authMethodItems: { value: AuthMethod; label: string }[] = [ - { value: "api_key", label: "API key" }, - { value: "federation", label: "Workload identity federation" }, +const authMethodItems: { value: AuthMethod; labelKey: ParseKeys }[] = [ + { value: "api_key", labelKey: "modelAdd.credentialModal.authMethods.apiKey" }, + { value: "federation", labelKey: "modelAdd.credentialModal.authMethods.federation" }, ]; interface CredentialModalProps { @@ -242,7 +243,7 @@ export default function CredentialModal({ )}