iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
生成式 AI

30天用React打造AI工具箱系列 第 21

30天用React打造AI工具箱 Day21

  • 分享至 

  • xImage
  •  

30天用React打造AI工具箱 Day21

成功串接翻譯API 翻譯工具正式上線

今天我讓整個翻譯頁面正式「活起來」了!
原本只是靜態介面,現在可以輸入文字,選擇中翻英或英翻中,按下按鈕後就能即時取得翻譯結果,並且會自動記錄翻譯歷史。

這次我改用MyMemory Translation API。它是一個免費、免註冊的公開翻譯API,不用擔心金鑰或額度問題。這個API的呼叫方式非常簡單,只要使用fetch傳入查詢字串,就能立即拿到翻譯結果,非常適合練習階段使用。

步驟

  1. 使用 fetch() 串接 MyMemory API
const response = await fetch(
  `https://api.mymemory.translated.net/get?q=${encodeURIComponent(input)}&langpair=${source}|${target}`
)
const data = await response.json()
setOutput(data.responseData.translatedText)
  1. 動態切換語言方向
    透過direction state控制英翻中或中翻英,使用簡潔的三元運算:
const [source, target] = direction === "en2zh" ? ["en", "zh-TW"] : ["zh-TW", "en"]
  1. 翻譯紀錄管理
    每次翻譯都會把結果存入 history 陣列,可以查看之前的翻譯並刪除。

  2. UX優化
    翻譯中會顯示「翻譯中...」
    若翻譯錯誤會顯示紅字警告
    自動清空輸入框與翻譯結果

程式碼

//TranslationTool.jsx
import { useState } from "react"

export default function TranslationTool() {
  const [input, setInput] = useState("")
  const [output, setOutput] = useState("")
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState("")
  const [history, setHistory] = useState([])
  const [direction, setDirection] = useState("en2zh")

  const handleTranslate = async () => {
    if (!input.trim()) return
    setLoading(true)
    setError("")
    setOutput("")

    const [source, target] =
      direction === "en2zh" ? ["en", "zh-TW"] : ["zh-TW", "en"]

    try {
      // 直接使用 MyMemory Translation API
      const response = await fetch(
        `https://api.mymemory.translated.net/get?q=${encodeURIComponent(
          input
        )}&langpair=${source}|${target}`
      )

      const data = await response.json()
      console.log("🌍 回傳資料:", data)

      if (!data?.responseData?.translatedText)
        throw new Error("API 回傳錯誤或內容無法解析")

      const result = data.responseData.translatedText
      setOutput(result)

      const newRecord = {
        id: Date.now(),
        from: input,
        to: result,
        type: direction,
      }

      setHistory([newRecord, ...history])
      setInput("")
    } catch (err) {
      console.error("❌ 翻譯錯誤:", err)
      setError("⚠️ 翻譯失敗,請稍後再試")
    } finally {
      setLoading(false)
    }
  }

  const handleDelete = (id) => {
    setHistory(history.filter((item) => item.id !== id))
  }

  return (
    <div className="space-y-6">
      <div className="bg-white p-4 rounded shadow space-y-3">
        <h2 className="text-lg font-semibold">🌐 多語言翻譯工具</h2>

        <div className="flex space-x-2">
          <select
            value={direction}
            onChange={(e) => setDirection(e.target.value)}
            className="border px-2 py-1 rounded"
          >
            <option value="en2zh">英文 → 中文</option>
            <option value="zh2en">中文 → 英文</option>
          </select>

          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="輸入要翻譯的文字..."
            className="flex-1 border px-3 py-2 rounded"
          />

          <button
            onClick={handleTranslate}
            disabled={loading}
            className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 disabled:opacity-50"
          >
            {loading ? "翻譯中..." : "翻譯"}
          </button>
        </div>

        {error && <p className="text-red-500 text-sm">{error}</p>}
        {output && (
          <div className="mt-4 bg-gray-100 p-3 rounded">
            <p className="text-gray-700 font-medium">翻譯結果:</p>
            <p>{output}</p>
          </div>
        )}
      </div>

      <div className="bg-white p-4 rounded shadow">
        <h3 className="font-semibold mb-2">📜 翻譯紀錄</h3>
        {history.length === 0 ? (
          <p className="text-gray-500 text-sm">目前沒有紀錄。</p>
        ) : (
          <ul className="space-y-2">
            {history.map((item) => (
              <li
                key={item.id}
                className="flex justify-between bg-gray-50 p-2 rounded border"
              >
                <div>
                  <p className="text-sm text-gray-600">
                    <strong>原文:</strong> {item.from}
                  </p>
                  <p className="text-sm text-gray-800">
                    <strong>譯文:</strong> {item.to}
                  </p>
                </div>
                <button
                  onClick={() => handleDelete(item.id)}
                  className="text-red-600 hover:text-red-800 text-sm"
                >
                  刪除
                </button>
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  )
}
// vite.config.js
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import tailwindcss from "@tailwindcss/vite"

export default defineConfig({
  plugins: [react(), tailwindcss()],
  server: {
    port: 5173,
  },
})

上一篇
30天用React打造AI工具箱 Day20
系列文
30天用React打造AI工具箱21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言