翻訳ウィジェット
言語ピッカー、入れ替え、コピー、文字数カウント、ローディング状態を備えた並列表示の翻訳カードです。RTL に対応し、shadcn のトークンでテーマ設定されています。
インストール
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 | 型 | 必須 | 説明 |
|---|---|---|---|
locales | Locale[] | いいえ | 各ペインの言語ピッカーで使用するロケール。省略すると静的なチップを表示します |
sourceLocale | string | いいえ | 翻訳元ロケールの BCP 47 コード(例: "en")。制御する場合に指定します |
defaultSourceLocale | string | いいえ | 非制御時の初期翻訳元ロケール(デフォルト: locales[0]、なければ "en") |
onSourceLocaleChange | (code: string) => void | いいえ | 翻訳元ロケールが変更されたときのコールバック |
targetLocale | string | いいえ | 翻訳先ロケールの BCP 47 コード(例: "fr"、"ar")。制御する場合に指定します |
defaultTargetLocale | string | いいえ | 非制御時の初期翻訳先ロケール(デフォルト: locales[1]、なければ "en") |
onTargetLocaleChange | (code: string) => void | いいえ | 翻訳先ロケールが変更されたときのコールバック |
sourceText | string | いいえ | 翻訳元テキスト。翻訳元ペインを制御する場合に指定します |
defaultSourceText | string | いいえ | 非制御時の初期翻訳元テキスト |
onSourceChange | (text: string) => void | いいえ | 翻訳元テキストが変更されたときのコールバック。ペインを編集可能にします |
targetText | string | いいえ | 翻訳先テキスト。翻訳先ペインを制御する場合に指定します(onTargetChange がない場合は読み取り専用) |
defaultTargetText | string | いいえ | 非制御時の初期翻訳先テキスト |
onTargetChange | (text: string) => void | いいえ | 翻訳先テキストが変更されたときのコールバック |
onSwap | () => void | いいえ | 入れ替えボタンがロケールとテキストを交換した後に呼び出されます |
sourcePlaceholder | string | いいえ | 翻訳元ペインのプレースホルダー |
targetPlaceholder | string | いいえ | 翻訳先ペインのプレースホルダー |
sourceLabel | string | いいえ | 翻訳元ペインのアクセシブルラベル(デフォルト: "Source text") |
targetLabel | string | いいえ | 翻訳先ペインのアクセシブルラベル(デフォルト: "Target text") |
readOnlySource | boolean | いいえ | onSourceChange が指定されていても翻訳元ペインを読み取り専用のままにします |
showCount | boolean | いいえ | 翻訳元の文字数を表示します(デフォルト: true) |
maxLength | number | いいえ | 両ペインの文字数上限。カウントには n / max と表示されます |
showClear | boolean | いいえ | 編集可能な翻訳元の下にクリアボタンを表示します(デフォルト: true) |
showCopy | boolean | いいえ | 翻訳先の下にクリップボードへのコピーボタンを表示します(デフォルト: true) |
onTargetCopy | (text: string) => void | いいえ | 翻訳先テキストがコピーされた後に、そのテキストを引数として呼び出されます |
labels | Partial<TranslationWidgetLabels> | いいえ | 組み込みの英語 UI テキストを上書きします(下記参照) |
loading | boolean | いいえ | 翻訳先ペインにスケルトンを表示し、aria-busy を設定します |
classNames | TranslationWidgetClassNames | いいえ | パーツごとのクラス上書き: root、header、pane、textarea、footer、swap、chip |
dir | "ltr" | "rtl" | いいえ | 自動検出されたレイアウトの方向を上書きします |
className | string | いいえ | 追加の 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" | 文字数カウントの後に読み上げられる語(件数に応じて複数形になります) |
languageSwitcher | LanguageSwitcher のデフォルト | 両方の言語ピッカー(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>= 18react-dom>= 18tailwindcss>= 3