PhraseFlow | 多場景短語工具

為日本旅遊新手打造的輕量網頁應用,幫助完全不懂日語的使用者,透過一鍵點選的方式,即時顯示實用的日語短句。

系統特色

  • 零依賴:純 JavaScript + Alpine.js
  • 離線使用:LocalStorage 儲存,無網路也能用
  • AI 整合:Google Gemini API 生成個人專屬短句
  • Material Design 3:乾淨的佈局
  • 響應式設計:手機、平板、電腦均適配

系統架構

Alpine.js
  ↓
┌─────────────────┐
│ Material Web    │ → 使用者介面
└─────────────────┘
       ↓
┌─────────────────┐
│ Google Gemini   │ ← AI 短句生成
└─────────────────┘
       ↓
┌─────────────────┐
│ LocalStorage    │ ← 資料儲存
└─────────────────┘

專案結構

phraseflow/
├── index.html          # 主頁面
├── app.js             # Alpine.js 應用邏輯
├── data.js            # 短句資料庫
├── utils.js           # 工具函數
├── styles.css         # 主要樣式
└── package.json       # 專案設定

核心技術實作

1. Alpine.js 狀態管理

function createApp() {
  return {
    view: "home",
    data: [],
    currentSceneName: "",
    currentPhrases: [],
    dialogPhrase: { native: "", target: "" },
    num: 1,
    place: "新宿",
    food: "牛肉",
    
    init() {
      const cached = localStorage.getItem("qp_cachedData");
      this.data = cached ? JSON.parse(cached) : defaultData;
      this.placeOptions = [...getRecent(), ...presetPlaces].filter(unique);
      this.foodOptions = [...getRecentFood(), ...presetFoods].filter(unique);
    },
    
    enterScene(id) {
      const s = this.data.find((x) => x.id === id);
      this.currentSceneName = s.scene;
      this.currentPhrases = s.phrases;
      this.view = "phrases";
    }
  }
}

2. Material Web Components 整合

<!-- 場景選擇按鈕 -->
<div class="scene-grid">
  <template x-for="scene in data" :key="scene.id">
    <md-filled-button class="scene-btn" @click="enterScene(scene.id)">
      <span x-text="scene.scene"></span>
    </md-filled-button>
  </template>
</div>

<!-- 短句對話框 -->
<md-dialog x-ref="mainDlg">
  <div slot="headline" x-text="dialogPhrase.native"></div>
  <div slot="content" style="text-align:center">
    <p id="targetText" class="big"></p>
    
    <!-- 數量控制 -->
    <div x-show="showNumCtrl" x-cloak class="num-wrap">
      <md-filled-tonal-button class="num-btn" @click="dec()">
        <span class="material-symbols-outlined">remove</span>
      </md-filled-tonal-button>
      <md-filled-tonal-button class="num-btn" @click="inc()">
        <span class="material-symbols-outlined">add</span>
      </md-filled-tonal-button>
    </div>
  </div>
</md-dialog>

3. Google Gemini AI 整合

async callGemini(plan) {
  const apiKey = "AIzaSyCpL-uCIMgV4MMk9xGdTy3MShK1v2Vw5lQ";
  const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;

  const promptText = "你是一個旅遊情境日語短句生成助手。" +
    "請根據使用者提供的中文行程內容,提取3到5個常見旅遊情境。" +
    "每個情境,請提供5到10組中日對照的短句。";

  const requestData = {
    contents: [
      { parts: [{ text: `${promptText}\n\n使用者行程:\n${plan}` }] }
    ]
  };

  const r = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(requestData)
  });

  const j = await r.json();
  let resultText = j.candidates[0].content.parts[0].text;
  resultText = resultText.replace(/```json/g, "").replace(/```/g, "").trim();
  
  const newData = JSON.parse(resultText);
  this.data = newData;
  localStorage.setItem("qp_cachedData", resultText);
}

4. 自訂短句資料

編輯 data.js 檔案,添加你的短句:

export const defaultData = [
  {
    "id": 1,
    "scene": "餐廳點餐",
    "phrases": [
      {
        "pid": 1,
        "native": "不好意思(用來招呼服務員)。",
        "target": "すみません。"
      },
      {
        "pid": 2,
        "native": "可以不要放<食物>嗎?",
        "target": "<食物>を入れないでください。"
      }
    ]
  }
];

系統展示

PhraseFlow 場景選擇介面

PhraseFlow 短句顯示介面


相關資料

🔗 PhraseFlow

💻 完整原始碼 (GitHub)