JavaScript | 1 日 120 分 × 7 日アプリ学習:API通信アプリ(LibreTranslate API)

APP JavaScript
スポンサーリンク

2日目のゴールと今日やること

2日目のテーマは
「翻訳アプリを“実用レベル”に近づけるために、API から言語一覧を取得し、UI とエラーハンドリングを強化する」
ことです。

1日目であなたはすでに、

  • テキストを入力して翻訳
  • fetch と async/await の基本
  • POST リクエストの書き方
  • ローディング表示
  • 通信失敗時の基本的な分岐

を習得しました。

2日目はここに、

  • LibreTranslate API から言語一覧を取得して自動生成
  • ローディング表示の改善
  • エラーハンドリングのパターン化
  • 入力チェック(バリデーション)
  • 「翻訳履歴」の基礎

を追加して、“毎日使える翻訳ツール”に近づけます。


言語一覧を API から取得する

なぜ自動取得が必要なのか

1日目では <select> に手書きで言語を入れていましたが、
実際の翻訳アプリは API から言語一覧を取得して UI を作る のが普通です。

理由はシンプルで、

  • API が対応言語を増やしたときに自動で反映される
  • コードの保守性が上がる
  • UI が常に最新の状態になる

というメリットがあるからです。

LibreTranslate の言語一覧 API

GET https://libretranslate.com/languages

返ってくる JSON はこういう形です。

[
  { "code": "en", "name": "English" },
  { "code": "ja", "name": "Japanese" },
  { "code": "es", "name": "Spanish" }
]

つまり、
「code を value に、name を表示名にして <option> を作る」
という流れになります。


言語一覧を取得して <select> を自動生成する

DOM 要素を取得

const sourceLang = document.getElementById("sourceLang");
const targetLang = document.getElementById("targetLang");
const statusDiv = document.getElementById("status");
JavaScript

言語一覧を取得する関数

async function loadLanguages() {
  statusDiv.textContent = "言語一覧を取得中です…";

  try {
    const response = await fetch("https://libretranslate.com/languages");

    if (!response.ok) {
      statusDiv.textContent = `言語一覧の取得に失敗しました。(${response.status})`;
      return;
    }

    const languages = await response.json();

    renderLanguageOptions(languages);

    statusDiv.textContent = "言語一覧の取得に成功しました。";

  } catch (error) {
    statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
    console.error(error);
  }
}
JavaScript

言語一覧を <select> に反映する

function renderLanguageOptions(languages) {
  sourceLang.innerHTML = "";
  targetLang.innerHTML = "";

  languages.forEach((lang) => {
    const option1 = document.createElement("option");
    option1.value = lang.code;
    option1.textContent = lang.name;

    const option2 = option1.cloneNode(true);

    sourceLang.appendChild(option1);
    targetLang.appendChild(option2);
  });

  sourceLang.value = "en";
  targetLang.value = "ja";
}
JavaScript

ページ読み込み時に実行

loadLanguages();
JavaScript

ローディング表示を改善する

「翻訳中」と「言語取得中」を区別する

1日目ではローディング表示が単一でしたが、
2日目では 状況に応じてメッセージを変える ことで、
ユーザーが「今何が起きているか」を理解しやすくします。

例:

  • 言語一覧取得中 → 「言語一覧を取得中です…」
  • 翻訳中 → 「翻訳中です…」

ボタン無効化も状況に応じて行う

function startLoading(message) {
  statusDiv.textContent = message;
  translateButton.disabled = true;
}

function endLoading() {
  translateButton.disabled = false;
}
JavaScript

翻訳処理のエラーハンドリングを強化する

1日目のエラー分岐を思い出す

  • ネットワークエラー
  • HTTP エラー
  • JSON の中身が想定外

2日目では、これを 「ユーザーに伝わる形」 に整えます。

改良版の翻訳関数

async function translateText() {
  const text = inputText.value.trim();
  const source = sourceLang.value;
  const target = targetLang.value;

  if (!text) {
    statusDiv.textContent = "翻訳する文章を入力してください。";
    resultDiv.textContent = "";
    return;
  }

  startLoading("翻訳中です…");
  resultDiv.textContent = "";

  try {
    const response = await fetch("https://libretranslate.com/translate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        q: text,
        source: source,
        target: target,
        format: "text"
      })
    });

    if (!response.ok) {
      statusDiv.textContent = `翻訳中にサーバーエラーが発生しました。(${response.status})`;
      return;
    }

    const data = await response.json();

    if (!data.translatedText) {
      statusDiv.textContent = "翻訳結果が取得できませんでした。";
      return;
    }

    statusDiv.textContent = "翻訳に成功しました。";
    resultDiv.textContent = data.translatedText;

    addHistory(text, data.translatedText, source, target);

  } catch (error) {
    statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
    console.error(error);

  } finally {
    endLoading();
  }
}
JavaScript

翻訳履歴の基礎を作る

履歴の構造を決める

翻訳履歴はこういう形にします。

  • 入力テキスト
  • 翻訳結果
  • 翻訳元言語
  • 翻訳先言語
const history = [];
const historyDiv = document.getElementById("history");
JavaScript

履歴を追加する

function addHistory(original, translated, source, target) {
  history.unshift({
    original,
    translated,
    source,
    target
  });

  if (history.length > 5) {
    history.pop();
  }

  renderHistory();
}
JavaScript

履歴を表示する

function renderHistory() {
  if (history.length === 0) {
    historyDiv.textContent = "翻訳履歴はまだありません。";
    return;
  }

  let html = "<h3>翻訳履歴</h3>";

  history.forEach((item) => {
    html += `
      <div class="history-item">
        <p><strong>${item.original}</strong></p>
        <p>→ ${item.translated}</p>
        <p>(${item.source}${item.target})</p>
      </div>
    `;
  });

  historyDiv.innerHTML = html;
}
JavaScript

2日目のまとめ

今日やったことを整理すると、こうなります。

  • 言語一覧を API から取得して <select> を自動生成
  • ローディング表示を状況に応じて変更
  • エラーハンドリングを「ユーザーに伝わる形」に改善
  • 翻訳履歴の基礎を実装
  • fetch / async/await の“型”をさらに強化

どれも新しい文法ではなく、
「API のレスポンスを理解して、UI に反映する」
という作業の積み重ねです。


今日いちばん深く理解してほしいこと

2日目の本質は、

「API のレスポンスを UI にどう反映するかがアプリの質を決める」
ということです。

言語一覧
翻訳結果
エラー内容
履歴

これらはすべて「API の結果」です。

fetch → async/await → JSON → UI
という流れを理解できれば、
どんな API でも扱えるようになります。

3日目では、
この翻訳アプリに「自動翻訳」「入力補完」「お気に入り翻訳」などを追加して、
さらに実用的なツールに育てていきます。

タイトルとURLをコピーしました