diff --git a/src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx b/src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx index db872cb6d6..08c3fee74e 100644 --- a/src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx +++ b/src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx @@ -3,6 +3,7 @@ // src/app/(dashboard)/dashboard/playground/components/ExportCodeModal.tsx import { useState, useEffect, useCallback } from "react"; +import { useTranslations } from "next-intl"; import type { PlaygroundState, ExportLanguage } from "@/lib/playground/codeExport"; import { exportAllLanguages, API_KEY_PLACEHOLDER } from "@/lib/playground/codeExport"; @@ -23,6 +24,7 @@ const LANGUAGE_TABS: Array<{ id: ExportLanguage; label: string }> = [ * Security: always uses API_KEY_PLACEHOLDER ("$OMNIROUTE_API_KEY") — never a real key (D11 / Hard Rule #1). */ export default function ExportCodeModal({ state, onClose }: ExportCodeModalProps) { + const t = useTranslations("playground"); const [activeLanguage, setActiveLanguage] = useState("curl"); const [copied, setCopied] = useState(false); @@ -63,7 +65,7 @@ export default function ExportCodeModal({ state, onClose }: ExportCodeModalProps onClick={onClose} role="dialog" aria-modal="true" - aria-label="Export code" + aria-label={t("exportCode")} >
</> -

Export code

+

{t("exportCodeTitle")}

@@ -110,8 +112,7 @@ export default function ExportCodeModal({ state, onClose }: ExportCodeModalProps
{hasRealKey ? (
- Security warning: export blocked — a real API key was detected in the output. - Please reset your API key and try again. + {t("exportRealKeyWarning")}
) : (
@@ -121,9 +122,9 @@ export default function ExportCodeModal({ state, onClose }: ExportCodeModalProps
 
           {/* Placeholder hint */}
           

- Replace{" "} + {t("placeholderHintPrefix")}{" "} {API_KEY_PLACEHOLDER} - {" "}with your actual API key, or set it as an environment variable. + {" "}{t("placeholderHintSuffix")}

@@ -133,7 +134,7 @@ export default function ExportCodeModal({ state, onClose }: ExportCodeModalProps onClick={onClose} className="text-xs px-3 py-1.5 rounded border border-border text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5 transition-colors" > - Close + {t("close")}
diff --git a/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx b/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx index d2367678a8..5112a2dbb4 100644 --- a/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx +++ b/src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx @@ -3,6 +3,7 @@ // src/app/(dashboard)/dashboard/playground/components/PresetPicker.tsx import { useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; import { usePresets } from "../hooks/usePresets"; import type { ConfigState } from "./StudioConfigPane"; @@ -18,6 +19,7 @@ interface PresetPickerProps { * Load applies preset values to configState; Save opens a name-input modal. */ export default function PresetPicker({ configState, setConfigState }: PresetPickerProps) { + const t = useTranslations("playground"); const { presets, loading, list, create, remove } = usePresets(); const [saveModalOpen, setSaveModalOpen] = useState(false); const [presetName, setPresetName] = useState(""); @@ -47,7 +49,7 @@ export default function PresetPicker({ configState, setConfigState }: PresetPick async function handleSave() { if (!presetName.trim()) { - setSaveError("Name is required"); + setSaveError(t("nameRequired")); return; } @@ -65,7 +67,7 @@ export default function PresetPicker({ configState, setConfigState }: PresetPick setSaving(false); if (result == null) { - setSaveError("Failed to save preset"); + setSaveError(t("failedToSavePreset")); return; } @@ -89,7 +91,7 @@ export default function PresetPicker({ configState, setConfigState }: PresetPick <>
- Presets + {t("presetsLabel")} {/* Load preset select */} @@ -105,10 +107,10 @@ export default function PresetPicker({ configState, setConfigState }: PresetPick }} defaultValue="" className="flex-1 text-xs bg-surface border border-border rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-primary text-text-main disabled:opacity-50 disabled:cursor-not-allowed" - aria-label="Load a preset" + aria-label={t("loadPreset")} > {presets.map((preset) => (
@@ -162,13 +164,13 @@ export default function PresetPicker({ configState, setConfigState }: PresetPick onClick={closeSave} role="dialog" aria-modal="true" - aria-label="Save preset" + aria-label={t("savePreset")} >
e.stopPropagation()} > -

Save preset

+

{t("savePreset")}

@@ -193,14 +195,14 @@ export default function PresetPicker({ configState, setConfigState }: PresetPick onClick={closeSave} className="text-xs px-3 py-1.5 rounded border border-border text-text-muted hover:text-text-main transition-colors" > - Cancel + {t("cancel")}
diff --git a/src/app/(dashboard)/dashboard/playground/components/StudioTopBar.tsx b/src/app/(dashboard)/dashboard/playground/components/StudioTopBar.tsx index a477d419f7..78839c9388 100644 --- a/src/app/(dashboard)/dashboard/playground/components/StudioTopBar.tsx +++ b/src/app/(dashboard)/dashboard/playground/components/StudioTopBar.tsx @@ -3,6 +3,7 @@ // src/app/(dashboard)/dashboard/playground/components/StudioTopBar.tsx import { useState } from "react"; +import { useTranslations } from "next-intl"; import TokenCostCounter from "./TokenCostCounter"; import ExportCodeModal from "./ExportCodeModal"; import type { StreamMetrics } from "@/shared/schemas/playground"; @@ -20,15 +21,15 @@ interface StudioTopBarProps { interface TabConfig { id: StudioTab; - label: string; + labelKey: "tabChat" | "tabCompare" | "tabApi" | "tabBuild"; icon: string; } const TABS: TabConfig[] = [ - { id: "chat", label: "Chat", icon: "chat" }, - { id: "compare", label: "Compare", icon: "compare" }, - { id: "api", label: "API", icon: "api" }, - { id: "build", label: "Build", icon: "build" }, + { id: "chat", labelKey: "tabChat", icon: "chat" }, + { id: "compare", labelKey: "tabCompare", icon: "compare" }, + { id: "api", labelKey: "tabApi", icon: "api" }, + { id: "build", labelKey: "tabBuild", icon: "build" }, ]; /** @@ -36,6 +37,7 @@ const TABS: TabConfig[] = [ * Export code modal uses ExportCodeModal (F7) when exportState is provided. */ export default function StudioTopBar({ activeTab, onTabChange, metrics, exportState }: StudioTopBarProps) { + const t = useTranslations("playground"); const [exportOpen, setExportOpen] = useState(false); return ( @@ -56,7 +58,7 @@ export default function StudioTopBar({ activeTab, onTabChange, metrics, exportSt }`} > {tab.icon} - {tab.label} + {t(tab.labelKey)} ))} @@ -72,11 +74,11 @@ export default function StudioTopBar({ activeTab, onTabChange, metrics, exportSt @@ -95,17 +97,17 @@ export default function StudioTopBar({ activeTab, onTabChange, metrics, exportSt onClick={(e) => e.stopPropagation()} >
-

Export code

+

{t("exportCodeTitle")}

- No playground state available to export. + {t("noStateToExport")}

diff --git a/src/app/(dashboard)/dashboard/playground/components/tabs/BuildTab.tsx b/src/app/(dashboard)/dashboard/playground/components/tabs/BuildTab.tsx index bf67889bf5..40bfb78c98 100644 --- a/src/app/(dashboard)/dashboard/playground/components/tabs/BuildTab.tsx +++ b/src/app/(dashboard)/dashboard/playground/components/tabs/BuildTab.tsx @@ -3,6 +3,7 @@ // src/app/(dashboard)/dashboard/playground/components/tabs/BuildTab.tsx import { useRef, useState } from "react"; +import { useTranslations } from "next-intl"; import { useToolsBuilder } from "../../hooks/useToolsBuilder"; import { useStructuredOutput } from "../../hooks/useStructuredOutput"; import ToolsBuilder from "../ToolsBuilder"; @@ -45,6 +46,7 @@ interface ToolResultDraft { * for the tool_result + "Send result" button to continue the conversation. */ export default function BuildTab({ configState }: BuildTabProps) { + const t = useTranslations("playground"); const toolsBuilder = useToolsBuilder(); const structuredOutput = useStructuredOutput(); @@ -243,7 +245,7 @@ export default function BuildTab({ configState }: BuildTabProps) { className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded bg-primary text-white hover:bg-primary/90 transition-colors disabled:opacity-40 disabled:cursor-not-allowed" > play_arrow - {running ? "Running…" : "Run"} + {running ? t("running") : t("runLabel")} {messages.length > 0 && ( @@ -251,7 +253,7 @@ export default function BuildTab({ configState }: BuildTabProps) { onClick={clearConversation} className="text-xs px-2.5 py-1.5 rounded border border-border text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5 transition-colors" > - Clear + {t("clearAll")} )} diff --git a/src/app/(dashboard)/dashboard/search-tools/components/ProviderCatalog.tsx b/src/app/(dashboard)/dashboard/search-tools/components/ProviderCatalog.tsx index 6304232561..bbc0df5f3f 100644 --- a/src/app/(dashboard)/dashboard/search-tools/components/ProviderCatalog.tsx +++ b/src/app/(dashboard)/dashboard/search-tools/components/ProviderCatalog.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import Link from "next/link"; +import { useTranslations } from "next-intl"; import type { SearchProviderCatalogItem } from "@/shared/schemas/searchTools"; interface ProviderCatalogProps { @@ -11,6 +12,7 @@ interface ProviderCatalogProps { } function StatusBadge({ status }: { status: SearchProviderCatalogItem["status"] }) { + const t = useTranslations("search"); if (status === "configured") { return ( ); } @@ -29,7 +31,7 @@ function StatusBadge({ status }: { status: SearchProviderCatalogItem["status"] } data-testid="status-rate-limited" > ); } @@ -39,7 +41,7 @@ function StatusBadge({ status }: { status: SearchProviderCatalogItem["status"] } data-testid="status-missing" > ); } @@ -53,6 +55,7 @@ function ProviderCard({ selected: boolean; onSelect?: () => void; }) { + const t = useTranslations("search"); const isClickable = item.status !== "missing" && onSelect; return ( @@ -80,7 +83,7 @@ function ProviderCard({
{item.name}
- {item.kind === "search" ? "Search" : "Fetch/Scrape"} + {item.kind === "search" ? t("kindSearch") : t("kindFetch")}
diff --git a/src/app/(dashboard)/dashboard/search-tools/components/SearchConceptCard.tsx b/src/app/(dashboard)/dashboard/search-tools/components/SearchConceptCard.tsx index 130b95d060..569253860d 100644 --- a/src/app/(dashboard)/dashboard/search-tools/components/SearchConceptCard.tsx +++ b/src/app/(dashboard)/dashboard/search-tools/components/SearchConceptCard.tsx @@ -1,39 +1,33 @@ "use client"; import { useState } from "react"; +import { useTranslations } from "next-intl"; + +type ConceptKey = "search" | "scrape" | "compare" | "rerank" | "auto"; interface ConceptItem { icon: string; - title: string; - description: string; + key: ConceptKey; + titleKey: + | "searchConceptTitle" + | "scrapeConceptTitle" + | "compareConceptTitle" + | "rerankConceptTitle" + | "autoConceptTitle"; + descKey: + | "searchConceptDesc" + | "scrapeConceptDesc" + | "compareConceptDesc" + | "rerankConceptDesc" + | "autoConceptDesc"; } const CONCEPTS: ConceptItem[] = [ - { - icon: "🔍", - title: "Search", - description: "Search = busca na web (lista de links com título, URL, snippet e score de relevância)", - }, - { - icon: "📄", - title: "Scrape", - description: "Scrape = extrai o conteúdo completo de uma URL (markdown, texto ou HTML)", - }, - { - icon: "⚖", - title: "Compare", - description: "Compare = executa a mesma query em N providers side-by-side para comparar latência, custo e overlap de resultados", - }, - { - icon: "↕", - title: "Rerank", - description: "Rerank = reordena resultados via LLM para melhorar relevância baseada na query", - }, - { - icon: "⚡", - title: "Auto (cheapest)", - description: "Auto (cheapest) = escolhe automaticamente o provider mais barato disponível e com credenciais configuradas", - }, + { icon: "🔍", key: "search", titleKey: "searchConceptTitle", descKey: "searchConceptDesc" }, + { icon: "📄", key: "scrape", titleKey: "scrapeConceptTitle", descKey: "scrapeConceptDesc" }, + { icon: "⚖", key: "compare", titleKey: "compareConceptTitle", descKey: "compareConceptDesc" }, + { icon: "↕", key: "rerank", titleKey: "rerankConceptTitle", descKey: "rerankConceptDesc" }, + { icon: "⚡", key: "auto", titleKey: "autoConceptTitle", descKey: "autoConceptDesc" }, ]; interface SearchConceptCardProps { @@ -42,6 +36,7 @@ interface SearchConceptCardProps { } export default function SearchConceptCard({ defaultCollapsed = false }: SearchConceptCardProps) { + const t = useTranslations("search"); const [collapsed, setCollapsed] = useState(defaultCollapsed); return ( @@ -58,7 +53,7 @@ export default function SearchConceptCard({ defaultCollapsed = false }: SearchCo > - Guia de modalidades + {t("modalitiesGuide")}