AWS Builder 文章

為 AWS Manila Community Day 打造 Tagalog 學習 App,採用教育優先的開發提示

把 Tagalog 學習 App 轉成可分享給 AWS 社群 builders 的靜態 HTML demo。技術重點很實務:讓 App 保持靜態、讓內容保持結構化、讓行動裝置閱讀舒適,並用 demo 句子作為可見證據,證明 App 支援真實活動溝通。這是一個學習者在 AWS Manila Community Day 前與活動期間都能真正使用的版本。透過靜態檔案、結構化 prompt、mobile-first 版面,以及具文化敏感度的句子卡,建立有意義的 demo。

Tagalog 學習 App 文章系列

English 繁體中文 简体中文

把 Tagalog 學習 App 轉成可分享給 AWS 社群 builders 的靜態 HTML demo。技術重點很實務:讓 App 保持靜態、讓內容保持結構化、讓行動裝置閱讀舒適,並用 demo 句子作為可見證據,證明 App 支援真實活動溝通。這是一個學習者在 AWS Manila Community Day 前與活動期間都能真正使用的版本。透過靜態檔案、結構化 prompt、mobile-first 版面,以及具文化敏感度的句子卡,建立有意義的 demo。

免責聲明

目的:本專案是獨立的教育原型,用於語言學習、社群準備與技術分享。它的設計目標,是協助開發者在參加 AWS Manila Community Day 前練習簡單的 Tagalog。

非商業用途:本專案沒有商業模式、付費存取、廣告計畫、聯盟行銷方案或營利目標。它是為社群準備與開發者教育而建立的學習作品。

不保證正確性:App 可能使用生成式 AI 內容,因此翻譯、發音指南、文法說明與文化註解,在公開使用前都應由 Tagalog 母語者審閱。

範圍:目標不是打造完整的翻譯平台,而是示範如何用清楚的資料結構、可重複的產生邏輯與驗證檢查,將靜態學習網站在地化。

尊重社群:App 應避免刻板印象,並謹慎教導禮貌用語。像 poopokayoninyo 這類詞,應被解釋為表達尊重的工具,而不是裝飾。


Demo

Umalis tayo nang maaga bukas.

文法拆解

  • Umalis:意思是「離開」或「出發」。
  • tayo:包含說話者與聽者的「我們」。
  • nang:連接動作與方式或時間。
  • maaga:意思是「早」。
  • bukas:意思是「明天」。

發音指南

慢慢念時可讀作: oo-mah-lees tah-yoh nahng mah-mah-ah-gah boo-kahs.

  • Umalis:oo-mah-lees。
  • tayo:tah-yoh。
  • nang:nahng。
  • maaga:mah-mah-ah-gah。
  • bukas:boo-kahs。 這是一句安排行程的句子,語氣保持平穩清楚即可。

Content Snapshot

自然 Tagalog:Umalis tayo nang maaga bukas.
禮貌 Tagalog:Umalis po tayo nang maaga bukas.
額外範例 1:Sa sakayan, umalis tayo nang maaga bukas.
額外範例 2:Okay lang, umalis tayo nang maaga bukas.
相關日常範例:Napakainit ng araw ngayon.

目錄

第 1 部分:在學習迴圈有效前,讓 Demo 保持靜態

本節說明為什麼第一版使用靜態 App 就足夠。HTML、CSS、JavaScript 與 JSON 可以在沒有帳號、伺服器或複雜部署的情況下,驗證句子卡流程。

第 2 部分:設計行動裝置的閱讀順序

本節展示行動版版面規則:內容優先,文章清單放在按鈕後面。學習者應先看到目前課程,再跨分類瀏覽。

第 3 部分:建立內容 Schema 與 Renderer

本節定義 Tagalog 句子卡的實用內容 schema,並示範如何在問候、工作坊、方向指引、志工與等待用語等類別中,渲染相同的卡片形狀。

第 4 部分:技術分享用的提示詞 QA

本節提供 prompt 檢查與開發檢查,讓專案能安全地在技術分享場次中展示。

第 5 部分:Demo 現場筆記

本節記錄可重用的開發提示:靜態優先部署、卡片一致性、無障礙、文化 QA,以及 prompt 具體性。


第 1 部分:在學習迴圈有效前,讓 Demo 保持靜態

目標

不用後端也能建立實用的 Tagalog 學習 App。第一版應證明學習者可以開啟頁面、選擇文章、閱讀句子卡,並練習 AWS Manila Community Day 可用的句子。

提示詞

建立一個深色模式、行動裝置響應式的靜態 HTML Tagalog 學習 App。

桌面 UI:
- 左側:文章清單,包含主題名稱與 20 字摘要
- 右側:文章內容

行動 UI:
- 上方:文章內容
- 按鈕:開啟文章清單,顯示主題名稱與摘要

內容:
- 總共 24 篇文章
- 11 篇 AWS Community Day 文章、8 篇 Friendship 文章、5 篇 Manila Daily 文章
- 每篇文章都有句子卡,涵蓋問候、方向指引、工作坊、人脈交流、志工、等待時間與告別
- 每張句子卡包含自然 Tagalog、禮貌 Tagalog、友善 Filipino-English、俏皮 Filipino-English、文法、範例與發音

結果

這個 App 可以用靜態檔案發布。學習者可以開啟 index.html、選擇文章,並閱讀活動可用句子。App 不需要帳號或資料庫,就能驗證學習體驗。

提示

  • 只要內容契約清楚,靜態優先並不弱。
  • 快速 demo 可使用單一 CSS 檔或內嵌 CSS。
  • 每張句子卡使用同一個 renderer。
  • 如果目標是方便分享,可讓每篇文章都有自己的 HTML 頁面。
  • 加入可下載的 zip,方便離線檢閱。

建議檔案結構

/tagalog-community-day-demo
  index.html
  article-1-community-greetings-introductions.html
  article-2-community-directions-registration.html
  article-3-community-workshops-learning.html
  article-4-community-networking-belonging.html
  article-5-community-volunteers-time-goodbye.html
  styles.css
  app.js

第 2 部分:設計行動裝置的閱讀順序

目標

讓 App 在忙碌的活動場地中也好用。學習者在手機上應該立刻看到目前文章內容,只有需要時才開啟文章清單。

提示詞

讓行動版採用內容優先。

桌面:
- 使用雙欄版面。
- 將文章導覽放在左側。
- 將文章內容放在右側。

行動版:
- 將文章內容放在最上方。
- 預設隱藏文章清單。
- 顯示「開啟文章清單」按鈕。
- 在抽屜或面板中顯示文章連結。
- 讓觸控目標夠大,方便單手操作。

結果

學習者會先看到課程,再看到導覽。這很重要,因為這個 App 首先不是型錄,而是練習工具。

CSS 草圖

.layout {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: 1.5rem;
}

.sidebar {
  position: sticky;
  top: 1rem;
}

.mobile-menu-button {
  display: none;
}

@media (max-width: 860px) {
  .layout {
    display: flex;
    flex-direction: column;
  }

  .content {
    order: 1;
  }

  .mobile-menu-button {
    display: block;
    order: 2;
  }

  .sidebar {
    display: none;
    position: fixed;
    left: 1rem;
    right: 1rem;
    bottom: 1rem;
    max-height: 72vh;
    overflow: auto;
  }

  .sidebar.open {
    display: block;
  }
}

可在行動裝置測試的 Demo 範例

Magpahinga muna tayo sa lilim.
Umupo ka sandali kung nahihilo ka.
Paki-check po kung pumasok ang bayad.

如果這三個範例在手機上容易閱讀、複製並大聲練習,代表版面方向正確。


第 3 部分:建立內容 Schema 與 Renderer

目標

在所有活動分類中使用同一個內容模型。

提示詞

為 Tagalog 句子卡建立 JSON schema。
schema 必須支援:
- 英文輸入
- 自然 Tagalog
- 禮貌 Tagalog
- 友善 Filipino-English
- 俏皮 Filipino-English
- 語氣
- 文化脈絡
- 文法拆解
- 額外範例
- 發音指南
- 活動分類

結果

App 可以用同一個元件渲染問候、工作坊問題、感謝志工,以及等待用語。

Schema 範例

{
  "articles": [
    {
      "id": "manila-daily-health-safety-weather-money",
      "title": "Manila Daily:健康、安全、天氣與金錢",
      "summary": "尋求協助、談論天氣、處理付款、描述簡單健康需求,並在 Manila 周邊保持安全。",
      "cards": [
        {
          "englishInput": "如果你覺得頭暈,先坐一下。",
          "naturalTagalog": "Umupo ka sandali kung nahihilo ka.",
          "politeTagalog": "Umupo po kayo sandali kung nahihilo kayo.",
          "friendlyFilipinoEnglish": "Sit muna po kung dizzy.",
          "playfulFilipinoEnglish": "Umupo muna, easy lang all right.",
          "tone": "關心、實用、適合健康與安全情境",
          "culturalContext": "和警衛、工作人員、司機、攤商、長輩與第一次接觸的人說話時,使用禮貌形式。",
          "grammar": [
            { "term": "Umupo", "meaning": "坐下" },
            { "term": "sandali", "meaning": "一下子" },
            { "term": "nahihilo", "meaning": "感到頭暈" },
            { "term": "po", "meaning": "禮貌標記" }
          ],
          "examples": [
            {
              "tagalog": "Magpahinga muna tayo sa lilim.",
              "english": "我們先在陰影處休息一下。"
            },
            {
              "tagalog": "Umupo ka sandali kung nahihilo ka.",
              "english": "如果你覺得頭暈,先坐一下。"
            },
            {
              "tagalog": "Paki-check po kung pumasok ang bayad.",
              "english": "請確認款項是否已入帳。"
            }
          ]
        }
      ]
    }
  ]
}

Renderer 草圖

function renderSentenceCard(card) {
  const grammar = card.grammar
    .map((item) => `<li><strong>${item.term}:</strong> ${item.meaning}</li>`)
    .join("");

  const examples = card.examples
    .map(
      (example) => `
      <li>
        <strong>Tagalog:</strong> ${example.tagalog}<br>
        <strong>英文:</strong> ${example.english}
      </li>
    `,
    )
    .join("");

  return `
    <article class="sentence-card">
      <h3>${card.englishInput}</h3>
      <dl>
        <dt>自然 Tagalog:</dt><dd>${card.naturalTagalog}</dd>
        <dt>禮貌 Tagalog:</dt><dd>${card.politeTagalog}</dd>
        <dt>友善 Filipino-English:</dt><dd>${card.friendlyFilipinoEnglish}</dd>
        <dt>俏皮 Filipino-English:</dt><dd>${card.playfulFilipinoEnglish}</dd>
        <dt>語氣:</dt><dd>${card.tone}</dd>
        <dt>文化脈絡:</dt><dd>${card.culturalContext}</dd>
        <dt>文法拆解:</dt><dd><ul>${grammar}</ul></dd>
        <dt>額外範例:</dt><dd><ol>${examples}</ol></dd>
      </dl>
    </article>
  `;
}

實作

內容 schema 與 renderer 這一節很關鍵,因為它把文章中繼資料、句子卡結構、響應式 CSS、頁面 renderer、文章產生器、ZIP 建立,以及驗證檢查,整合成一條靜態網站建置流程。

from pathlib import Path
import html
import zipfile
import textwrap
import re
import json
import os

out_dir = Path(".")

# 文章中繼資料,包含 20 字摘要
# 透過程式註解/log 輸出的簡單空白字數計算檢查。
articles = [
    {
        "id": 1,
        "slug": "community-greetings-introductions",
        "title": "Community Day:問候與有禮貌的自我介紹",
        "category": "Community Day",
        "summary": (
            "練習有禮貌的問候、名字、初學者句子與溫暖介紹,"
            "讓你在 Manila 自信地認識講者、志工、學生與 builders。"
        ),
    },
    {
        "id": 2,
        "slug": "community-directions-registration",
        "title": "Community Day:方向、場地與報到",
        "category": "Community Day",
        "summary": (
            "學習報到、房間、座位、洗手間、出口、"
            "排隊、識別證與活動期間禮貌求助的實用場地句子。"
        ),
    },
    {
        "id": 3,
        "slug": "community-workshops-learning",
        "title": "Manila Daily:健康、安全、天氣與金錢",
        "category": "Community Day",
        "summary": (
            "用尊重的 Tagalog 詢問工作坊問題、請對方重複、討論 demo、serverless 想法、"
            "AI 解決方案、cloud labs 與學習目標。"
        ),
    },
    {
        "id": 4,
        "slug": "community-networking-belonging",
        "title": "Community Day:人脈交流、連結與歸屬感",
        "category": "Community Day",
        "summary": (
            "使用友善句子與開發者、學生、領袖和同儕連結,"
            "同時表達興奮、謙遜、感謝與歸屬感。"
        ),
    },
    {
        "id": 5,
        "slug": "community-volunteers-time-goodbye",
        "title": "Community Day:志工、等待、時間與告別",
        "category": "Community Day",
        "summary": (
            "感謝志工、管理等待時間、禮貌道歉、協調抵達、"
            "溫暖告別,並用實用句子尊重時程。"
        ),
    },
    {
        "id": 6,
        "slug": "friendship-one-question-one-answer",
        "title": "Friendship Tagalog:一個問題,一個溫柔回答",
        "category": "Friendship",
        "summary": (
            "練習關於真誠、回答、希望、勇氣與真實感受的原創友情問題,"
            "不複製任何受著作權保護的歌詞。"
        ),
    },
    {
        "id": 7,
        "slug": "friendship-friends-only-feelings",
        "title": "Friendship Tagalog:只是朋友的感受",
        "category": "Friendship",
        "summary": (
            "學習關於友情、不確定、希望、界線與情緒清晰度的溫柔句子,"
            "使用尊重、柔和且適合初學者的 Tagalog 模式。"
        ),
    },
    {
        "id": 8,
        "slug": "friendship-you-are-my-beloved",
        "title": "Friendship Tagalog:你是我珍視的人",
        "category": "Friendship",
        "summary": (
            "探索關於關心、情感、信任、支持與選擇某人的原創詩意奉獻句,"
            "使用溫暖但簡單的 Tagalog。"
        ),
    },
    {
        "id": 9,
        "slug": "friendship-honest-heart-deep-feelings",
        "title": "Friendship Tagalog:誠實的心與深刻感受",
        "category": "Friendship",
        "summary": (
            "建立真實情緒、緊張的心、真誠欣賞、"
            "安靜思念與謹慎告白的自然禮貌 Tagalog 詞彙。"
        ),
    },
    {
        "id": 10,
        "slug": "friendship-distance-hope-promises",
        "title": "Friendship Tagalog:距離、希望與承諾",
        "category": "Friendship",
        "summary": (
            "練習關於距離、等待、承諾、北方與南方、"
            "希望、耐心與依然穩定的愛的原創句子。"
        ),
    },
]

community_sets = {
    1: [
        (
            "今天很高興認識你。",
            "Masaya akong makilala ka ngayon.",
            "Masaya po akong makilala kayo ngayon.",
            "Happy to meet you today, kumusta!",
            "Masaya akong makilala ka ngayon, all right.",
            "溫暖、有禮貌、活動可用",
        ),
        (
            "你好,我正在學 Tagalog。",
            "Kumusta, nag-aaral ako ng Tagalog.",
            "Kumusta po, nag-aaral po ako ng Tagalog.",
            "Hello po, learning Tagalog ako.",
            "Kumusta, learning Tagalog na ako, all right.",
            "友善、適合初學者",
        ),
        (
            "我的名字是 Martinez。",
            "Ako si Martinez.",
            "Ako po si Martinez.",
            "Hi po, ako si Martinez.",
            "Ako si Martinez, ready na ako all right.",
            "簡單自我介紹",
        ),
        (
            "這是我第一次來 Manila。",
            "First time ko sa Manila.",
            "First time ko po sa Manila.",
            "First time ko po sa Manila, excited ako.",
            "First time ko sa Manila, all right amazed.",
            "個人、興奮",
        ),
        (
            "我想有禮貌地向大家打招呼。",
            "Gusto kong bumati sa lahat nang magalang.",
            "Gusto ko pong bumati sa lahat nang magalang.",
            "Gusto ko pong mag-greet sa lahat.",
            "Gusto kong mag-greet sa lahat, polite mode all right.",
            "尊重、實用",
        ),
        (
            "大家早安。",
            "Magandang umaga sa lahat.",
            "Magandang umaga po sa inyong lahat.",
            "Good morning po sa lahat.",
            "Magandang umaga, ready na tayo all right.",
            "正式問候",
        ),
        (
            "我可以坐這裡嗎?",
            "Puwede ba akong umupo dito?",
            "Puwede po ba akong umupo dito?",
            "Can I sit here po?",
            "Puwede ba akong umupo dito, all right?",
            "禮貌請求",
        ),
        (
            "我是初學者,所以請說慢一點。",
            "Baguhan ako, kaya pakibagalan.",
            "Baguhan po ako, kaya pakibagalan po.",
            "Beginner po ako, slow lang please.",
            "Beginner ako, slow lang all right please.",
            "謙遜、實用",
        ),
        (
            "我很期待參加 Manila Community Day。",
            "Excited akong sumali sa Manila Community Day.",
            "Excited po akong sumali sa Manila Community Day.",
            "Excited po ako for Manila Community Day.",
            "Excited na ako sa Community Day, all right.",
            "興奮、活動可用",
        ),
        (
            "謝謝你歡迎我。",
            "Salamat sa pagtanggap sa akin.",
            "Salamat po sa pagtanggap sa akin.",
            "Thank you po for welcoming me.",
            "Salamat sa welcome, belong na ako all right.",
            "感謝、情感",
        ),
    ],
    2: [
        (
            "報到區在哪裡?",
            "Saan ang registration area?",
            "Saan po ang registration area?",
            "Saan po ang registration area, please?",
            "Saan ang registration area, ready na ako all right.",
            "禮貌問路",
        ),
        (
            "工作坊教室在哪裡?",
            "Saan ang workshop room?",
            "Saan po ang workshop room?",
            "Saan po ang workshop room, please?",
            "Saan ang workshop room, workshop mode all right.",
            "實用、活動可用",
        ),
        (
            "可以指給我入口在哪裡嗎?",
            "Puwede mo ba akong ituro sa entrance?",
            "Puwede po ba ninyo akong ituro sa entrance?",
            "Pa-point po sa entrance?",
            "Point me sa entrance, lost na ako all right.",
            "禮貌求助",
        ),
        (
            "這是正確的隊伍嗎?",
            "Tamang pila ba ito?",
            "Tamang pila po ba ito?",
            "Correct line po ba ito?",
            "Tamang pila ba ito, checking all right.",
            "排隊、實用",
        ),
        (
            "我在哪裡可以拿到識別證?",
            "Saan ko makukuha ang badge ko?",
            "Saan ko po makukuha ang badge ko?",
            "Saan po badge pickup?",
            "Saan badge ko, excited na all right.",
            "報到、禮貌",
        ),
        (
            "這裡有空位嗎?",
            "May bakanteng upuan ba dito?",
            "May bakanteng upuan po ba dito?",
            "May seat po ba dito?",
            "May seat ba dito, sit na ako all right.",
            "禮貌、座位",
        ),
        (
            "洗手間在哪裡?",
            "Saan ang restroom?",
            "Saan po ang restroom?",
            "Saan po ang CR?",
            "Saan ang CR, quick lang all right.",
            "實用問路",
        ),
        (
            "下一場 session 幾點開始?",
            "Anong oras magsisimula ang susunod na session?",
            "Anong oras po magsisimula ang susunod na session?",
            "What time po next session?",
            "Next session what time, all right.",
            "時間、活動可用",
        ),
        (
            "請等我幾分鐘。",
            "Hintayin mo ako ng ilang minuto.",
            "Hintayin n’yo po ako ng ilang minuto.",
            "Wait me for a few minutes po.",
            "Hintayin mo ako, papunta na ako all right.",
            "等待、協調",
        ),
        (
            "請確認付款是否已入帳。",
            "Paki-check kung pumasok ang bayad sa venue.",
            "Paki-check po kung pumasok ang bayad sa venue.",
            "Payment received po ba?",
            "Paki-check kung pumasok ang bayad sa venue, all right.",
            "付款確認",
        ),
    ],
}

# 將每組 10 個基礎句子擴展成 40 個情境變體。
community_contexts = [
    ("在上午報到時", "sa morning registration"),
    ("在工作坊開始前", "bago magsimula ang workshop"),
    ("遇到志工時", "habang may nakikilalang volunteer"),
    ("session 結束後", "pagkatapos ng session"),
]

friendship_contexts = [
    ("輕聲說話時", "kapag marahang nagsasalita"),
    ("等待回答時", "habang naghihintay ng sagot"),
    ("在真誠訊息中", "sa isang tapat na mensahe"),
    ("以謹慎的心情", "nang may maingat na puso"),
]


def expand(base, article_id):
    contexts = community_contexts if article_id <= 5 else friendship_contexts
    cards = []

    for i, b in enumerate(base):
        for j, (enctx, tlctx) in enumerate(contexts):
            eng, nat, pol, friendly, playful, tone = b

            # 保持句子清楚;把情境放在背景中,
            # 不一定附加到每一句翻譯後面。
            cards.append(
                {
                    "num": len(cards) + 1,
                    "background": (
                        f"在{enctx}使用這個句子。它有助於連結情緒、文化、"
                        "禮貌與實用溝通。"
                    ),
                    "english": eng,
                    "natural": nat,
                    "polite": pol,
                    "friendly": friendly,
                    "playful": playful,
                    "tone": tone,
                    "context_en": enctx,
                    "context_tl": tlctx,
                }
            )

    return cards[:40]


for a in articles:
    base = community_sets.get(a["id"]) or friendship_sets.get(a["id"])
    a["cards"] = expand(base, a["id"])


# 簡易發音產生器,讓指南保持一致。
pron_map = {
    "Kumusta": "koo-MOOS-tah",
    "Salamat": "sah-LAH-maht",
    "Puwede": "PWEH-deh",
    "Saan": "SAH-ahn",
    "Ako": "AH-koh",
    "po": "poh",
    "kayo": "KAH-yoh",
    "ninyo": "NEEN-yoh",
    "n’yo": "nyoh",
    "Gusto": "GOOS-toh",
    "Excited": "ek-SAI-ted",
    "Nandito": "nahn-DEE-toh",
    "Papunta": "pah-POON-tah",
    "Hintayin": "hin-tah-YEEN",
    "Pasensya": "pah-SEN-shah",
    "Mahalaga": "mah-hah-LAH-gah",
    "Sasamahan": "sah-sah-MAH-hahn",
    "Kahit": "kah-HEET",
    "Manila": "mah-NEE-lah",
    "Tagalog": "tah-GAH-log",
    "magtanong": "mag-tah-NONG",
    "pakiulit": "pah-kee-OO-lit",
}


def pronunciation(tl):
    # 使用已知句首提供精簡指南,並加上簡單 fallback。
    for k, v in pron_map.items():
        if tl.startswith(k) or f" {k}" in tl:
            # 標出第一個相關片段,並加入易讀文字。
            return (
                f"慢慢清楚地念。從這裡開始:{v}。讓 'po' 保持柔和,"
                "並重讀本指南中的大寫音節。"
            )

    return (
        "每個母音都清楚發出:a 像 ah、e 像 eh、i 像 ee、o 像 oh、u 像 oo。"
        "最後一個音節也要清楚。"
    )


def breakdown(card):
    e = card["english"].lower()
    nat = card["natural"]
    items = []

    def add(tag, meaning):
        items.append((tag, meaning))

    if "thank" in e or "salamat" in nat.lower():
        add("Salamat", "謝謝;核心感謝用語")
        add(
            "po",
            "面對長輩、講者、主辦者、志工或新認識的人時使用的禮貌標記",
        )
        add("sa", "依情境可表示 for、in、at 或 to")

    elif "where" in e or nat.lower().startswith("saan"):
        add("Saan", "哪裡;詢問地點或方向")
        add("po", "用於尊重提問的禮貌標記")
        add("ang", "放在被詢問地點或事物前的焦點標記")

    elif "may i" in e or "could" in e or "please" in e:
        add("Puwede / Paki-", "可以、能否或請;讓請求更柔和")
        add("po", "禮貌標記")
        add("ba", "用於是非問句的疑問標記")

    elif "wait" in e or "way" in e or "late" in e:
        add(
            "Hintayin / Papunta / Pasensya",
            "等待、正在路上與道歉的協調用語",
        )
        add("na", "現在或好了;常表示狀態改變")
        add("po", "禮貌標記")

    elif any(
        w in e
        for w in [
            "heart",
            "love",
            "feel",
            "answer",
            "friends",
            "hope",
            "promise",
            "dear",
            "admire",
        ]
    ):
        add("puso / damdamin", "心或感受;情感詞彙")
        add("sana", "希望或願望;讓句子更溫柔")
        add(
            "po / kayo / ninyo",
            "當語氣需要尊重且謹慎時使用的尊敬形式",
        )

    elif "excited" in e:
        add("Excited ako", "我很興奮;常見 Filipino-English 表達")
        add("-ng / akong", "連接『我是』與下一個動作的連接形式")
        add("sumali / matuto", "參加或學習")

    else:
        add("Ako / ko", "我或我的;標記說話者")
        add("ka / kayo", "你/你們;kayo 是禮貌或複數形式")
        add("po", "用於表示尊重的禮貌標記")

    return items[:3]


def examples(card, article_id):
    if article_id <= 5:
        return [
            ("Magpahinga muna tayo sa lilim.", "我們先在陰影處休息一下。"),
            ("Umupo ka sandali kung nahihilo ka.", "如果你覺得頭暈,先坐一下。"),
            ("Paki-check po kung pumasok ang bayad.", "請確認付款是否已入帳。"),
        ]

    return [
        ("Sana maintindihan mo ako.", "我希望你理解我。"),
        ("Makikinig po ako nang may respeto.", "我會帶著尊重聆聽。"),
        ("Tapat ang damdamin ko, all right.", "我的感受是真誠的 all right。"),
    ]


def sentence_card_html(card, article):
    bds = "".join(
        f"<li><strong>{html.escape(k)}:</strong> {html.escape(v)}</li>"
        for k, v in breakdown(card)
    )

    exs = "".join(
        (
            f"<li><strong>Tagalog:</strong> {html.escape(tl)}<br>"
            f"<strong>英文:</strong> {html.escape(en)}</li>"
        )
        for tl, en in examples(card, article["id"])
    )

    related = (
        "活動模式:和講者、主辦者、志工,以及第一次見面的人說話時,先使用禮貌版本。"
        "隨意與俏皮風格比較適合在建立關係後和同儕使用。"
        if article["id"] <= 5
        else (
            "情感模式:讓句子保持溫柔,避免造成壓力;當尊重與謹慎語氣很重要時,"
            "使用 sana、po、kayo 或 ninyo。"
        )
    )

    return f"""
    <article class="sentence-card" id="sentence-{card['num']}">
      <h3>句子 {card['num']}</h3>
      <dl class="template">
        <dt>背景脈絡:</dt><dd>{html.escape(card['background'])}</dd>
        <dt>英文輸入:</dt><dd>{html.escape(card['english'])}</dd>
        <dt>自然 Tagalog:</dt><dd lang="tl">{html.escape(card['natural'])}</dd>
        <dt>禮貌 Tagalog:</dt><dd lang="tl">{html.escape(card['polite'])}</dd>
        <dt>友善 Filipino-English:</dt><dd>{html.escape(card['friendly'])}</dd>
        <dt>俏皮 Filipino-English:</dt><dd>{html.escape(card['playful'])}</dd>
        <dt>語氣:</dt><dd>{html.escape(card['tone'])}</dd>
        <dt>文化脈絡:</dt><dd>和長輩、講者、主辦者、志工、場地工作人員,以及第一次接觸的人說話時,使用禮貌形式。和朋友或同儕相處且情境放鬆時,可使用隨意說法。</dd>
        <dt>文法拆解:</dt><dd><ul>{bds}</ul></dd>
        <dt>常見情境與慣用方式:</dt><dd>這句話適合用於{html.escape(card['context_en'])}。它同時教正確 Tagalog 與安全、尊重的說法。</dd>
        <dt>額外範例:</dt><dd><ol>{exs}</ol></dd>
        <dt>發音指南:</dt><dd>{html.escape(pronunciation(card['polite']))}</dd>
        <dt>相關的情感、詩意、禮貌與實用句型理解:</dt><dd>{html.escape(related)}</dd>
      </dl>
    </article>"""

開發技能

關鍵開發技能是把內容產生與內容渲染分開。AI prompt 產生結構化內容。UI 負責渲染該內容。開發者不應該為每一個新的句子分類手動重新設計頁面。


第 4 部分:技術分享用的提示詞 QA

目標

透過同時展示 prompt 紀律與程式碼紀律,讓專案準備好用於開發者演講。

提示詞

檢閱產生出的 Tagalog 學習 App 內容。

檢查:
- 每張句子卡是否都包含自然 Tagalog?
- 需要時,每張句子卡是否都包含禮貌 Tagalog?
- po、opo、kayo 與 ninyo 是否解釋正確?
- 俏皮 Filipino-English 是否標示為非正式?
- 活動句子對 AWS Manila Community Day 是否實用?
- 是否包含等待與時間相關句子?
- 範例是否夠短,適合行動裝置?
- 輸出是否避免未經支持的文化假設?

結果

檢閱步驟成為建置的一部分,而不是事後補救。技術分享訊息也因此更有說服力,因為 demo 展示了負責任的 AI 使用方式。

QA 檢查清單

  • 存在內容契約。
  • 每篇文章都有主題與摘要。
  • 每張句子卡都有相同欄位順序。
  • 禮貌 Tagalog 清楚可見。
  • 友善與俏皮風格有區分。
  • 範例包含活動行為。
  • 行動版版面先顯示內容。
  • 文章抽屜可以開啟與關閉。
  • 深色模式對比可讀。
  • 沒有 prompt-only 註記流入最終 UI。

Demo 驅動的 QA 範例

使用這些最新 HTML 範例作為 regression set:

1. Magpahinga muna tayo sa lilim.
   檢查健康/安全脈絡,以及自然的關懷句型。

2. Umupo ka sandali kung nahihilo ka.
   檢查適合初學者的建議,以及清楚的 Manila 日常用語。

3. Paki-check po kung pumasok ang bayad.
   檢查禮貌請求用語、付款脈絡,以及額外範例的唯一性。

第 5 部分:Demo 現場筆記

現場筆記 1:靜態部署

背景:第一版只需要證明句子卡是有用的。

目標:用最小的部署表面發布 App。

提示詞:建立具備深色模式與行動裝置響應式行為的靜態 HTML 頁面。

結果:demo 可以直接在瀏覽器中開啟,並以檔案形式分享。

提示:

  • 在需要使用者帳號或持久化之前,避免投入後端工作。
  • 使用靜態檔案取得早期回饋。
  • 讓檔名有意義。
  • 讓每篇文章連結都可分享。
  • 將 demo 打包成 zip。

現場筆記 2:卡片一致性

背景:如果卡片使用不同標籤,每篇文章都可能逐漸偏離。

目標:讓每篇文章都像同一個學習產品的一部分。

提示詞:每張卡片都使用相同句子模板。

結果:學習者可以從問候移動到工作坊內容,而不需要重新學習 UI。

提示:

  • 保持標籤順序一致。
  • 使用相同的語氣名稱。
  • 使用相同的範例數量。
  • 讓文法解說保持簡短。
  • 避免依分類改變卡片形狀。

現場筆記 3:無障礙即是尊重

背景:語言學習 App 應該讓在活動中已經疲累、使用手機的參加者也能閱讀。

目標:讓內容容易掃讀、點選與閱讀。

提示詞:使用可讀的對比、大型觸控目標、語意化標題與簡單導覽。

結果:App 不需要增加複雜度,就能變得更包容。

提示:

  • 使用語意化 HTML 元素。
  • 在深色模式中保持高對比。
  • 讓按鈕清楚明顯。
  • 避免在文法區塊使用太小的文字。
  • 不要只依賴顏色表示狀態。

現場筆記 4:文化 QA

背景:禮貌是產品需求,不是可有可無的註記。

目標:協助學習者避免對剛認識的人聽起來太直接。

提示詞:說明何時使用隨意說法,以及何時使用尊重說法比較安全。

結果:App 同時教授語言與社交脈絡。

提示:

  • 用適合初學者的語言解釋 po
  • 使用 kayo 表示尊重或複數的「你/你們」。
  • 使用 ninyo 表示尊重或複數的「你的/你們的」。
  • 清楚標示俏皮 Filipino-English 屬於非正式。
  • 請母語人士檢閱最終內容。

現場筆記 5:技術分享結構

背景:demo 本身很有用,但技術演講需要建置地圖。

目標:說明 prompt 如何變成檔案、schema、元件與 QA 規則。

提示詞:使用目標、prompt、結果、提示、證據與檢閱檢查來撰寫部落格文章。

結果:這篇文章成為可重用的教學材料,供想建立類似學習工具的 builders 使用。

提示:

  • 展示 prompt。
  • 展示 schema。
  • 展示 UI 片段。
  • 展示 demo 範例。
  • 以 builders 可重用的檢查清單作結。

結尾反思

這個 Tagalog 學習 App 最好的版本,不是最複雜的版本。它應該是學習者在 AWS Manila Community Day 前與活動期間都能真正使用的版本。靜態檔案、結構化 prompt、mobile-first 版面,以及具文化敏感度的句子卡,已足以建立有意義的 demo。從小處開始、檢查語言,並讓社群學習目標引導技術設計。