Babelize Elements
ウィジェット

翻訳ウィジェット

言語ピッカー、入れ替え、コピー、文字数カウント、ローディング状態を備えた並列表示の翻訳カードです。RTL に対応し、shadcn のトークンでテーマ設定されています。

72 / 500 characters

インストール

npx shadcn@latest add @babelize/translation-widget

使い方

"use client";

import { useState } from "react";
import { TranslationWidget } from "@/components/ui/translation-widget";

export function Translate() {
  const [text, setText] = useState("");

  return (
    <TranslationWidget
      sourceLocale="en"
      targetLocale="fr"
      sourceText="Hello, world!"
      targetText={text} // omit for uncontrolled
      onTargetChange={setText}
    />
  );
}

言語ピッカーと入れ替えボタンを使用する場合:

"use client";

import { useState } from "react";
import { TranslationWidget } from "@/components/ui/translation-widget";

const locales = [{ code: "en" }, { code: "fr" }, { code: "ar" }];

export function Translate() {
  const [source, setSource] = useState("Hello, world!");
  const [target, setTarget] = useState("Bonjour, le monde !");
  const [sourceLocale, setSourceLocale] = useState("en");
  const [targetLocale, setTargetLocale] = useState("fr");

  return (
    <TranslationWidget
      locales={locales}
      sourceLocale={sourceLocale}
      targetLocale={targetLocale}
      onSourceLocaleChange={setSourceLocale}
      onTargetLocaleChange={setTargetLocale}
      sourceText={source}
      onSourceChange={setSource}
      targetText={target}
      onTargetChange={setTarget}
      onSwap={() => console.log("swapped")}
    />
  );
}

Props

Prop型必須説明
localesLocale[]いいえ各ペインの言語ピッカーで使用するロケール。省略すると静的なチップを表示します
sourceLocalestringいいえ翻訳元ロケールの BCP 47 コード(例: "en")。制御する場合に指定します
defaultSourceLocalestringいいえ非制御時の初期翻訳元ロケール(デフォルト: locales[0]、なければ "en")
onSourceLocaleChange(code: string) => voidいいえ翻訳元ロケールが変更されたときのコールバック
targetLocalestringいいえ翻訳先ロケールの BCP 47 コード(例: "fr"、"ar")。制御する場合に指定します
defaultTargetLocalestringいいえ非制御時の初期翻訳先ロケール(デフォルト: locales[1]、なければ "en")
onTargetLocaleChange(code: string) => voidいいえ翻訳先ロケールが変更されたときのコールバック
sourceTextstringいいえ翻訳元テキスト。翻訳元ペインを制御する場合に指定します
defaultSourceTextstringいいえ非制御時の初期翻訳元テキスト
onSourceChange(text: string) => voidいいえ翻訳元テキストが変更されたときのコールバック。ペインを編集可能にします
targetTextstringいいえ翻訳先テキスト。翻訳先ペインを制御する場合に指定します(onTargetChange がない場合は読み取り専用)
defaultTargetTextstringいいえ非制御時の初期翻訳先テキスト
onTargetChange(text: string) => voidいいえ翻訳先テキストが変更されたときのコールバック
onSwap() => voidいいえ入れ替えボタンがロケールとテキストを交換した後に呼び出されます
sourcePlaceholderstringいいえ翻訳元ペインのプレースホルダー
targetPlaceholderstringいいえ翻訳先ペインのプレースホルダー
sourceLabelstringいいえ翻訳元ペインのアクセシブルラベル(デフォルト: "Source text")
targetLabelstringいいえ翻訳先ペインのアクセシブルラベル(デフォルト: "Target text")
readOnlySourcebooleanいいえonSourceChange が指定されていても翻訳元ペインを読み取り専用のままにします
showCountbooleanいいえ翻訳元の文字数を表示します(デフォルト: true)
maxLengthnumberいいえ両ペインの文字数上限。カウントには n / max と表示されます
showClearbooleanいいえ編集可能な翻訳元の下にクリアボタンを表示します(デフォルト: true)
showCopybooleanいいえ翻訳先の下にクリップボードへのコピーボタンを表示します(デフォルト: true)
onTargetCopy(text: string) => voidいいえ翻訳先テキストがコピーされた後に、そのテキストを引数として呼び出されます
labelsPartial<TranslationWidgetLabels>いいえ組み込みの英語 UI テキストを上書きします(下記参照)
loadingbooleanいいえ翻訳先ペインにスケルトンを表示し、aria-busy を設定します
classNamesTranslationWidgetClassNamesいいえパーツごとのクラス上書き: root、header、pane、textarea、footer、swap、chip
dir"ltr" | "rtl"いいえ自動検出されたレイアウトの方向を上書きします
classNamestringいいえ追加の CSS クラス

その他のprop(id、data-*、aria-*)はすべてルート要素に展開され、 refは同要素に転送されます。

編集可能な翻訳元

翻訳元ペインはデフォルトで読み取り専用です。編集可能にするには onSourceChange を渡してください。変更ハンドラーが存在する場合でも読み取り専用に固定するには readOnlySource を使用します:

<TranslationWidget
  sourceLocale="en"
  targetLocale="de"
  sourceText={source}
  onSourceChange={setSource}
  readOnlySource
/>

制御された翻訳先

翻訳先ペインは、非制御の場合(defaultTargetText)、または targetText と onTargetChange の両方を渡した場合に編集可能になります。ハンドラーのない制御された翻訳先は読み取り専用で描画されるため、入力されたテキストが黙って失われることはありません:

// Read-only: controlled with no way to report edits
<TranslationWidget sourceLocale="en" targetLocale="fr" targetText="Bonjour" />

// Editable
<TranslationWidget
  sourceLocale="en"
  targetLocale="fr"
  targetText={text}
  onTargetChange={setText}
/>

言語ピッカー

静的な言語チップの代わりに、各ペインに 言語スイッチャー を描画するには locales を渡します。各ピッカーはそのペインのロケールを更新し、テキストエリアの lang と dir もそれに追従します。そのため、翻訳先をアラビア語に切り替えると、そのペインは RTL に切り替わります:

<TranslationWidget
  locales={[{ code: "en" }, { code: "fr" }, { code: "ar" }]}
  defaultSourceLocale="en"
  defaultTargetLocale="fr"
  sourceText={source}
  onSourceChange={setSource}
/>

ロケールの状態は非制御(defaultSourceLocale / defaultTargetLocale)または制御(sourceLocale + onSourceLocaleChange、翻訳先も同様)が可能です。locales を省略すると、ウィジェットはコンパクトな静的チップを維持します。

入れ替え

ペイン間の入れ替えボタンは、ロケールとテキストの両方を交換します(翻訳元 ↔ 翻訳先)。そのうえで onSwap を発火します。ボタンは2つの言語ピッカーの間にあるヘッダーバーに配置され、入れ替えのたびにアイコンが半回転します。ボタンには aria-label="Swap languages" が付与されており、交換される値のいずれかが変更ハンドラーなしで制御されている場合は、入れ替えを適用できないため無効になります:

<TranslationWidget
  locales={locales}
  sourceLocale={sourceLocale}
  targetLocale={targetLocale}
  onSourceLocaleChange={setSourceLocale}
  onTargetLocaleChange={setTargetLocale}
  sourceText={source}
  onSourceChange={setSource}
  targetText={target}
  onTargetChange={setTarget}
  onSwap={() => analytics.track("languages-swapped")}
/>

ツールバー

各ペインの下部には小さなツールバーがあります。翻訳元には、リアルタイムの文字数カウントが表示されます(maxLength を渡した場合は n / max、上限に達すると警告色になります)。ペインが編集可能な場合はクリアボタンも表示されます。翻訳先にはコピーボタンがあり、チェックマークで完了を示し、スクリーンリーダーには「翻訳をクリップボードにコピーしました」と通知されます:

<TranslationWidget
  sourceLocale="en"
  targetLocale="fr"
  sourceText={source}
  onSourceChange={setSource}
  maxLength={500}
  onTargetCopy={(text) => analytics.track("translation-copied", { length: text.length })}
/>

これらのいずれも showCount={false}、showClear={false}、または showCopy={false} で無効にできます。

ローディング状態

翻訳の処理中は、loading を渡すと翻訳先テキストを点滅するスケルトンに置き換えられます。テキストエリアには aria-busy が設定され、コピーは無効になります:

const [loading, setLoading] = useState(false);

async function translate(text: string) {
  setLoading(true);
  setTarget(await api.translate(text, targetLocale));
  setLoading(false);
}

<TranslationWidget loading={loading} /* … */ />;

翻訳された UI テキスト

ペインのプレースホルダーとラベルには、それぞれ専用のprop(sourcePlaceholder、 sourceLabel など)があります。その他の組み込みテキスト(ツールバーのボタン、コピー時のアナウンス、文字数カウントの後に読み上げられる語、2つの言語ピッカー)は labels から取得されます。省略したキーは英語のデフォルトのままになります。

<TranslationWidget
  locales={locales}
  defaultSourceLocale="fr"
  defaultTargetLocale="en"
  onSourceChange={(text) => console.log(text)}
  sourcePlaceholder="Texte source"
  sourceLabel="Texte source"
  labels={{
    swap: "Inverser les langues",
    copy: "Copier la traduction",
    copied: "Copié",
    copiedStatus: "Traduction copiée dans le presse-papiers",
    clearSource: "Effacer le texte source",
    characters: (count) => (count === 1 ? "caractère" : "caractères"),
    languageSwitcher: {
      current: (name) => `Langue actuelle : ${name}`,
      searchPlaceholder: "Rechercher des langues…",
      emptyMessage: "Aucune langue trouvée.",
    },
  }}
/>
キーデフォルト用途
swap"Swap languages"入れ替えボタンのアクセシブルラベル
copy"Copy translation"コピーボタンのアクセシブルラベル
copied"Copied"コピー直後のコピーボタンのラベル
copiedStatus"Translation copied to clipboard"コピー後のスクリーンリーダーへの通知
clearSource"Clear source text"クリアボタンのアクセシブルなラベル
characters(count) => "characters"文字数カウントの後に読み上げられる語(件数に応じて複数形になります)
languageSwitcherLanguageSwitcher のデフォルト両方の言語ピッカー(Partial<LanguageSwitcherLabels>)

カスタマイズ

各パーツは classNames を通じて追加のクラスを受け取ります。これらは cn によってデフォルトの後にマージされるため、独自のクラスが優先されます:

<TranslationWidget
  classNames={{
    root: "rounded-2xl shadow-lg",
    header: "bg-muted/50",
    textarea: "font-serif text-lg",
    swap: "bg-primary text-primary-foreground",
  }}
/>

RTL対応

翻訳先ロケールが RTL の言語(ar、he、fa、ur、…)である場合、または RTL のスクリプトサブタグ(az-Arab)を含む場合、翻訳先ペインのテキストは右から左へ流れます。ペインの順序は変わりません。翻訳元が先、翻訳先が後のままで、各ペインは独自の dir と lang を持つため、LTR→RTL の混在したペアでも両側で正しく読めます。ウィジェットの方向を明示的に設定するには dir を渡してください。

<TranslationWidget sourceLocale="en" targetLocale="ar" sourceText="Hello, world!" />

機能

  • 並列のペイン — 翻訳元と翻訳先を1つのカードに収め、狭い画面では縦に積み重ねます
  • 言語ピッカー — ペインごとに言語スイッチャーを表示するには locales を渡します。それ以外の場合は静的なチップを表示します
  • 入れ替えボタン — ロケールとテキストの両方を交換します。onSwap とアクセシブルラベルを備えています
  • 編集可能な翻訳先 — 非制御、または targetText + onTargetChange による制御が可能です(ハンドラーのない制御された翻訳先は読み取り専用で描画されるため、入力されたテキストが黙って失われることはありません)
  • RTL対応 — ペインごとの dir と lang は各ロケールに従います。ペインの順序は固定されたままです
  • ツールバー — maxLength を含む文字数カウント、クリア、クリップボードへのコピー
  • ローディング状態 — aria-busy を持つスケルトンの翻訳先ペイン
  • アクセシブル — ラベル付きのテキストエリア、lang 属性、見えるフォーカスリング、コピー時のライブ通知
  • キーボード対応 — Tab でペイン間を移動し、入力するだけで翻訳されます
  • テーマ対応 — shadcn のトークン(bg-card、border、muted、ring)を使用するため、お使いのテーマやダークモードに追従します
  • カスタマイズ可能 — すべてのパーツに対して classNames が使えます
  • モーション — バネ状の入れ替えアイコンとコピー確認。モーション低減の設定に対応しています
  • 制御/非制御 — targetText の有無にかかわらず動作します。ロケールも同様です

デザイン

1枚のカードが、翻訳元ペインと、わずかに色味のついた翻訳先ペインに分かれています。2つの言語ピッカーと入れ替えボタンは共通のヘッダーバーに配置されており、翻訳ツールでおなじみのレイアウトです。色は shadcn のセマンティックトークンから取得されるため、追加の設定なしでウィジェットがお使いのテーマ、角丸、ダークモードに適用されます。

依存関係

このコンポーネントは次を使用しています:

  • clsx と tailwind-merge(dependencies としてバンドル)
  • アイコンには lucide-react、入れ替えとコピーのアニメーションには motion を使用します
  • 言語スイッチャー コンポーネント(レジストリの依存関係を通じて自動的に取り込まれます)

peerDependencies:

  • react >= 18
  • react-dom >= 18
  • tailwindcss >= 3

このページの内容