AWS Builder 文章

為 AWS Manila Community Day 打造 Tagalog 學習 App 的 Extra Examples 更獨特且可審閱

當 Tagalog 學習 App 成長到 24 個文章頁後,最大的內容風險是重複。靜態頁面可能看起來完整,但許多卡片其實重複使用相同的 extra examples。Python scripts 展示了如何重寫、去重與驗證例句內容,讓 App 更容易審閱,也更適合技術分享。

Tagalog 學習 App 文章系列

English 繁體中文 简体中文

當 Tagalog 學習 App 成長到 24 個文章頁後,最大的內容風險是重複。靜態頁面可能看起來完整,但許多卡片其實重複使用相同的 extra examples。Python scripts 展示了如何重寫、去重與驗證例句內容,讓 App 更容易審閱,也更適合技術分享。

免責聲明

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

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

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

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

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


Demo

Natural Tagalog:
Nakalimutan ko ang payong ko.

English:
I forgot my umbrella.

Polite Tagalog:
Nakalimutan ko po ang payong ko po.

Friendly Filipino-English:
Nakalimutan ko ang payong ko, okay po.

Playful Filipino-English:
Uy, Nakalimutan ko ang payong ko, all right.

Tone:
daily

Cultural Context:
這句可用於下雨情境。和警衛、司機、攤販、工作人員、長輩,以及第一次見面的人說話時,先使用禮貌形式。

Context Use:
適合日常 Manila 情境。簡短的 Tagalog 句子加上禮貌標記,在公共場所聽起來自然、溫暖且實用。

Grammatical Breakdown(文法拆解)

  • Nakalimutan:和 nakalimut 相關的地點、物件或動作形式。
  • ko:依句型可表示「我的」、「我」或「由我」。
  • ang:焦點標記,放在主要名詞或概念前。
  • payong:和 yong 相關的請求或方向用語。
  • po:禮貌標記,用於尊重語氣。

Pronunciation Guide(發音指南)

逐字發音可以讀成:nah-kah-lee-moo-tahn koh ahng pah-yohng poh.

  • Nakalimutan:拆成 na: nah + ka: kah + li: lee + mu: moo + tan: tahn。
  • ko:讀成 koh。
  • ang:讀成 ahng。
  • payong:拆成 pa: pah + yong: yohng。
  • po:讀成 poh。

內容快照

內容 QA 腳本:
- rewrite_extras_from_main_examples.py
- force_rewrite_all_extra_examples.py
- dedupe_tagalog_examples.py

主要工作:
- 讀取每個 sentence-card article block
- 抽出 Natural Tagalog
- 當 Natural Tagalog 重複時讓它變得可追蹤
- 為每張卡片建立三個 extra examples
- 加入 English、Natural Tagalog、Polite Tagalog 版本
- 重新產生文法與發音段落
- 確認每篇文章仍有 40 張卡片
- 印出更新檔案數量

目錄

Part 1: 找出重複問題

說明為什麼主文章頁產生後,extra examples 還需要自己的 QA pass。

Part 2: 從主句重寫 Extra Examples

說明如何根據當前卡片的 Natural Tagalog 產生 extra examples。

Part 3: 對所有卡片強制一致重寫

說明當卡片邊界或既有 HTML 結構不一致時,為什麼第二輪 pass 會有幫助。

Part 4: 用可追蹤的課程情境去重

說明 article number、sentence number 與 example number 如何讓重複內容更容易審閱。

Part 5: 驗證卡片數量與更新檔案

說明簡單 assert 如何保護靜態網站,避免產生破損文章頁。


Part 1: 找出重複問題

目標

讓每張卡片都感覺有用,而不是像複製貼上。

開發技能

這裡的技能是 generated static pages 的內容 QA。產生出的網站可能通過版型檢查,但如果每個 extra example 都重複同一句,學習品質仍然會失敗。

Prompt

審閱每一張 sentence card。
如果 extra examples 重複或太泛用,就重寫。
每張卡片維持三個 extra examples。
每個 extra example 都應包含:
- Tagalog
- English
- Natural Tagalog
- Polite Tagalog
- Grammatical Breakdown
- Pronunciation Guide

審閱訊號

重複內容可能藏在有效 HTML 裡:

<div class="extra-example">
  <p><strong>Tagalog:</strong><br><span lang="tl">Ayos, salamat.</span></p>
  <p><strong>English:</strong><br>All right, thank you.</p>
</div>

這個區塊結構正確,但如果數百張卡片都使用同一句,學習價值就會下降。

Tips

  • 分開計算 cards 與 examples。
  • 搜尋重複的 Tagalog text。
  • 依文章群組審閱例句。
  • 除非主句重複,否則保持主句穩定。
  • 去重變更要可追蹤,不要隨機。

Part 2: 從主句重寫 Extra Examples

目標

使用卡片目前的 Natural Tagalog 句子,作為相關例句的來源。

開發技能

rewrite_extras_from_main_examples.py 會抽出主句,並圍繞它建立三個 example blocks。

def extra_block(index: int, main: str) -> str:
    examples = [
        (
            f'Gagamitin ko rin ang linyang "{main}" mamaya.',
            f'I will also use the line "{main}" later.',
            f'Uulitin ko ang linyang "{main}" nang dahan-dahan.',
            f'Pakisuyo, uulitin ko po ang linyang "{main}" nang dahan-dahan.',
        ),
        (
            f'Sasabihin ko ang linyang "{main}" sa kausap ko.',
            f'I will say the line "{main}" to the person I am talking to.',
            f'Ipapaliwanag ko ang linyang "{main}" sa simpleng paraan.',
            f'Pakisuyo, ipapaliwanag ko po ang linyang "{main}" sa simpleng paraan.',
        ),
        (
            f'Magsanay tayo gamit ang linyang "{main}" ngayon.',
            f'Let us practice using the line "{main}" now.',
            f'Isusulat ko ang linyang "{main}" sa notes ko.',
            f'Pakisuyo, isusulat ko po ang linyang "{main}" sa notes ko.',
        ),
    ]

為什麼重要

這些例句會連回目前卡片。如果卡片教的是:

Natural Tagalog:
Paki-check kung pumasok ang bayad.

extra examples 就可以說明如何使用、重複、解釋或寫下這句話。這比不相關的填充句更有用。

產生後的 example shape

<div class="extra-example">
  <p><strong>Tagalog:</strong><br><span lang="tl">Gagamitin ko rin ang linyang "Paki-check kung pumasok ang bayad" mamaya.</span></p>
  <p><strong>English:</strong><br>I will also use the line "Paki-check kung pumasok ang bayad" later.</p>
  <p><strong>Natural Tagalog:</strong><br><span lang="tl">Uulitin ko ang linyang "Paki-check kung pumasok ang bayad" nang dahan-dahan.</span></p>
  <p><strong>Polite Tagalog:</strong><br><span lang="tl">Pakisuyo, uulitin ko po ang linyang "Paki-check kung pumasok ang bayad" nang dahan-dahan.</span></p>
</div>

Tips

  • 根據當前卡片建立 examples。
  • 每張卡片維持三個 examples。
  • 同時包含 natural 與 polite versions。
  • 重寫 examples 後重新使用文法與發音 helpers。
  • 寫入 HTML 前要 escape 注入文字。

Part 3: 對所有卡片強制一致重寫

目標

處理 regex card matching 或 example boundaries 需要更強 pass 的文章頁。

開發技能

force_rewrite_all_extra_examples.py 會掃描 sentence-card start tags、切出每張卡片、重寫 extra examples、刷新 helper sections,並檢查卡片數量。

ARTICLE_START = re.compile(r'<article class="sentence-card" id="sentence-\d+">')

def rewrite_cards(path: Path, text: str) -> tuple[str, int]:
    starts = [match.start() for match in ARTICLE_START.finditer(text)]
    if not starts:
        return text, 0

    parts = []
    cursor = 0
    count = 0

    for index, start in enumerate(starts):
        end = starts[index + 1] if index + 1 < len(starts) else text.find("</section>", start)
        card = text[start:end]
        natural = field_text(card, "Natural Tagalog:")
        card = rewrite_extras(card, natural)
        card = refresh_main_sections(card)
        parts.append(text[cursor:start])
        parts.append(card)
        cursor = end
        count += 1

    parts.append(text[cursor:])
    return "".join(parts), count

為什麼重要

產生的 HTML 可能有很長的單行、重複區塊或巢狀區塊,讓簡單替換變得脆弱。用卡片切片當工作單位,腳本會更清楚。

結果

腳本可以明確做到:

針對每個文章頁:
- 找出 40 張 sentence cards
- 重寫每張卡片
- 刷新文法與發音
- 如果頁面不是 40 張卡片就失敗

Tips

  • 用穩定的 article-card markers 切片。
  • 預期數量錯誤時要明確失敗。
  • 主卡片文字與 helper-section refresh 放在同一輪 pass。
  • 當較精準的 rewrite 留下不一致區塊時,使用 force rewrite。
  • force pass 後要審閱產生的 HTML。

Part 4: 用可追蹤的課程情境去重

目標

移除重複的 Natural Tagalog 內容,並讓 examples 足夠獨特、方便審閱。

開發技能

dedupe_tagalog_examples.py 加入兩個重要概念:

1. 追蹤所有文章頁中的重複 Natural Tagalog lines。
2. 用 article number、sentence number、extra example number 產生可追蹤的 extra examples。

程式模式

def unique_main_text(text: str, article: int, sentence: int, occurrence: int) -> str:
    context = f"sa aralin {number_word(article)}, pangungusap {number_word(sentence)}"
    clean = text.strip()
    if occurrence <= 1:
        return clean
    if clean.endswith("?"):
        return re.sub(r"\?$", f" {context}?", clean)
    return re.sub(r"[.!]?$", f" {context}.", clean)

為什麼重要

如果同一個 Natural Tagalog 句子出現超過一次,腳本可以讓重複版本可追蹤:

原本:
Salamat sa tulong.

可追蹤的 duplicate-safe 版本:
Salamat sa tulong sa aralin lima, pangungusap dalawa.

這不只是語言變更,也是 QA 訊號。審閱者可以定位重複內容,判斷這個 generated uniqueness 是否可接受,或是否應手動改寫 source content。

依主題產生 examples

dedupe script 也會根據文章群組選擇 example scenes:

def group_for(path: Path) -> str:
    if "friendship" in path.name:
        return "friendship"
    if "manila-daily" in path.name:
        return "manila"
    return "community"

接著每個群組會得到符合領域的 examples:

Community:
- session
- workshop
- community table

Friendship:
- mahinahong usapan
- tapat na mensahe
- ligtas na pag-uusap

Manila Daily:
- commute
- palengke
- araw-araw na errand

產生的 extra blocks 範例

Community example:
Sa aralin apat, pangungusap sampu, gagamitin ko ito sa workshop.

Friendship example:
Sa aralin labing-anim, pangungusap pito, aalagaan ko ang tapat na mensahe.

Manila Daily example:
Sa aralin dalawampu't dalawa, pangungusap tatlo, gagamitin ko ito sa palengke.

Tips

  • 讓 duplicate fixes 可追蹤。
  • 審閱 generated examples 時使用 article 與 sentence numbers。
  • 依內容群組選擇 example scenes。
  • polite forms 要和 natural forms 有差異。
  • 把 dedupe output 當審閱素材,不要當成最終語言權威。

Part 5: 驗證卡片數量與更新檔案

目標

大量重寫時,保護產生出的網站結構不被破壞。

開發技能

腳本使用 card count 的硬性檢查與輸出摘要。

for path in sorted(ROOT.glob("article-*.html"), key=lambda p: article_number(p)):
    text = path.read_text(encoding="utf-8")
    card_count = 0

    def replace_card(match: re.Match[str]) -> str:
        nonlocal card_count
        card_count += 1
        return rewrite_one_card(match.group(0))

    updated = CARD_RE.sub(replace_card, text)

    if card_count != 40:
        raise RuntimeError(f"{path.name}: expected 40 cards, found {card_count}")

結果

rewrite process 變得更安全:

預期:
24 個文章頁
每篇文章 40 張 sentence cards
每張卡片 3 個 extra examples

失敗模式:
raise error,而不是寫出部分破損頁面

審閱清單

  • 每篇文章是否仍有 40 張卡片?
  • 每張卡片是否仍有 Natural Tagalog 與 Polite Tagalog?
  • extra examples 是否分開且可讀?
  • 重複的 Natural Tagalog lines 是否可追蹤?
  • example rewrites 後是否重新產生文法與發音?
  • 產生的 examples 是否適合該文章群組?

Field Note 1: Extra Examples 是產品內容

背景:Extra examples 不是裝飾,它們教學習者如何重用短句。

目標:讓 examples 連回目前卡片。

Prompt:從卡片的 Natural Tagalog sentence 建立 extra examples。

結果:學習者會看到同一句如何被練習、重複、解釋或放進場景。

Review check:每個 example 是否支援主句,而不是分散注意力?

Field Note 2: Dedupe 應該可解釋

背景:Generated content 可能在許多卡片中重複。

目標:讓重複句子可見且可追蹤。

Prompt:追蹤所有文章頁的 Natural Tagalog strings,並在重複時加入 lesson context。

結果:審閱者能快速知道重複內容來自哪裡。

Review check:審閱者是否能把 generated example 對回 article number 與 sentence number?

Field Note 3: Polite Variants 需要真正不同

背景:如果 polite field 和 natural field 完全相同,教學價值有限。

目標:適當加入 poPakisuyo 或其他尊重句型。

Prompt:如果 Natural Tagalog 與 Polite Tagalog 相同,就建立一個有差異的 polite version。

結果:學習者更容易比較自然語氣與尊重語氣。

Review check:polite example 是否尊重但不生硬?

Field Note 4: Regex 有用,但需要護欄

背景:HTML regex 如果亂用會很脆弱。

目標:只在穩定的 generated markers 周圍使用 regex,並在之後驗證 counts。

Prompt:match 已知 sentence-card blocks,重寫它們,然後 assert 40 cards。

結果:對 generated static HTML 來說,腳本保持實用,也避免 silent corruption。

Review check:當預期 card count 不存在時,腳本是否會失敗?

技術分享角度

這個內容 QA flow 很適合開發者分享,因為它展示了一個可信 generated demo 背後的隱藏工作:

Generated article pages
      ->
detect sentence cards
      ->
extract Natural Tagalog
      ->
rewrite three extra examples
      ->
make duplicates traceable
      ->
regenerate grammar and pronunciation
      ->
assert 40 cards
      ->
write updated files
      ->
print JSON or count summary

最重要的 lesson 是:generated educational content 需要 QA pipeline,而不只是 prompt。

範例分享講稿

第一版產生了很多卡片,但 card count 不等於 learning quality。
所以我們加入 content QA pass。

腳本讀取每個 sentence-card block,抽出 Natural Tagalog line,然後重建 extra examples。
如果這句已經出現過,dedupe step 會加入 article 與 sentence context。

重寫後,腳本重新產生 grammar 與 pronunciation helper sections。
接著 assert 每篇文章仍然有 exactly 40 cards。

這帶來兩個好處:
1. 學習者得到更有用的 examples。
2. 開發者得到可量測的 review output。

結語

extra-example rewrite scripts 顯示內容 QA 也是工程問題。專案使用小型 Python 腳本、穩定 HTML markers、escaped output、依群組產生的 example scenes、duplicate tracking、grammar refresh、pronunciation refresh 與硬性 card-count checks。這個流程讓 Tagalog 學習 App 對學習者更有用,也讓技術分享 demo 更有說服力。