Mohit Sapat

Translation Widget

Component 2

About

Two-pane translator card: language pickers, animated swap, character count, loading skeleton and copy. RTL-aware, bring your own translation API.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Translation Widget to your project and fits it to your theme.

Code

How Translation Widget is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import * as React from "react";
import { TranslationWidget, type Locale } from "@/components/ui/translation-widget";

const locales: Locale[] = [
  { code: "en" },
  { code: "fr" },
  { code: "es" },
  { code: "de" },
  { code: "ja" },
  { code: "ar" },
];

const SOURCE = "Your order has shipped! Track your package anytime from the Orders page.";

// Stand-in for a real translation API, so the demo runs without keys.
const PHRASEBOOK: Record<string, string> = {
  en: SOURCE,
  fr: "Votre commande a été expédiée ! Suivez votre colis à tout moment depuis la page Commandes.",
  es: "¡Tu pedido ha sido enviado! Sigue tu paquete en cualquier momento desde la página Pedidos.",
  de: "Deine Bestellung wurde versandt! Verfolge dein Paket jederzeit auf der Seite Bestellungen.",
  ja: "ご注文の商品を発送しました!注文ページからいつでも荷物を追跡できます。",
  ar: "تم شحن طلبك! تتبّع طردك في أي وقت من صفحة الطلبات.",
};

function fakeTranslate(text: string, to: string): Promise<string> {
  const known = Object.values(PHRASEBOOK).includes(text);
  return new Promise((resolve) =>
    setTimeout(() => resolve(known ? PHRASEBOOK[to] : `[${to}] ${text}`), 700),
  );
}

export default function TranslationWidgetDemo() {
  const [sourceLocale, setSourceLocale] = React.useState("en");
  const [targetLocale, setTargetLocale] = React.useState("fr");
  const [sourceText, setSourceText] = React.useState(SOURCE);
  const [targetText, setTargetText] = React.useState(PHRASEBOOK.fr);
  const [loading, setLoading] = React.useState(false);
  const request = React.useRef(0);

  const translate = React.useCallback((text: string, to: string) => {
    const id = ++request.current;
    if (!text.trim()) {
      setTargetText("");
      setLoading(false);
      return;
    }
    setLoading(true);
    fakeTranslate(text, to).then((result) => {
      // Ignore responses that a newer edit has superseded.
      if (id !== request.current) return;
      setTargetText(result);
      setLoading(false);
    });
  }, []);

  return (
    <div className="flex min-h-[420px] w-full items-center justify-center bg-background p-4 sm:p-8">
      <TranslationWidget
        className="max-w-3xl"
        locales={locales}
        sourceLocale={sourceLocale}
        onSourceLocaleChange={setSourceLocale}
        targetLocale={targetLocale}
        onTargetLocaleChange={(code) => {
          setTargetLocale(code);
          translate(sourceText, code);
        }}
        sourceText={sourceText}
        onSourceChange={(text) => {
          setSourceText(text);
          translate(text, targetLocale);
        }}
        targetText={targetText}
        onTargetChange={setTargetText}
        onSwap={() => {
          // Swap already exchanged texts; cancel any in-flight request.
          request.current++;
          setLoading(false);
        }}
        loading={loading}
        maxLength={500}
      />
    </div>
  );
}

Dependencies

More by Mohit Sapat

View all
  • Phone Input

    Phone Input

    3
  • Language Switcher Pill

    Language Switcher Pill

    2
  • NavBar

    NavBar

    2

Explore more

Part of Babelize Elements — browse the full library on 21st.dev.