MediaPipe言語検出入門:日本語・英語の文章を判定する
文章を見て「何語で書かれていそうか」を推定するのがLanguageDetectorです。日本語と英語の自作の例文を使い、言語コードとスコアを読みます。画像のCanvasは必要ありませんが、SDK・WASM・モデルを準備し、入力を推論して結果を表示する流れは、画像編と同じです。
1. この記事のゴールと使用する版
文字列をdetect()へ渡して、言語候補を画面に表示できること。翻訳や文章の内容判定とは違う処理だと理解し、短文・混在文・空欄の扱いを考えられることを目指します。
前提はHTMLの要素、JavaScriptの変数・関数・イベント、async/awaitの基本です。モデルを自分で学習させる知識は不要です。CDN版のSDKを使用するため、このサンプルへMediaPipeをnpmで追加インストールする必要はありません。SDKとWASMは同じパッケージ・同じ版に固定します。
教材の構成:@mediapipe/tasks-text 1.1.0。モデルはlanguage_detector、float32、モデル版1。2026年10月12日にnpm公式レジストリでtasks-textの最新版を確認しました。画像用tasks-visionとは版を別々に管理します。
このサイトのAI画像ツールで使うSDKと配信方法、旧Solutionsとの違いは概要記事の現在のサイト構成に整理しています。音声・テキスト編は追加の学習教材です。今回のモデルは公式ストレージの版が分かるURLを指定し、コードの先頭で確認できます。
2. サンプルを試し、自分のPCで起動する
VS Codeでフォルダーを開く方法や、app.jsの編集・保存が初めてなら、導入記事のエディター準備と「保存 → ブラウザ更新 → 実行」の手順を先に確認してください。下では、このサンプルの起動に必要な操作を示します。
最初の日本語例文で「文章の言語を推定する」を押します。言語コードとスコア、JSONを確認し、英語へ切り替えて同じ操作をします。混在文と短い文字列も試した後、入力をすべて消して実行し、サンプル側が空欄を止めることを確認してください。

日本語文と混在文で確認できたこと
「今日は愛猫の写真を使って、ブラウザで画像処理を試します。」はja、0.99878でした。一方、「ブラウザで写真を編集します。This tool works on my phone.」という混在文も、返った候補はja、0.97356でした。

全文に対する候補を返すので、これを「各部分の言語を判定して注釈した結果」とは読めません。混在を扱うアプリでは、1位の言語で全文を分類してよいかを要件として考える必要があります。
ZIPを展開して、HTMLを直接開かずHTTPで起動する
- ZIPを保存し、エクスプローラーの「すべて展開」で中身を展開します。ZIPの中を閲覧している状態で実行しません。
index.htmlとapp.jsが見えるフォルダーで右クリックし、ターミナルを開きます。README.txtにも手順があります。- 下の最初の2行を1行ずつ実行します。バージョンが表示されればNode.jsとnpmを利用できます。「見つからない」ならNode.js公式サイトのLTS版を導入し、ターミナルを開き直します。
- 3行目を実行します。
http-serverの初回取得には通信が必要です。サーバーが起動したらターミナルは開いたままにします。 - ブラウザのアドレス欄へ
http://localhost:8080/を入力します。サンプルが表示されたら上の操作を試します。 - 編集後は保存してブラウザを再読み込みします。終了はターミナルでCtrl+Cです。
node --version
npm.cmd --version
npx.cmd --yes http-server . -p 8080 Windows PowerShellではnpm.cmdとnpx.cmdを使うと、スクリプト実行ポリシーによるつまずきを避けやすくなります。macOS・Linuxはnpm・npxで実行します。8080が使用中なら-p 8081へ変え、ブラウザ側もhttp://localhost:8081/に合わせます。
初回のボタン操作でSDK・WASM・モデルを取得するので、しばらく待つ場合があります。HTTPサーバーはHTMLとJavaScriptを配信するためのもので、AI推論をサーバー側で行う構成ではありません。file://での直接起動は使いません。基本操作の確認はPCのブラウザで行い、スマホは画面幅の確認と実機での動作検証を区別します。
3. テキスト用SDKとモデルを準備する
画像用のFilesetResolver.forVisionTasks()ではなく、forTextTasks()を使います。CDNから読み込むファイルもtext_bundle.mjsです。JavaScriptとWASMは、同じtasks-text 1.1.0へ固定します。パッケージが違えば、同じ数字のバージョンが存在するとは限りません。
const { LanguageDetector, FilesetResolver } = await import(`${BASE}/text_bundle.mjs`);
const files = await FilesetResolver.forTextTasks(`${BASE}/wasm`);
const detector = await LanguageDetector.createFromOptions(files, {
baseOptions: { modelAssetPath: MODEL },
maxResults: 5,
});LanguageDetector.createFromOptions()は初回だけ実行し、その後の例文変更でも同じ検出器を使います。入力の文字列を変えるたびにモデルを再取得する必要はありません。終わりにclose()で検出器を解放する点も、画像編と共通です。
このモデルは入力文字列の言語を推定するためのものです。文章を別の言語へ翻訳したり、正しい文法へ修正したりはしません。「英語だったら英語向けの画面へ案内する」といったアプリの分岐に使う場合も、候補が曖昧なときに利用者が選び直せる設計を用意すると扱いやすくなります。
4. 入力は文字列。空欄はアプリ側で止める
入力は通常のJavaScriptの文字列です。画像の読み込み待ちやCanvasへの描画はありません。テキストエリアのvalueを読み、前後の空白をtrim()で除いてからdetect()へ渡します。トークン化などのモデル入力処理はタスクが担当します。
const text = input.value.trim();
if (!text) {
status.textContent = "文章を入力してください。";
return;
}
const result = detector.detect(text);空欄への対応はモデル任せにせず、「文章を入力してください」と画面に表示します。これはモデルが返したエラーではなく、サンプルの入力チェックです。空欄になったときには前回の候補も消し、古い推定を現在の文章の結果と取り違えないようにしています。
サンプルは入力欄を2,000文字までにしています。この値は教材の使いやすさのための上限で、すべてのモデルに共通の上限ではありません。記事やファイル全体を渡す機能へ広げるなら、長さ・分割方法・実行回数を別に考えます。利用者が文章を書いている途中に毎キー入力で推論するより、最初は明確な実行ボタンにすると処理の流れを追いやすくなります。
5. languageCodeとprobabilityを読む
結果のlanguagesには候補が入り、各候補にはlanguageCodeとprobabilityがあります。jaなら日本語、enなら英語といったコードです。画面では上位5候補までを数値とメーターで表示し、JSONにはAPIから返った構造を残します。
for (const candidate of result.languages) {
console.log(candidate.languageCode, candidate.probability);
}候補名は文章の言語であって、書いた人の国籍、居住地、母語を表すものではありません。英語の文を書いた日本語話者に対しても、入力文から英語を推定する処理です。結果を人の属性の推定へ置き換えないようにします。
スコアはモデルの予測です。ここでは高い候補を見やすく表示していますが、判定の確実さを保証するものではありません。実用画面の切り替えに使うなら、対象の文章を集めて確認し、採用する条件を決めます。maxResultsを1へ変えると候補を絞れますが、モデルの理解力が上がる設定ではありません。
6. 短文と混在文で限界を確かめる
「OK」のような短い文字列は、複数の言語で使われます。モデルが何かを返したとしても、十分な情報があるとは限りません。入力を長めの自作文へ変え、結果の変わり方を観察してみてください。コードの導入失敗と、入力だけでは判断しづらい状態を分ける練習になります。
日本語と英語の混在例文では、どちらの候補が高くなるかを見ます。このタスクは文字ごとに言語を塗り分けるサンプルではありません。文章全体へ候補を返しているため、1位の言語だけで全文を同じ言語だと扱うのは、要件によっては不十分です。段落ごとに推定する場合も、短く分けすぎると情報量が減ります。
URL、数字、固有名詞、絵文字を多く含む文でも試せます。元の言語に関する手がかりが少ないほど、結果の採用方法を慎重に設計します。推定に失敗したときの手動選択や、候補を表示して利用者へ確認する形を、最初から画面へ組み込むとよいでしょう。
このサイトで応用を考えるなら、入力テキストに応じて説明文の言語を提案する、といった機能が候補になります。これは提案で、現在の画像AIツールへ実装済みではありません。サンプルの推論はブラウザ内で行い、テキストを外部へ送るコードはありません。ただしCDNとモデルの取得には通信します。検証は掲載例文と作成環境のブラウザでの動作確認までです。
7. 完全コードを確認する
以下はダウンロードZIPと同じソースです。index.htmlが画面と操作ボタン、app.jsがSDKの初期化・入力の準備・推論・出力表示を担当します。コードを途中だけコピーすると必要な要素が欠けるので、最初はZIPの完全版から起動してください。慣れたら設定を1つずつ変更し、JSONと画面の変化を見ます。
index.html:画面の完全コード
<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>MediaPipeで文章の言語を推定する | 猫の手ツール</title><style>:root{font-family:system-ui,sans-serif;color:#17243c;background:#eef2f8}*{box-sizing:border-box}body{margin:0}main{max-width:1080px;margin:auto;padding:28px 20px}h1{font-size:clamp(1.7rem,5vw,2.4rem)}p{line-height:1.8}button,input,select,textarea{font:inherit}button{background:#4338ca;color:white;border:0;border-radius:8px;padding:12px 18px;cursor:pointer}button:disabled{opacity:.55;cursor:wait}label{display:block;font-weight:650;margin:12px 0 6px}textarea{width:100%;min-height:100px;padding:12px;border:1px solid #aab7ce;border-radius:8px}select,input[type=file]{max-width:100%;padding:8px}section,.box{background:white;border:1px solid #dbe1eb;border-radius:14px;padding:18px;margin:18px 0}.controls{display:flex;flex-wrap:wrap;gap:12px;align-items:center}#status{border-left:4px solid #4338ca;padding:14px;background:white;min-height:54px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}canvas,video{max-width:100%;height:auto;display:block;border-radius:8px;background:#14243b}table{border-collapse:collapse;width:100%;font-size:.9rem}th,td{text-align:left;border-bottom:1px solid #dbe1eb;padding:10px}pre{background:#12203a;color:#e0e8ff;padding:16px;border-radius:8px;white-space:pre-wrap;overflow-wrap:anywhere;max-height:420px;overflow:auto}.scroll{overflow:auto}meter{width:160px;max-width:100%}a{color:#4338ca}small{color:#53637d}button:focus-visible,a:focus-visible{outline:3px solid #e99a23;outline-offset:3px}@media(max-width:640px){.grid{grid-template-columns:1fr}main{padding:18px 12px}section{padding:14px}}</style></head><body><main><p><a href="./README.txt">起動手順を開く</a></p><h1>MediaPipeで文章の言語を推定する</h1><p>自作の日本語・英語の短文を使い、言語コードとモデルのスコアを確認します。翻訳や話者の国籍の判定は行いません。</p><section><h2>実行する</h2><label for="sample">例文を選ぶ</label><select id="sample"><option value="ja">日本語</option><option value="en">英語</option><option value="mixed">日本語と英語の混在</option><option value="short">短い文字列</option></select><label for="text-input">推定する文章</label><textarea id="text-input" maxlength="2000"></textarea><button id="run-button">文章の言語を推定する</button></section><p id="status" role="status" aria-live="polite">準備できました。ボタンを押すとSDK・モデルを読み込みます。</p><section><h2>言語候補</h2><table><thead><tr><th>言語コード</th><th>スコア</th><th>目安</th></tr></thead><tbody id="results-body"></tbody></table></section><section><h2>実際の出力JSON・検証条件</h2><pre id="json-output">まだ実行していません。</pre></section><p><small>推論はブラウザ内で行います。SDK・モデル取得のためCDN / Googleの公開ストレージへ接続します。入力データを送信する処理はありません。記事の検証日:2026年10月12日。</small></p></main><script type="module" src="./app.js"></script></body></html>app.js:処理の完全コード
const VERSION = '1.1.0';
const BASE = `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-text@${VERSION}`;
const MODEL = 'https://storage.googleapis.com/mediapipe-models/language_detector/language_detector/float32/1/language_detector.tflite';
const examples = { ja: '今日は愛猫の写真を使って、ブラウザで画像処理を試します。',
en: 'Today I will use a photo of my cat to try image processing in the browser.',
mixed: 'ブラウザで写真を編集します。This tool works on my phone.', short: 'OK' };
const select = document.querySelector('#sample');
const input = document.querySelector('#text-input');
const button = document.querySelector('#run-button');
const status = document.querySelector('#status');
const body = document.querySelector('#results-body');
const json = document.querySelector('#json-output');
let detector;
function selectExample() { input.value = examples[select.value]; }
select.addEventListener('change', selectExample); selectExample();
button.addEventListener('click', async () => {
const text = input.value.trim();
if (!text) { status.textContent = '文章を入力してください。空欄では推論しません。'; body.replaceChildren(); json.textContent = '入力が空のため未実行。'; return; }
button.disabled = input.disabled = select.disabled = true;
try {
if (!detector) {
status.textContent = 'テキスト用SDK・WASM・言語検出モデルを準備しています。';
const { LanguageDetector, FilesetResolver } = await import(`${BASE}/text_bundle.mjs`);
const files = await FilesetResolver.forTextTasks(`${BASE}/wasm`);
detector = await LanguageDetector.createFromOptions(files, { baseOptions: { modelAssetPath: MODEL }, maxResults: 5 });
}
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const start = performance.now(); const result = detector.detect(text); const elapsedMs = performance.now() - start;
body.replaceChildren();
for (const prediction of result.languages) {
const row = body.insertRow(); row.insertCell().textContent = prediction.languageCode;
row.insertCell().textContent = prediction.probability.toFixed(5);
const meter = document.createElement('meter'); meter.min = 0; meter.max = 1; meter.value = prediction.probability;
row.insertCell().append(meter);
}
json.textContent = JSON.stringify({ sdkVersion: VERSION, model: MODEL, inputText: text,
elapsedMs, userAgent: navigator.userAgent, result }, null, 2);
status.textContent = `推定完了:${result.languages.length} 候補、${elapsedMs.toFixed(2)} ms。短文・混在文では解釈に注意してください。`;
} catch (error) { console.error(error); status.textContent = `実行できませんでした:${error.message}。HTTP起動・通信を確認してください。`; }
finally { button.disabled = input.disabled = select.disabled = false; }
});
window.addEventListener('pagehide', () => { detector?.close(); detector = null; });
画面には入力文やモデルのラベルをtextContentで表示します。文字列をHTMLとして実行する必要はありません。ボタンを処理中に無効にするのは、初期化や推論を重ねて実行しないためです。推論が同期処理のタスクでは、async関数から呼んでも自動的に別スレッドにはなりません。
8. 困ったときの確認表
| 症状 | 確認 |
|---|---|
| 入力しても以前の候補が見える | ボタン実行のタイミングを確認。実行するまでは前回の結果です。 |
| 短文の推定が予想と違う | 情報量を増やして再確認。短文だけの正解保証はしません。 |
| 混在文に1位の言語しか出ない | 全文の候補です。文字ごとの言語分割ではありません。 |
| tasks-visionの初期化を使っている | tasks-textのSDK、text_bundle.mjs、forTextTasks()へ揃えます。 |
| SDK・モデルを読み込めない | HTTP起動か、通信できるか、固定URLと版が揃っているか確認します。開発者ツールのConsoleとNetworkで、最初のエラーと失敗した取得先を見ます。 |
問い合わせや自分のメモでは「動かない」だけでなく、使用OS・ブラウザ、選んだ入力、画面の状態、最初のエラー、SDK・モデルの版を残すと切り分けやすくなります。入力内容や私的なファイル名を公開する必要はありません。この記事のキャプチャは実行したサンプルの観察で、すべての端末・入力への品質を保証するものではありません。
参考資料
資料確認日:2026年10月12日。SDKとモデルは別々に更新されます。更新する際は固定した版のサンプルを残し、変更後の入力・結果・実行条件を比べてください。