3日目のゴールと今日やること
3日目のテーマは
「翻訳アプリを“使いやすく・賢く”するために、入力補助・自動翻訳・履歴の強化を行う」
ことです。
技術的な柱はいつも通りこの 3 つです。
- fetch
- Promise / async-await
- エラーハンドリング
ただし今日は、これらを「アプリとしての使い勝手」に結びつけます。
具体的には次の機能を追加します。
- 入力中に自動翻訳(一定時間入力が止まったら翻訳)
- 翻訳履歴の強化(クリックで再翻訳)
- ローディング表示の改善(自動翻訳時も対応)
- エラーハンドリングの“パターン化”
- fetch の共通化(翻訳専用関数を作る)
これらはすべて fetch / async-await の型をそのまま使い回す ことで実現できます。
自動翻訳(入力が止まったら翻訳)を実装する
なぜ自動翻訳が必要なのか
翻訳アプリを使うとき、
「毎回ボタンを押すのが面倒」と感じることがあります。
そこで、
入力が 500ms 止まったら自動で翻訳する
という仕組みを作ります。
これを「デバウンス」と呼びます。
デバウンスの基本イメージ
let timerId = null;
inputText.addEventListener("input", () => {
clearTimeout(timerId);
timerId = setTimeout(() => {
translateText();
}, 500);
});
JavaScriptポイントは、
- 入力のたびにタイマーをリセット
- 最後の入力から 500ms 経ったら翻訳
という流れです。
自動翻訳にローディング表示を対応させる
自動翻訳でも、
ローディング中はボタンを無効化し、状態を表示する
というルールは変わりません。
function startLoading(message) {
statusDiv.textContent = message || "翻訳中です…";
translateButton.disabled = true;
}
function endLoading() {
translateButton.disabled = false;
}
JavaScript自動翻訳でもこの関数を使い回します。
翻訳処理を「共通関数」にまとめる
なぜ共通化が必要なのか
3日目では、
- ボタンを押したときの翻訳
- 自動翻訳
- 履歴からの再翻訳
など、翻訳処理を呼ぶ場面が増えます。
そこで、
翻訳処理をひとつの関数にまとめる
ことで、コードの重複を防ぎます。
共通の翻訳関数(重要)
async function requestTranslate(text, source, target) {
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) {
throw new Error(`HTTPエラー(${response.status})`);
}
const data = await response.json();
if (!data.translatedText) {
throw new Error("翻訳結果が取得できませんでした。");
}
return data.translatedText;
} catch (error) {
throw new Error(error.message);
}
}
JavaScriptここでの深掘りポイントは、
- fetch のエラー
- HTTP エラー
- JSON パースエラー
- API の中身が想定外
をすべて throw new Error(…) に統一していることです。
これにより、
上位の関数は「翻訳に失敗した」という事実だけを扱えばよくなります。
翻訳のメイン関数を“読みやすい流れ”にする
translateText の完成形
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 translated = await requestTranslate(text, source, target);
statusDiv.textContent = "翻訳に成功しました。";
resultDiv.textContent = translated;
addHistory(text, translated, source, target);
} catch (error) {
statusDiv.textContent = `翻訳中にエラーが発生しました:${error.message}`;
console.error(error);
} finally {
endLoading();
}
}
JavaScriptこの関数は、上から読むと“物語”になっています。
- 入力チェック
- ローディング開始
- 共通関数で翻訳
- 成功したら結果表示+履歴追加
- 失敗したらエラーメッセージ
- 最後にローディング終了
fetch の細かい処理はすべて requestTranslate に隠れているため、
とても読みやすくなっています。
翻訳履歴を「再翻訳できる UI」にする
履歴の構造
const 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, index) => {
html += `
<div class="history-item" data-index="${index}">
<p><strong>${item.original}</strong></p>
<p>→ ${item.translated}</p>
<p>(${item.source} → ${item.target})</p>
</div>
`;
});
historyDiv.innerHTML = html;
const items = historyDiv.querySelectorAll(".history-item");
items.forEach((item) => {
item.addEventListener("click", () => {
const index = item.dataset.index;
const h = history[index];
inputText.value = h.original;
sourceLang.value = h.source;
targetLang.value = h.target;
translateText();
});
});
}
JavaScriptここでの深掘りポイントは、
- 履歴をクリックすると、入力欄に値を戻して再翻訳
- 翻訳処理は translateText に一本化しているため、再利用が簡単
という「責務分離の効果」です。
エラーハンドリングを“パターン化”する
翻訳エラーの種類を整理する
翻訳アプリでは、次のようなエラーが起きます。
- ネットワークエラー
- HTTP エラー
- JSON パースエラー
- API の中身が想定外
- 入力が空
- 言語が同じ(en → en など)
これらをすべて「ユーザーに伝わる形」に変換します。
例:言語が同じときのチェック
if (source === target) {
statusDiv.textContent = "翻訳元と翻訳先が同じです。別の言語を選んでください。";
return;
}
JavaScript例:ネットワークエラーの表示
catch (error) {
statusDiv.textContent = `翻訳中にエラーが発生しました:${error.message}`;
}
JavaScriptここでのポイントは、
- エラーの種類ごとに「ユーザーが理解できる言葉」に変換する
- 技術的なエラー文をそのまま出さない
ということです。
ローディング表示を“自動翻訳にも対応”させる
自動翻訳でも translateText を呼ぶだけ
自動翻訳はこう書きます。
let autoTimer = null;
inputText.addEventListener("input", () => {
clearTimeout(autoTimer);
autoTimer = setTimeout(() => {
translateText();
}, 600);
});
JavaScripttranslateText の中でローディング処理をしているため、
自動翻訳でも UI が整ったまま動きます。
3日目のまとめ
今日やったことを整理すると、こうなります。
- 自動翻訳(デバウンス)を実装
- 翻訳処理を requestTranslate に共通化
- translateText を“読みやすい流れ”に整理
- 翻訳履歴をクリックで再翻訳できるように
- エラーハンドリングをパターン化
- ローディング表示を自動翻訳にも対応
どれも新しい文法ではなく、
「fetch / async-await / エラーハンドリングの型をどう再利用するか」
という話です。
今日いちばん深く理解してほしいこと
3日目の本質は、
「API 通信の型をひとつ作れば、機能はどんどん増やせる」
ということです。
翻訳
自動翻訳
履歴
再翻訳
エラー表示
ローディング
これらは全部、
同じ fetch / async-await / try-catch の型の上に乗っています。
4日目では、
この翻訳アプリに「お気に入り翻訳」「ローカルストレージ保存」「UI の改善」などを加えて、
さらに実用的なツールに育てていきます。

