diff --git a/src/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram.tsx b/src/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram.tsx
new file mode 100644
index 0000000000..d9e144ff29
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram.tsx
@@ -0,0 +1,122 @@
+"use client";
+
+import { useCallback, type ReactNode } from "react";
+import { useTranslations } from "next-intl";
+import Tooltip from "@/shared/components/Tooltip";
+
+interface FlowNodeProps {
+ icon: string;
+ color: "primary" | "orange" | "blue" | "emerald" | "amber" | "purple" | "cyan" | "pink";
+ title: string;
+ example: string;
+ tooltipContent?: string;
+}
+
+const COLOR_MAP: Record<
+ FlowNodeProps["color"],
+ { border: string; bg: string; text: string }
+> = {
+ primary: { border: "border-primary/30", bg: "bg-primary/5", text: "text-primary" },
+ orange: { border: "border-orange-500/30", bg: "bg-orange-500/5", text: "text-orange-500" },
+ blue: { border: "border-blue-500/30", bg: "bg-blue-500/5", text: "text-blue-500" },
+ emerald: {
+ border: "border-emerald-500/30",
+ bg: "bg-emerald-500/5",
+ text: "text-emerald-500",
+ },
+ amber: { border: "border-amber-500/30", bg: "bg-amber-500/5", text: "text-amber-500" },
+ purple: {
+ border: "border-purple-500/30",
+ bg: "bg-purple-500/5",
+ text: "text-purple-500",
+ },
+ cyan: { border: "border-cyan-500/30", bg: "bg-cyan-500/5", text: "text-cyan-500" },
+ pink: { border: "border-pink-500/30", bg: "bg-pink-500/5", text: "text-pink-500" },
+};
+
+function FlowNode({ icon, color, title, example, tooltipContent }: FlowNodeProps) {
+ const c = COLOR_MAP[color];
+ const node: ReactNode = (
+
+
+ {icon}
+
+
{title}
+
{example}
+
+ );
+
+ return tooltipContent ? (
+
+ {node}
+
+ ) : (
+ node
+ );
+}
+
+function FlowArrow({ label }: { label?: string }) {
+ return (
+
+
+ arrow_forward
+
+ {label && (
+ {label}
+ )}
+
+ );
+}
+
+export default function TranslateFlowDiagram() {
+ const t = useTranslations("translator");
+ const tr = useCallback(
+ (key: string, fallback: string) => {
+ try {
+ const translated = t(key);
+ return translated === key || translated === `translator.${key}` ? fallback : translated;
+ } catch {
+ return fallback;
+ }
+ },
+ [t],
+ );
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard.tsx b/src/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard.tsx
new file mode 100644
index 0000000000..c612788e2e
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard.tsx
@@ -0,0 +1,79 @@
+"use client";
+
+import { useCallback, useState } from "react";
+import { useTranslations } from "next-intl";
+import { Card } from "@/shared/components";
+import TranslateFlowDiagram from "./TranslateFlowDiagram";
+
+export default function TranslatorConceptCard() {
+ const t = useTranslations("translator");
+ const [open, setOpen] = useState(false);
+
+ const tr = useCallback(
+ (key: string, fallback: string) => {
+ try {
+ const translated = t(key);
+ return translated === key || translated === `translator.${key}` ? fallback : translated;
+ } catch {
+ return fallback;
+ }
+ },
+ [t],
+ );
+
+ return (
+
+
+
+
+ info
+
+
+
+ {tr(
+ "conceptHeadline",
+ 'Sua app fala o "idioma" de uma API. O Translator converte para usar outro provider.',
+ )}
+
+
+ {tr(
+ "friendlySubtitle",
+ "Use sua app existente com qualquer provider — sem reescrever código.",
+ )}
+
+
+
+
+
+
+
setOpen((v) => !v)}
+ aria-expanded={open}
+ aria-controls="translator-concept-how-it-works"
+ className="flex items-center gap-2 text-xs font-medium text-primary hover:text-primary/80 transition-colors w-full justify-start py-1 rounded"
+ >
+ {tr("conceptHowItWorksToggle", "Como funciona")}
+
+ {open ? "expand_less" : "expand_more"}
+
+
+
+ {open && (
+
+ {tr(
+ "conceptHowItWorksBody",
+ "Sua app envia um pedido no formato dela. O Translator detecta o formato, converte via OpenAI como hub intermediário (ou direto, quando há tradutor direto disponível), envia ao provider escolhido e devolve a resposta convertida de volta no formato da sua app.",
+ )}
+
+ )}
+
+
+ );
+}
diff --git a/tests/unit/translator-friendly-concept-card.test.tsx b/tests/unit/translator-friendly-concept-card.test.tsx
new file mode 100644
index 0000000000..44e1f5fc0d
--- /dev/null
+++ b/tests/unit/translator-friendly-concept-card.test.tsx
@@ -0,0 +1,294 @@
+// @vitest-environment jsdom
+import React from "react";
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+// Minimal i18n stub — returns the key so tests can assert on fallback rendering
+vi.mock("next-intl", () => ({
+ useTranslations: () => (key: string) => key,
+}));
+
+// Minimal shared component stubs — Card wraps children, Tooltip passes through
+vi.mock("@/shared/components", () => ({
+ Card: ({
+ children,
+ className,
+ }: {
+ children: React.ReactNode;
+ className?: string;
+ }) => {children}
,
+}));
+
+vi.mock("@/shared/components/Tooltip", () => ({
+ default: ({ children }: { children: React.ReactNode }) => <>{children}>,
+}));
+
+const cleanupCallbacks: Array<() => void> = [];
+
+function makeContainer(): HTMLElement {
+ const container = document.createElement("div");
+ document.body.appendChild(container);
+ cleanupCallbacks.push(() => {
+ container.remove();
+ });
+ return container;
+}
+
+describe("TranslatorConceptCard", () => {
+ beforeEach(() => {
+ (
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+ ).IS_REACT_ACT_ENVIRONMENT = true;
+ });
+
+ afterEach(() => {
+ while (cleanupCallbacks.length > 0) {
+ cleanupCallbacks.pop()?.();
+ }
+ document.body.innerHTML = "";
+ });
+
+ it("exports a default function component", async () => {
+ const mod = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ expect(typeof mod.default).toBe("function");
+ });
+
+ it("renders the card with info icon and headline", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ // Card should be in the DOM
+ expect(container.querySelector("[data-testid='card']")).toBeTruthy();
+ // Info icon should be present
+ const icons = container.querySelectorAll(".material-symbols-outlined");
+ const iconTexts = Array.from(icons).map((el) => el.textContent?.trim());
+ expect(iconTexts).toContain("info");
+ });
+
+ it("renders the flow diagram with 3 FlowNode elements (app, source, target)", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ // The diagram grid should contain 3 node icons (smart_toy, psychology, auto_awesome)
+ const icons = container.querySelectorAll(".material-symbols-outlined");
+ const iconTexts = Array.from(icons).map((el) => el.textContent?.trim());
+ expect(iconTexts).toContain("smart_toy");
+ expect(iconTexts).toContain("psychology");
+ expect(iconTexts).toContain("auto_awesome");
+ });
+
+ it("renders the diagram with arrow_forward separators", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ const icons = container.querySelectorAll(".material-symbols-outlined");
+ const iconTexts = Array.from(icons).map((el) => el.textContent?.trim());
+ // Two arrows between 3 nodes
+ expect(iconTexts.filter((t) => t === "arrow_forward").length).toBeGreaterThanOrEqual(2);
+ });
+
+ it("toggle button starts collapsed (aria-expanded=false)", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ const toggleBtn = container.querySelector(
+ "button[aria-controls='translator-concept-how-it-works']",
+ );
+ expect(toggleBtn).toBeTruthy();
+ expect(toggleBtn?.getAttribute("aria-expanded")).toBe("false");
+ // Collapsed panel should not be in the DOM yet
+ expect(container.querySelector("#translator-concept-how-it-works")).toBeNull();
+ });
+
+ it("toggle expands 'Como funciona' section and sets aria-expanded=true", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ const toggleBtn = container.querySelector(
+ "button[aria-controls='translator-concept-how-it-works']",
+ ) as HTMLButtonElement | null;
+ expect(toggleBtn).toBeTruthy();
+
+ // Click to expand
+ await act(async () => {
+ toggleBtn?.click();
+ });
+
+ expect(toggleBtn?.getAttribute("aria-expanded")).toBe("true");
+ const panel = container.querySelector("#translator-concept-how-it-works");
+ expect(panel).toBeTruthy();
+ });
+
+ it("toggle collapses section on second click and restores aria-expanded=false", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ const toggleBtn = container.querySelector(
+ "button[aria-controls='translator-concept-how-it-works']",
+ ) as HTMLButtonElement | null;
+
+ // Expand
+ await act(async () => {
+ toggleBtn?.click();
+ });
+ expect(toggleBtn?.getAttribute("aria-expanded")).toBe("true");
+
+ // Collapse
+ await act(async () => {
+ toggleBtn?.click();
+ });
+ expect(toggleBtn?.getAttribute("aria-expanded")).toBe("false");
+ expect(container.querySelector("#translator-concept-how-it-works")).toBeNull();
+ });
+
+ it("toggle button icon changes between expand_more and expand_less", async () => {
+ const { default: TranslatorConceptCard } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslatorConceptCard"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+
+ const toggleBtn = container.querySelector(
+ "button[aria-controls='translator-concept-how-it-works']",
+ ) as HTMLButtonElement | null;
+
+ // Initially collapsed: should show expand_more
+ const btnIcons = toggleBtn?.querySelectorAll(".material-symbols-outlined");
+ const btnIconTexts = Array.from(btnIcons ?? []).map((el) => el.textContent?.trim());
+ expect(btnIconTexts).toContain("expand_more");
+ expect(btnIconTexts).not.toContain("expand_less");
+
+ // After click: should show expand_less
+ await act(async () => {
+ toggleBtn?.click();
+ });
+ const btnIconsAfter = toggleBtn?.querySelectorAll(".material-symbols-outlined");
+ const btnIconTextsAfter = Array.from(btnIconsAfter ?? []).map((el) => el.textContent?.trim());
+ expect(btnIconTextsAfter).toContain("expand_less");
+ expect(btnIconTextsAfter).not.toContain("expand_more");
+ });
+});
+
+describe("TranslateFlowDiagram", () => {
+ beforeEach(() => {
+ (
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+ ).IS_REACT_ACT_ENVIRONMENT = true;
+ });
+
+ afterEach(() => {
+ while (cleanupCallbacks.length > 0) {
+ cleanupCallbacks.pop()?.();
+ }
+ document.body.innerHTML = "";
+ });
+
+ it("exports a default function component", async () => {
+ const mod = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"
+ );
+ expect(typeof mod.default).toBe("function");
+ });
+
+ it("renders all 3 flow node icons", async () => {
+ const { default: TranslateFlowDiagram } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ const icons = container.querySelectorAll(".material-symbols-outlined");
+ const iconTexts = Array.from(icons).map((el) => el.textContent?.trim());
+ expect(iconTexts).toContain("smart_toy");
+ expect(iconTexts).toContain("psychology");
+ expect(iconTexts).toContain("auto_awesome");
+ });
+
+ it("renders exactly 2 arrow_forward separators between nodes", async () => {
+ const { default: TranslateFlowDiagram } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ const icons = container.querySelectorAll(".material-symbols-outlined");
+ const iconTexts = Array.from(icons).map((el) => el.textContent?.trim());
+ expect(iconTexts.filter((t) => t === "arrow_forward")).toHaveLength(2);
+ });
+
+ it("renders a responsive grid container", async () => {
+ const { default: TranslateFlowDiagram } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ // The grid wrapper should have grid class and responsive columns
+ const grid = container.querySelector(".grid");
+ expect(grid).toBeTruthy();
+ // Responsive class for sm breakpoint
+ expect(grid?.className).toContain("sm:grid-cols-");
+ });
+
+ it("i18n fallback: renders labels using fallback strings when translations return keys", async () => {
+ const { default: TranslateFlowDiagram } = await import(
+ "@/app/(dashboard)/dashboard/translator/components/TranslateFlowDiagram"
+ );
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render( );
+ });
+ // When mock returns key, tr() detects key === translation and uses fallback
+ // The fallback text should appear in the DOM
+ const text = container.textContent ?? "";
+ expect(text).toContain("Sua app");
+ expect(text).toContain("ex: SDK Anthropic");
+ expect(text).toContain("Formato origem");
+ expect(text).toContain("claude");
+ expect(text).toContain("Provider destino");
+ expect(text).toContain("Gemini");
+ });
+});