Tagalog 学习应用文章系列
- 第 1 篇:用提示词优先的产品设计,为 AWS Manila Community Day 构建 Tagalog 学习应用
- 第 2 篇:用教育优先的开发提示,为 AWS Manila Community Day 构建 Tagalog 学习应用
- 第 3 篇:面向 AWS Manila Community Day 的 Tagalog 学习应用深度开发流程
- 第 4 篇:为 AWS Manila Community Day 将 Tagalog 学习应用本地化为中文变体
- 第 5 篇:为 AWS Manila Community Day 的 Tagalog 卡片构建语法与发音增强流水线
- 第 6 篇:在 AWS Manila Community Day 的 Tagalog 学习应用中,让额外示例唯一且可审查
Tagalog 学习应用最初是为 AWS Manila Community Day 构建的学习站点。下一个工程挑战是本地化:在保留 Tagalog 学习价值的同时创建两个中文版本。本地化流程使用两种相关方法。一种方法侧重于通过 UI 和文本替换规则翻译现有 HTML 站点;另一种方法则从结构化内容字典、句子卡片数据、可复用标签和本地化语法指南重新生成本地化页面。
免责声明
目的:这个静态应用是用于语言练习和开发者分享的教育原型。它帮助学习者为 AWS Manila Community Day 准备礼貌的 Tagalog 短语。
非商业用途:本项目没有付费功能、广告、注册要求或商业目的。它用于学习、实验和社区准备。
不作保证:生成的语言内容可能存在错误。Tagalog 翻译、语法说明、发音指南和文化注释在生产使用前应由母语者审核。
范围:此应用不是官方 AWS 产品,不是官方翻译工具,也不能替代真人语言教学。它是一个带有实用学习内容的技术 demo。
尊重社区:应用应避免刻板印象,并谨慎教授礼貌用语。po、opo、kayo 和 ninyo 等词应被解释为表达尊重的工具,而不是装饰。
Demo
Uminom ka ng tubig dahil mainit.
语法拆解
- Uminom:表示“喝”。
- ka:非正式的“你”。
- ng:用于宾语前的标记。
- tubig:表示“水”。
- dahil:表示“因为”。
- mainit:表示“热”。
发音指南
逐词发音如下:
oo-mee-nohm kah ngah too-beeg dah-heel mah-mah-ee-neet.
- Uminom:oo-mee-nohm。
- tubig:too-beeg。
- dahil:dah-heel。
- mainit:mah-mah-ee-neet。
与年长者或不熟悉的人说话时,请使用带有 kayo 的礼貌版本。
内容快照
自然 Tagalog:Uminom ka ng tubig dahil mainit.
礼貌 Tagalog:Uminom po kayo ng tubig dahil mainit.
补充示例 1:Uminom ka muna ng malamig na tubig.
补充示例 2:Magdala ka ng tubig dahil mainit sa labas.
相关安全示例:Ilapit mo ang bag mo sa iyo.
目录
第 1 部分:把本地化拆分为 UI 文本和学习内容
本节说明为什么本地化应区分界面文字、文章元数据、学习注释和句子卡片内容。UI 标签经常变化,而 Tagalog 学习短语应保持稳定,方便发音练习。
第 2 部分:把语言配置作为产品契约
本节展示语言字典如何定义 ZIP 名称、输出文件夹、HTML 语言标记、站点品牌名、导航标签、文章列表按钮、说明、分类和文化指南。
第 3 部分:用替换映射本地化现有 HTML
本节说明第一条本地化路径:读取现有 HTML 文件,替换界面字符串和文章标题,注入本地化说明,然后打包为按语言区分的 ZIP 文件。
第 4 部分:从结构化内容生成本地化站点
本节说明第二条本地化路径:直接从文章元数据、句子卡片数组、语法辅助函数、示例、CSS、JavaScript 和模板生成本地化站点。
第 1 部分:把本地化拆分为 UI 文本和学习内容
目标
在不破坏 Tagalog 学习体验的前提下本地化应用。应用应将界面和说明翻译成中文变体,但 Tagalog 句子要保持可见且稳定,方便学习者练习口语。
开发技能
这里的开发技能是关注点分离。项目会区别处理这些层:
UI 层:
- 站点标题
- 文章列表标签
- 打开菜单按钮
- 开始按钮
- 分类徽标
- 练习说明
内容层:
- 文章标题
- 文章摘要
- 句子卡片标签
- 中文说明
- 语法注释
- 本地化示例
语言学习层:
- 自然 Tagalog
- 礼貌 Tagalog
- 友好的 Filipino-English
- 有趣的 Filipino-English
Tagalog 层不能被盲目翻译掉,因为学习者需要阅读并发音。
Prompt
为静态 Tagalog 学习站点创建两个本地化版本:
- 粤语 / 香港中文
- 台湾中文 / 繁体中文
翻译 UI、文章标题、摘要、学习注释、语法标签、文化说明和示例含义。
保留 Tagalog 句子文本,用于发音练习。
每个语言版本输出一个 ZIP 文件。
结果
输出会成为两个独立的静态站点。每个站点都有本地化导航和说明,同时 Tagalog 短语仍可用于口语练习。
提示
- 如果 Tagalog 短语本身是学习目标,不要翻译它。
- 翻译短语周围的标签和说明。
- UI 和内容使用独立字典。
- 使用
zh-HK、zh-TW等html lang标记。 - ZIP 名称保持明确,让审核者知道正在打开哪个语言版本。
第 2 部分:把语言配置作为产品契约
目标
通过配置对象描述每个语言版本,使其行为可预测。
开发技能
关键技能是配置驱动的本地化。语言配置包括输出 ZIP 文件、文件夹名称、HTML 语言标签、品牌文本、导航文字、文章列表控件、学习说明、分类名称和文化指南。
示例配置模式
lang_cfgs = {
"zh_tw": {
"zip": "tagalog_static_html_site_taiwan_chinese_zh_tw.zip",
"folder": "tagalog_zh_tw",
"html_lang": "zh-TW",
"site": "AWS 馬尼拉社群日 Tagalog 學習站",
"list": "文章列表",
"open": "開啟文章列表",
"choose": "選擇文章",
"note": "繁體中文(台灣)版本:介面、說明、語法提示與學習語境為繁體中文;Tagalog 句子保留原文方便朗讀練習。"
}
}
为什么重要
语言配置会成为产品契约。它告诉生成器文件写到哪里、页面如何标注、体验如何描述,以及结果如何打包。没有这个契约,每个本地化页面都可能漂移。
Prompt
为每个中文变体创建语言配置。
每个语言配置必须包含:
- zip 文件名
- 输出文件夹
- html lang 代码
- 站点标题
- 文章列表标签
- 打开文章列表按钮
- 开始按钮
- 语言说明
- 分类标签
- 句子卡片标签
- 尊重用语指南
- 日常随意用法指南
结果
生成器可以遍历语言配置,并用同样结构创建每个本地化站点。
提示
- 将特定语言文本集中存放。
- 避免在模板中硬编码标签。
- 保持文件夹名和 ZIP 名一致。
- 一致使用语言键:
zh_hk、zh_tw或zh-hk、zh-tw。 - 让每个用户可见字符串都能被搜索到。
第 3 部分:用替换映射本地化现有 HTML
目标
使用一个已生成的 HTML 站点作为起点,在不从头重建所有页面的情况下完成本地化。
开发技能
这是 replacement-map 方法。工作流如下:
1. 查找源 HTML 文件。
2. 定义语言字典。
3. 定义文章标题映射。
4. 定义摘要映射。
5. 定义通用 UI 替换映射。
6. 在每个 HTML 文件中替换字符串。
7. 注入本地化说明。
8. 写入对应语言文件夹。
9. 将每个文件夹打包为 ZIP 文件。
10. 运行一致性检查。
当现有 HTML 结构已经正确,而目标是快速修补文本时,这种方法很有用。
Prompt
使用现有静态 HTML 页面作为源文件。
对每个语言版本:
- 替换站点标题和导航标签
- 替换文章标题和摘要
- 替换句子卡片标签
- 替换文化指南文本
- 在 hero 区域添加本地化说明
- 设置 HTML lang 属性
- 将本地化文件打包为 ZIP
示例替换分类
article_title_map:
- Community Day: Greetings and Respectful Introductions
- Community Day: Directions, Venue, and Registration
- Friendship Tagalog: Honest Heart and Deep Feelings
summary_map:
- 原始英文文章摘要
- 本地化中文摘要
common_replacements:
- 背景语境
- 英文输入
- 自然 Tagalog
- 礼貌 Tagalog
- 语气
- 文化语境
- 发音指南
结果
本地化工具可以从现有页面创建语言版本,而不必重写整套生成逻辑。
提示
- 替换映射很快,但如果源文本变化,会比较脆弱。
- 仔细匹配精确字符串。
- 本地化后搜索仍残留的英文标签。
- 注入新内容时使用 HTML escaping。
- 这种方法适合 UI 修补,不适合复杂内容重构。
附件实现示例:完整本地化流水线
更新后的附件从 lang_ui.py 增加了一个具体的批处理模式。这个示例适合 replacement-map 路径,因为它会遍历语言配置,重建每个本地化文件夹,读取每个源 HTML 文件,应用本地化,然后把本地化页面写回对应语言的输出文件夹。
for lang, cfg in langs.items():
folder = Path(cfg['folder'])
if folder.exists():
shutil.rmtree(folder)
folder.mkdir()
for src in source_files:
text = src.read_text(encoding='utf-8')
localized = localize(text, lang, cfg)
(folder / src.name).write_text(localized, encoding='utf-8')
附件实现示例:HTML lang 替换
这次更新还增加了一个基于 regex 的示例,用来替换 HTML 语言标记。它属于现有 HTML 本地化流程,因为它可以在不重建完整模板的情况下修补生成页面。
text = re.sub(r'<html lang="en">', f'<html lang="{cfg["html_lang"]}">', text)
附件实现示例:向 HTML 注入本地化说明
本地化说明可以注入第一个匹配的 section 中,让审核者立刻知道自己正在查看哪个中文变体,以及为什么 Tagalog 文本被保留。
note_html = f"<p class='notice'>{cfg['note']}</p>"
text = text.replace('</section>', note_html + '</section>', 1)
UI 本地化
本节描述如何本地化一个已生成的静态 HTML 站点。流程会读取 index.html 和 article-*.html,定义中文变体 UI 字典,把英文文章标题和摘要映射到本地化版本,替换通用句子卡片标签,注入本地化学习说明,写入按语言区分的文件夹,将每个文件夹打包为 ZIP 文件,并打印二次验证检查。
from pathlib import Path
import zipfile, re, json, shutil, os
source_files = [Path('index.html')] + sorted(Path('.').glob('article-*.html'))
source_files = [p for p in source_files if p.exists()]
assert source_files, 'No source HTML files found.'
langs = {
'zh-hk': {
'zip': 'tagalog_static_html_site_zh_hk_cantonese.zip',
'folder': 'tagalog_static_html_site_zh_hk',
'html_lang': 'zh-HK',
'brand': 'AWS 馬尼拉 Community Day Tagalog 學習站',
'pill': '深色模式 · 手機自適應 · 10 篇文章',
'static_pill': '深色模式 · 靜態 HTML',
'articles_list': '文章清單',
'choose': '揀一篇文章',
'open': '打開文章清單',
'start': '開始第 1 篇',
'start_summary': '打開第一課,開始練習去馬尼拉時用得着嘅禮貌問候語。',
'intro': '呢個靜態學習網站有 10 個文章頁,每篇都有 40 張句子卡,用嚟練 Tagalog、文化語境、禮貌講法同有趣 Filipino-English。',
'note': '呢個廣東話版本已翻譯介面、文章說明同學習提示;Tagalog 句子保留原文,方便你直接朗讀練習。',
'category_community': 'Community Day',
'category_friendship': '浪漫句子',
'respect': '尊重:',
'practice': '練習:',
'culture': '文化:',
'respect_text': '同長輩、講者、主辦、義工、場地職員,或者第一次見面嘅人講嘢時,可以用 po、opo、kayo 同 ninyo 表示尊重。',
'practice_text': '先讀禮貌版本,再比較自然版本同 playful 版本。',
'culture_text': '細細個字都可以表達溫暖、謙遜、感謝、耐性同歸屬感。',
},
'zh-tw': {
'zip': 'tagalog_static_html_site_zh_tw_taiwan_chinese.zip',
'folder': 'tagalog_static_html_site_zh_tw',
'html_lang': 'zh-TW',
'brand': 'AWS 馬尼拉社群日 Tagalog 學習站',
'pill': '深色模式 · 行動裝置響應式 · 10 篇文章',
'static_pill': '深色模式 · 靜態 HTML',
'articles_list': '文章列表',
'choose': '選擇一篇文章',
'open': '開啟文章列表',
'start': '開始第 1 篇',
'start_summary': '開啟第一課,開始練習前往馬尼拉時能使用的禮貌問候語。',
'intro': '這個靜態學習網站包含 10 個文章頁面,每篇都有 40 張句子卡,用來練習 Tagalog、文化情境、禮貌表達與有趣的 Filipino-English。',
'note': '本繁體中文(台灣)版本已翻譯介面、文章說明與學習提示;Tagalog 句子保留原文,方便直接朗讀練習。',
'category_community': '社群日',
'category_friendship': '浪漫句子',
'respect': '尊重:',
'practice': '練習:',
'culture': '文化:',
'respect_text': '面對長輩、講者、主辦單位、志工、場地人員與第一次見面的人時,使用 po、opo、kayo 與 ninyo 表示尊重。',
'practice_text': '先朗讀禮貌版本,再比較自然版本與 playful 版本。',
'culture_text': '小小的詞也能傳達溫暖、謙遜、感謝、耐心與歸屬感。',
}
}
article_title_map = {
'Community Day: Greetings and Respectful Introductions': {
'zh-hk':'Community Day:問候同禮貌自我介紹','zh-tw':'社群日:問候與禮貌自我介紹'},
'Community Day: Directions, Venue, and Registration': {
'zh-hk':'Community Day:方向、場地同登記','zh-tw':'社群日:方向、會場與報到'},
'Community Day: Workshop Questions and Hands-On Learning': {
'zh-hk':'Community Day:Workshop 提問同實作學習','zh-tw':'社群日:工作坊提問與實作學習'},
'Community Day: Networking, Connect, and Belong': {
'zh-hk':'Community Day:Networking、連結同歸屬感','zh-tw':'社群日:交流、連結與歸屬感'},
'Community Day: Volunteers, Waiting, Time, and Goodbye': {
'zh-hk':'Community Day:義工、等候、時間同道別','zh-tw':'社群日:志工、等待、時間與道別'},
'Friendship Tagalog: One Question, One Gentle Answer': {
'zh-hk':'浪漫 Tagalog:一個問題,一個溫柔答案','zh-tw':'浪漫 Tagalog:一個問題,一個溫柔答案'},
'Friendship Tagalog: Friends Only Feelings': {
'zh-hk':'浪漫 Tagalog:原來只係朋友嘅感覺','zh-tw':'浪漫 Tagalog:只是朋友的心情'},
'Friendship Tagalog: You Are My Beloved': {
'zh-hk':'浪漫 Tagalog:你係我珍惜嘅人','zh-tw':'浪漫 Tagalog:你是我珍愛的人'},
'Friendship Tagalog: Honest Heart and Deep Feelings': {
'zh-hk':'浪漫 Tagalog:真心同深層感受','zh-tw':'浪漫 Tagalog:真誠的心與深層感受'},
'Friendship Tagalog: Distance, Hope, and Promises': {
'zh-hk':'浪漫 Tagalog:距離、希望同承諾','zh-tw':'浪漫 Tagalog:距離、希望與承諾'},
}
summary_map = {
'Practice polite greetings, names, beginner phrases, and warm introductions for meeting speakers, volunteers, students, and builders in Manila confidently today.': {
'zh-hk':'練習禮貌問候、姓名介紹、初學短句同自然開場,幫你喺馬尼拉自信認識講者、義工、學生同 builders。',
'zh-tw':'練習禮貌問候、姓名介紹、初學短句與溫暖開場,協助你在馬尼拉自信認識講者、志工、學生與 builders。'},
'Learn useful venue phrases for registration, rooms, seats, restrooms, exits, queues, badges, and polite help requests during the event today.': {
'zh-hk':'學習場地實用句,涵蓋登記、房間、座位、洗手間、出口、排隊、名牌同禮貌求助。',
'zh-tw':'學習會場實用短句,涵蓋報到、房間、座位、洗手間、出口、排隊、名牌與禮貌求助。'},
'Ask workshop questions, request repeats, discuss demos, serverless ideas, AI solutions, cloud labs, and learning goals with respectful Tagalog practice.': {
'zh-hk':'用禮貌 Tagalog 練習 workshop 提問、請人重複、討論 demo、serverless 諗法、AI 方案、cloud lab 同學習目標。',
'zh-tw':'用禮貌 Tagalog 練習工作坊提問、請對方重複、討論 demo、serverless 想法、AI 解決方案、雲端實驗與學習目標。'},
'Use friendly phrases to connect with developers, students, leaders, and peers while expressing excitement, humility, gratitude, belonging, and respect all right.': {
'zh-hk':'用友善句子連結 developers、學生、leaders 同 peers,同時表達興奮、謙遜、感謝、歸屬感同尊重。',
'zh-tw':'使用友善短句連結開發者、學生、領導者與夥伴,同時表達興奮、謙遜、感謝、歸屬感與尊重。'},
'Thank volunteers, manage waiting time, apologize politely, coordinate arrivals, say goodbye warmly, and respect schedules with practical event phrases today.': {
'zh-hk':'用實用活動句感謝義工、處理等候時間、禮貌道歉、協調到達、溫暖道別,同尊重時間表。',
'zh-tw':'用實用活動短句感謝志工、管理等待時間、禮貌道歉、協調抵達、溫暖道別,並尊重時程安排。'},
}
common_replacements = {
'Background context:': {'zh-hk':'背景情境:','zh-tw':'背景情境:'},
'English Input:': {'zh-hk':'英文輸入:','zh-tw':'英文輸入:'},
'Natural Tagalog:': {'zh-hk':'自然 Tagalog:','zh-tw':'自然 Tagalog:'},
'Polite Tagalog:': {'zh-hk':'禮貌 Tagalog:','zh-tw':'禮貌 Tagalog:'},
'Friendly Filipino-English:': {'zh-hk':'友善 Filipino-English:','zh-tw':'友善 Filipino-English:'},
'playful Filipino-English:': {'zh-hk':'玩味 Filipino-English:','zh-tw':'有趣 Filipino-English:'},
'Tone:': {'zh-hk':'語氣:','zh-tw':'語氣:'},
'Cultural Context:': {'zh-hk':'文化語境:','zh-tw':'文化情境:'},
'Grammatical Breakdown:': {'zh-hk':'語法拆解:','zh-tw':'語法拆解:'},
'Common Context and Idiomatic Use:': {'zh-hk':'常見語境同慣用法:','zh-tw':'常見情境與慣用法:'},
'Extra Examples:': {'zh-hk':'基本例子:','zh-tw':'基礎例子:'},
'Pronunciation Guide:': {'zh-hk':'發音指南:','zh-tw':'發音指南:'},
'Related understand emotional, poetic, polite, and practical sentence patterns:': {'zh-hk':'相關:理解情感、詩意、禮貌同實用句型:','zh-tw':'相關:理解情感、詩意、禮貌與實用句型:'},
'Sentence ': {'zh-hk':'句子 ','zh-tw':'句子 '},
'Article ': {'zh-hk':'第 ','zh-tw':'第 '},
' of 24': {'zh-hk':' / 10 篇','zh-tw':' / 10 篇'},
'40 sentence cards': {'zh-hk':'40 張句子卡','zh-tw':'40 張句子卡'},
'Each card separates natural Tagalog, respectful Tagalog, friendly Filipino-English, and playful Filipino-English so learners can choose the right tone.': {
'zh-hk':'每張卡會分開顯示自然 Tagalog、禮貌 Tagalog、友善 Filipino-English 同玩味 Filipino-English,方便學習者揀啱語氣。',
'zh-tw':'每張卡都會分開顯示自然 Tagalog、禮貌 Tagalog、友善 Filipino-English 與有趣 Filipino-English,方便學習者選擇合適語氣。'},
'Use the polite form with elders, speakers, organizers, volunteers, venue staff, and first-time contacts. Use casual speech with friends or peers when the situation feels relaxed.': {
'zh-hk':'同長輩、講者、主辦、義工、場地職員同第一次見面嘅人講嘢時,用禮貌版本。同朋友或熟咗嘅 peers 傾偈時,可以用輕鬆講法。',
'zh-tw':'面對長輩、講者、主辦單位、志工、場地人員與第一次見面的人時,使用禮貌版本。和朋友或熟悉夥伴聊天時,可使用較輕鬆說法。'},
'Let us rest in the shade first.': {'zh-hk':'多謝你幫忙。','zh-tw':'謝謝你的幫忙。'},
'Sit for a moment if you feel dizzy.': {'zh-hk':'我可唔可以問一個問題?','zh-tw':'我可以問一個問題嗎?'},
'Please check if the payment came in.': {'zh-hk':'我已經到咗。','zh-tw':'我已經到了。'},
'Choose an Article': {'zh-hk':'揀一篇文章','zh-tw':'選擇一篇文章'},
'Open Articles List': {'zh-hk':'打開文章清單','zh-tw':'開啟文章列表'},
'Start Article 1': {'zh-hk':'開始第 1 篇','zh-tw':'開始第 1 篇'},
'Open the first lesson and begin practicing respectful greetings for Manila.': {'zh-hk':'打開第一課,開始練習去馬尼拉時用得着嘅禮貌問候語。','zh-tw':'開啟第一課,開始練習前往馬尼拉時能使用的禮貌問候語。'},
}
def localize(text, lang, cfg):
# html lang
text = re.sub(r'<html lang="en"|<html lang=\'en\'', lambda m: m.group(0).replace('en', cfg['html_lang']), text)
# common fixed strings
text = text.replace('Tagalog | AWS Community Day', cfg['brand'])
text = text.replace('Dark mode · Mobile responsive · 24 articles', cfg['pill'])
text = text.replace('Dark mode · Static HTML', cfg['static_pill'])
text = text.replace('Articles List', cfg['articles_list'])
text = text.replace('Community Day', cfg['category_community'])
text = text.replace('Friendship', cfg['category_friendship'])
# article titles
for en, m in article_title_map.items():
text = text.replace(en, m[lang])
for en, m in summary_map.items():
text = text.replace(en, m[lang])
for en, m in common_replacements.items():
text = text.replace(en, m[lang])
# Replacements for info boxes
text = re.sub(r'<strong>Respect:</strong><br>Use po, opo, kayo, and ninyo for elders, speakers, organizers, volunteers, venue staff, and first-time contacts\.', f'<strong>{cfg["respect"]}</strong><br>{cfg["respect_text"]}', text)
text = re.sub(r'<strong>Practice:</strong><br/?>Read the natural version first, then the polite version, then try the Filipino-English bridge\.', f'<strong>{cfg["practice"]}</strong><br>{cfg["practice_text"]}', text)
text = re.sub(r'<strong>Culture:</strong><br>Small words can carry warmth, humility, gratitude, patience, and a feeling of belonging\.', f'<strong>{cfg["culture"]}</strong><br>{cfg["culture_text"]}', text)
# Landing intro
text = text.replace('This static learning site includes 24 article pages, each with 40 sentence cards for Tagalog practice, cultural context, respectful speech, and playful Filipino-English.', cfg['intro'])
# Add localization note in hero before closing first hero section if article page
note_html = f'<p class="notice">{cfg["note"]}</p>'
if cfg['note'] not in text:
text = text.replace('</section>\n <button class="mobile-menu-button"', note_html + '\n </section>\n <button class="mobile-menu-button"', 1)
text = text.replace('</section><button class=\'mobile-menu-button\'', note_html + '</section><button class=\'mobile-menu-button\'', 1)
# Meta description charset title handled via title replacements
return text
created = []
for lang, cfg in langs.items():
folder = Path(cfg['folder'])
if folder.exists():
shutil.rmtree(folder)
folder.mkdir()
for src in source_files:
text = src.read_text(encoding='utf-8')
localized = localize(text, lang, cfg)
(folder / src.name).write_text(localized, encoding='utf-8')
zip_path = Path(cfg['zip'])
if zip_path.exists():
zip_path.unlink()
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
for p in sorted(folder.glob('*.html'), key=lambda x: (x.name != 'index.html', x.name)):
z.write(p, arcname=f"{folder.name}/{p.name}")
created.append(str(zip_path))
# Secondary consistency checks
checks = {}
for lang, cfg in langs.items():
folder = Path(cfg['folder'])
html_files = list(folder.glob('*.html'))
checks[lang] = {
'zip_exists': Path(cfg['zip']).exists(),
'html_count': len(html_files),
'article_pages': len(list(folder.glob('article-*.html'))),
'index_exists': (folder/'index.html').exists(),
'lang_marker_count': sum(cfg['html_lang'] in p.read_text(encoding='utf-8') for p in html_files),
'mobile_css_pages': sum('@media (max-width: 860px)' in p.read_text(encoding='utf-8') for p in html_files),
'sentence_card_counts': {p.name: p.read_text(encoding='utf-8').count('class="sentence-card"') for p in folder.glob('article-*.html')},
'zip_size': Path(cfg['zip']).stat().st_size,
}
print(json.dumps({'created': created, 'checks': checks}, ensure_ascii=False, indent=2))
第 4 部分:从结构化内容生成本地化站点
目标
直接从结构化数据构建本地化页面,而不是只在 HTML 中替换字符串。
开发技能
这是 structured-generation 方法。生成器不是从现有 HTML 开始,而是从本地化文章元数据、句子卡片数据、语法辅助函数、示例、CSS 和模板开始。
数据模型
lang_cfgs:
- 语言级 UI 和标签契约
articles_base:
- slug
- category
- 每种语言的本地化标题和摘要
comm_cards:
- 中文含义
- 自然 Tagalog
- 礼貌 Tagalog
- 友好的 Filipino-English
- 有趣的 Filipino-English
- 语气
friendship_cards:
- 中文含义
- 自然 Tagalog
- 礼貌 Tagalog
- 友好的 Filipino-English
- 有趣的 Filipino-English
- 语气
Prompt
从结构化数据生成本地化静态 HTML 页面。
对每种语言:
- 创建 index.html
- 创建 24 个文章页
- 每个文章页必须有 40 张句子卡片
- 使用本地化文章标题和摘要
- 使用本地化句子卡片标签
- 保留 Tagalog 文本
- 将输出打包为 ZIP
示例句子卡片数据
中文含义:我可以问一个问题吗?
自然 Tagalog:Umupo ka sandali kung nahihilo ka.
礼貌 Tagalog:Umupo po kayo sandali kung nahihilo kayo.
友好的 Filipino-English:Can I ask po?
有趣的 Filipino-English:Magtatanong na ako, all right.
语气:礼貌提问、工作坊
结果
生成出的本地化站点比原始替换更稳定,因为源数据本身就是多语言的。如果文章标题需要修改,开发者更新 articles_base;如果标签需要调整,开发者更新 lang_cfgs;如果短语需要改进,开发者更新 comm_cards 或 friendship_cards。
提示
- 长期维护优先使用结构化生成。
- 只把替换映射用于快速修补。
- 将本地化标题和摘要数据靠近文章元数据存放。
- 所有语言使用同一个卡片渲染器。
- 所有语言版本复用同一套 CSS 和 JavaScript。
本节说明第二条本地化路径:直接从文章元数据、句子卡片数组、语法辅助函数、示例、CSS、JavaScript 和模板生成本地化站点。
结构化生成器循环
从 lang_content.py 增加结构化生成循环。它适合长期生成路径,因为每个文章页都由文章元数据、语言配置和可复用的 article_page 渲染器生成。
for i,(slug,cat,trans) in enumerate(articles_base, start=1):
(folder/f'article-{i}.html').write_text(
article_page(i,slug,cat,trans,lang_key,cfg),
encoding='utf-8'
)
附件实现示例:卡片渲染模式
同一次更新还包含句子卡片渲染循环。它展示了每篇生成文章如何在复用基础句子数据的同时,稳定地产生 40 张练习卡片。
for n in range(1,41):
cards.append(card_html(n, bases[(n-1)%len(bases)]))
结构化本地化站点生成器
本节描述结构化内容生成:它从语言配置、本地化文章元数据、可复用句子卡片数组、语法辅助函数、示例、CSS、模板、ZIP 打包和验证检查重建本地化页面。它补充了 lang_ui.py:lang_ui.py 修补现有 HTML,而 lang_content.py 从结构化多语言数据生成本地化 HTML。
from pathlib import Path
import zipfile, shutil, json, html, re
lang_cfgs = {
'zh_hk': {
'zip':'tagalog_static_html_site_cantonese_zh_hk.zip','folder':'tagalog_zh_hk','html_lang':'zh-HK',
'site':'AWS 馬尼拉 Community Day Tagalog 學習站','list':'文章清單','open':'打開文章清單','choose':'揀文章','start':'開始學習','note':'廣東話版本:介面、說明、語法提示同學習語境用廣東話;Tagalog 句子保留原文,方便直接朗讀練習。',
'labels':['背景情境','中文意思','自然 Tagalog','禮貌 Tagalog','友善 Filipino-English','玩味 Filipino-English','語氣','文化語境','語法拆解','常見語境同用法','基本例子','發音指南','相關句型'],
'cat_comm':'Community Day','cat_friendship':'浪漫句子','sentence':'句子','article':'第 {n} / 10 篇','cards':'40 張句子卡',
'respect':'同講者、主辦、義工、長輩或者第一次見面嘅人講嘢時,優先用 po、opo、kayo、ninyo 表示尊重。',
'casual':'同熟朋友或者 peers 傾偈時,可以用自然版本或者玩味版本。',
},
'zh_tw': {
'zip':'tagalog_static_html_site_taiwan_chinese_zh_tw.zip','folder':'tagalog_zh_tw','html_lang':'zh-TW',
'site':'AWS 馬尼拉社群日 Tagalog 學習站','list':'文章列表','open':'開啟文章列表','choose':'選擇文章','start':'開始學習','note':'繁體中文(台灣)版本:介面、說明、語法提示與學習情境為繁體中文;Tagalog 句子保留原文,方便朗讀練習。',
'labels':['背景情境','中文意思','自然 Tagalog','禮貌 Tagalog','友善 Filipino-English','有趣 Filipino-English','語氣','文化情境','語法拆解','常見情境與用法','基礎例子','發音指南','相關句型'],
'cat_comm':'社群日','cat_friendship':'浪漫句子','sentence':'句子','article':'第 {n} / 10 篇','cards':'40 張句子卡',
'respect':'面對講者、主辦單位、志工、長輩或第一次見面的人時,優先使用 po、opo、kayo、ninyo 表示尊重。',
'casual':'與熟悉朋友或夥伴交流時,可以使用自然版本或有趣版本。',
}
}
# Titles and summaries per language
articles_base = [
('community-greetings-introductions','comm',{
'zh_hk':('Community Day:問候同禮貌自我介紹','練習禮貌問候、姓名介紹、初學短句同自然開場,幫你喺馬尼拉自信認識講者、義工、學生同 builders。'),
'zh_tw':('社群日:問候與禮貌自我介紹','練習禮貌問候、姓名介紹、初學短句與溫暖開場,協助你在馬尼拉自信認識講者、志工、學生與 builders。')}),
('community-directions-registration','comm',{
'zh_hk':('Community Day:方向、場地同登記','學習場地實用句,涵蓋登記、房間、座位、洗手間、出口、排隊、名牌同禮貌求助。'),
'zh_tw':('社群日:方向、會場與報到','學習會場實用短句,涵蓋報到、房間、座位、洗手間、出口、排隊、名牌與禮貌求助。')}),
('community-workshops-learning','comm',{
'zh_hk':('Community Day:Workshop 提問同實作學習','用禮貌 Tagalog 練習 workshop 提問、請人重複、討論 demo、serverless 諗法、AI 方案、cloud lab 同學習目標。'),
'zh_tw':('社群日:工作坊提問與實作學習','用禮貌 Tagalog 練習工作坊提問、請對方重複、討論 demo、serverless 想法、AI 解決方案、雲端實驗與學習目標。')}),
('community-networking-belonging','comm',{
'zh_hk':('Community Day:Networking、連結同歸屬感','用友善句子連結 developers、學生、leaders 同 peers,同時表達興奮、謙遜、感謝、歸屬感同尊重。'),
'zh_tw':('社群日:交流、連結與歸屬感','使用友善短句連結開發者、學生、領導者與夥伴,同時表達興奮、謙遜、感謝、歸屬感與尊重。')}),
('community-volunteers-time-goodbye','comm',{
'zh_hk':('Community Day:義工、等候、時間同道別','用實用活動句感謝義工、處理等候時間、禮貌道歉、協調到達、溫暖道別,同尊重時間表。'),
'zh_tw':('社群日:志工、等待、時間與道別','用實用活動短句感謝志工、管理等待時間、禮貌道歉、協調抵達、溫暖道別,並尊重時程安排。')}),
('friendship-one-question-one-answer','rom',{
'zh_hk':('浪漫 Tagalog:一個問題,一個溫柔答案','練習關於真誠、答案、希望、勇氣同真實感受嘅原創浪漫問題,同時避免複製受版權保護歌詞。'),
'zh_tw':('浪漫 Tagalog:一個問題,一個溫柔答案','練習關於真誠、答案、希望、勇氣與真實感受的原創浪漫問題,同時避免複製受著作權保護的歌詞。')}),
('friendship-friends-only-feelings','rom',{
'zh_hk':('浪漫 Tagalog:原來只係朋友嘅感覺','學習關於友情、不確定、希望、界線同情感清晰度嘅溫柔句,用尊重、柔和、初學者友善嘅 Tagalog 句型。'),
'zh_tw':('浪漫 Tagalog:只是朋友的心情','學習關於友情、不確定、希望、界線與情感清晰度的溫柔短句,使用尊重、柔和、適合初學者的 Tagalog 句型。')}),
('friendship-you-are-my-beloved','rom',{
'zh_hk':('浪漫 Tagalog:你係我珍惜嘅人','探索關於關心、情感、信任、支持同選擇某人嘅原創詩意句,用溫暖但簡單嘅 Tagalog 表達。'),
'zh_tw':('浪漫 Tagalog:你是我珍愛的人','探索關於關心、情感、信任、支持與選擇某人的原創詩意短句,用溫暖但簡單的 Tagalog 表達。')}),
('friendship-honest-heart-deep-feelings','rom',{
'zh_hk':('浪漫 Tagalog:真心同深層感受','建立關於真實情緒、緊張嘅心、真誠欣賞、安靜掛念同小心告白嘅自然禮貌 Tagalog 詞彙。'),
'zh_tw':('浪漫 Tagalog:真誠的心與深層感受','建立關於真實情緒、緊張的心、真誠欣賞、安靜思念與謹慎告白的自然禮貌 Tagalog 詞彙。')}),
('friendship-distance-hope-promises','rom',{
'zh_hk':('浪漫 Tagalog:距離、希望同承諾','練習關於距離、等候、承諾、南北、希望、耐性同穩定情感嘅原創句。'),
'zh_tw':('浪漫 Tagalog:距離、希望與承諾','練習關於距離、等待、承諾、南北、希望、耐心與穩定情感的原創短句。')}),
]
comm_cards = [
('谢谢你的帮助。','Magpahinga muna tayo sa lilim.','Magpahinga muna po tayo sa lilim.','Rest muna tayo sa shade po.','Shade break muna tayo, all right.','health, safety, daily practice'),
('我可以问一个问题吗?','Umupo ka sandali kung nahihilo ka.','Umupo po kayo sandali kung nahihilo kayo.','Sit muna po kung dizzy.','Umupo muna, easy lang all right.','health advice, care'),
('我已经到了。','Paki-check kung pumasok ang bayad.','Paki-check po kung pumasok ang bayad.','Please check po kung pumasok ang payment.','Payment check muna, all right.','payment, polite request'),
('请慢一点。','Pakibagalan.','Pakibagalan po.','Slow down po please.','Slow lang po, catching up all right.','学习支持、礼貌'),
('我正在学习 Tagalog。','Nag-aaral ako ng Tagalog.','Nag-aaral po ako ng Tagalog.','Learning Tagalog po ako.','Learning Tagalog na ako, all right.','自我介绍、友善'),
('我很兴奋参加社区日。','Excited akong sumali sa Community Day.','Excited po akong sumali sa Community Day.','Excited po ako for Community Day.','Excited na ako, all right.','兴奋、归属感'),
('洗手间在哪里?','Saan ang restroom?','Saan po ang restroom?','Saan po ang CR?','Saan ang CR, quick lang all right.','方向、实用'),
('下一场什么时候开始?','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 all right.','等待、协调'),
('再见,下次见。','Kita tayo sa susunod.','Kita po tayo sa susunod.','See you po next time.','Kita-kits next time, all right.','道别、温暖'),
]
friendship_cards = [
('Thank you for being my safe friend.','Salamat sa pagiging ligtas kong kaibigan.','Salamat sa pagiging ligtas ko pong kaibigan.','Thank you po for being my safe friend.','Safe friend ka, thank you all right.','gentle friendship support'),
('我希望你理解我。','Sana maintindihan mo ako.','Sana po maintindihan ninyo ako.','Hope po you understand me.','Sana gets mo ako, all right.','希望、柔和'),
('我会带着尊重倾听。','Makikinig ako nang may respeto.','Makikinig po ako nang may respeto.','Listen po ako with respect.','Makikinig ako, respect mode all right.','成熟、尊重'),
('我的感受是真诚的。','Tapat ang damdamin ko.','Tapat po ang damdamin ko.','Sincere po feelings ko.','Tapat ang damdamin ko, all right.','情感、诗意'),
('我不会强迫你的感受。','Hindi ko pipilitin ang damdamin mo.','Hindi ko po pipilitin ang damdamin ninyo.','No pressure po sa feelings.','No forcing feelings, respectful all right.','界线、尊重'),
('我的心正在学习勇敢。','Natututong maging matapang ang puso ko.','Natututo pong maging matapang ang puso ko.','Heart ko learning bravery po.','Heart bravery training all right.','脆弱、希望'),
('不管距离多远,我仍然关心。','Kahit malayo, nagmamalasakit ako.','Kahit malayo po, nagmamalasakit po ako.','Even far po, I care.','Malayo man, care pa rin all right.','距离、关心'),
('请温柔地接受我的话。','Tanggapin mo sana nang malumanay ang mga salita ko.','Tanggapin po sana ninyo nang malumanay ang mga salita ko.','Receive po my words gently.','Words ko gentle delivery all right.','温柔、告白'),
('我会耐心等待。','Maghihintay ako nang may pasensya.','Maghihintay po ako nang may pasensya.','Wait po ako with patience.','Waiting patiently all right.','等待、耐心'),
('再见,愿你平安。','Paalam, sana payapa ka.','Paalam po, sana po ay payapa kayo.','Goodbye po, peace sana.','Paalam, peaceful ending all right.','道别、诗意'),
]
# Per language localized grammar words
def grammar_items(card, cfg_key):
cn = {
'zh_hk': [('po','禮貌標記,常用嚟表示尊重。'),('kayo / ninyo','禮貌或眾數嘅「你 / 你哋 / 您的」。'),('na / all right','表示「已經 / 而家」,玩味版本會加 all right 做輕鬆語氣。')],
'zh_tw': [('po','禮貌標記,常用來表示尊重。'),('kayo / ninyo','禮貌或複數的「你 / 你們 / 您的」。'),('na / all right','表示「已經 / 現在」,有趣版本會加 all right 作輕鬆語氣。')],
}
return cn[cfg_key]
def examples(is_comm, cfg_key):
if is_comm:
ex = {
'zh_hk':[('Magpahinga muna tayo sa lilim.','多謝你幫忙。'),('Umupo ka sandali kung nahihilo ka.','我可唔可以問一個問題?'),('Paki-check po kung pumasok ang bayad.','我已經到咗。')],
'zh_tw':[('Magpahinga muna tayo sa lilim.','謝謝你的幫忙。'),('Umupo ka sandali kung nahihilo ka.','我可以問一個問題嗎?'),('Paki-check po kung pumasok ang bayad.','我已經到了。')],
}
else:
ex = {
'zh_hk':[('Sana maintindihan mo ako.','我希望你明白我。'),('Makikinig po ako nang may respeto.','我會帶住尊重去聽。'),('Tapat ang damdamin ko, all right.','我嘅感受已經好真誠。')],
'zh_tw':[('Sana maintindihan mo ako.','我希望你理解我。'),('Makikinig po ako nang may respeto.','我會帶著尊重傾聽。'),('Tapat ang damdamin ko, all right.','我的感受已經很真誠。')],
}
return ex[cfg_key]
def card_html(num, base, is_comm, cfg, lang_key):
l = cfg['labels']; meaning,nat,pol,friendly,playful,tone = base
g = ''.join(f"<li><strong>{html.escape(k)}:</strong> {html.escape(v)}</li>" for k,v in grammar_items(base, lang_key))
ex = ''.join(f"<li><strong>Tagalog:</strong> {html.escape(tl)}<br><strong>中文:</strong> {html.escape(cn)}</li>" for tl,cn in examples(is_comm, lang_key))
use = cfg['respect'] + ' ' + cfg['casual']
pron = 'Tagalog 母音可穩定朗讀:a=ah, e=eh, i=ee, o=oh, u=oo;po 輕聲,句尾保持清楚。'
if lang_key=='zh_hk': pron='Tagalog 母音可以穩定咁讀:a=ah, e=eh, i=ee, o=oh, u=oo;po 輕聲,句尾保持清楚。'
return f"""<article class='sentence-card' id='sentence-{num}'><h3>{cfg['sentence']} {num}</h3><dl class='template'>
<dt>{l[0]}:</dt><dd>{'活動現場練習句' if is_comm else '情感表達練習句'},用來比較自然、禮貌與 playful 語氣。</dd>
<dt>{l[1]}:</dt><dd>{html.escape(meaning)}</dd>
<dt>{l[2]}:</dt><dd lang='tl'>{html.escape(nat)}</dd>
<dt>{l[3]}:</dt><dd lang='tl'>{html.escape(pol)}</dd>
<dt>{l[4]}:</dt><dd>{html.escape(friendly)}</dd>
<dt>{l[5]}:</dt><dd>{html.escape(playful)}</dd>
<dt>{l[6]}:</dt><dd>{html.escape(tone)}</dd>
<dt>{l[7]}:</dt><dd>{html.escape(use)}</dd>
<dt>{l[8]}:</dt><dd><ul>{g}</ul></dd>
<dt>{l[9]}:</dt><dd>{'適合報到、工作坊、問路、感謝義工、等待協調與道別。' if is_comm else '適合學習柔和、尊重、有界線的情感表達。'}</dd>
<dt>{l[10]}:</dt><dd><ol>{ex}</ol></dd>
<dt>{l[11]}:</dt><dd>{html.escape(pron)}</dd>
<dt>{l[12]}:</dt><dd>{'先學自然 Tagalog,再學禮貌版本,最後才使用 playful Filipino-English。' if lang_key!='zh_hk' else '先學自然 Tagalog,再學禮貌版本,最後先用玩味 Filipino-English。'}</dd>
</dl></article>"""
created=[]
checks={}
for lang_key,cfg in lang_cfgs.items():
folder=Path(cfg['folder'])
if folder.exists(): shutil.rmtree(folder)
folder.mkdir()
(folder/'index.html').write_text(index_page(lang_key,cfg), encoding='utf-8')
for i,(slug,cat,trans) in enumerate(articles_base, start=1):
(folder/f'article-{i}-{slug}.html').write_text(article_page(i,slug,cat,trans,lang_key,cfg), encoding='utf-8')
zip_path=Path(cfg['zip'])
if zip_path.exists(): zip_path.unlink()
with zipfile.ZipFile(zip_path,'w',zipfile.ZIP_DEFLATED) as z:
for p in sorted(folder.glob('*.html'), key=lambda x:(x.name!='index.html', x.name)):
z.write(p, arcname=f"{folder.name}/{p.name}")
created.append(cfg['zip'])
html_files=list(folder.glob('*.html'))
checks[lang_key]={
'zip':cfg['zip'], 'zip_exists':zip_path.exists(), 'html_count':len(html_files),
'article_count':len(list(folder.glob('article-*.html'))),
'mobile_css_pages':sum('@media (max-width:860px)' in p.read_text(encoding='utf-8') for p in html_files),
'article_card_counts': {p.name:p.read_text(encoding='utf-8').count("class='sentence-card'") for p in folder.glob('article-*.html')},
'zip_size':zip_path.stat().st_size
}
print(json.dumps({'created':created,'checks':checks}, ensure_ascii=False, indent=2))
覆盖内容:
- UI 本地化与内容本地化的区别
- 粤语和台湾中文语言配置
zh-HK和zh-TW的 html lang 处理- 从现有 HTML 出发的 replacement-map 本地化流程
- 使用
lang_cfgs、articles_base、comm_cards和friendship_cards的结构化内容生成 - 在本地化中文说明的同时,将 Tagalog 保留为学习目标
- 静态 HTML 渲染、ZIP 打包和验证检查
- 技术分享现场笔记和审核检查
现场笔记 1:UI 本地化不是内容本地化
背景:第一个脚本主要关注 UI 替换:品牌文本、按钮、分类、标签、文章标题、摘要和通用教学文本。
目标:在保留现有站点结构的同时翻译可见界面。
Prompt:为现有 HTML 页面创建语言字典和替换映射。
结果:应用可以快速从现有 HTML 源生成两个本地化 ZIP 文件。
审核检查:所有导航标签、文章标题、摘要和句子卡片标签是否都已本地化?
现场笔记 2:结构化生成更适合长期维护
背景:替换映射可行,但依赖精确源字符串。当源 HTML 变化时,替换可能静默失败。
目标:把重要内容迁移到结构化多语言数据中。
Prompt:从 lang_cfgs、articles_base、comm_cards、friendship_cards、辅助函数和模板构建本地化页面。
结果:本地化内容位于可预测的数据结构中,生成器更易维护。
审核检查:开发者是否可以不搜索 HTML 就更新一个文章标题?
现场笔记 3:保留学习目标
背景:普通本地化工作流可能会翻译所有内容。语言学习应用不应该这样做。
目标:保留 Tagalog 可见,同时翻译其周围的中文说明。
Prompt:在每张句子卡片中保留自然 Tagalog、礼貌 Tagalog、友好的 Filipino-English 和有趣的 Filipino-English。
结果:学习者仍然可以练习目标语言,同时用自己偏好的中文变体理解说明。
审核检查:每张句子卡片是否仍显示 Tagalog 文本?
现场笔记 4:本地化语气需要本地化表达
背景:粤语和台湾中文不只是书写风格不同,也可能在语气、词汇和节奏上不同。
目标:让每个本地化版本对目标读者来说都自然。
Prompt:zh-HK 使用贴近香港读者的粤语风格措辞,zh-TW 使用台湾繁体中文。
结果:三个站点像独立本地化产品,而不是同一个通用中文转换。
审核检查:粤语版本是否使用了 揀文章、打開文章清單 和 用嚟練 等适合香港读者的表达?
现场笔记 5:验证是本地化的一部分
背景:本地化构建可能因缺失文件、缺失移动端 CSS、文章页不完整或语言标记损坏而失败。
目标:把本地化视为构建产物,而不只是翻译任务。
Prompt:打包每个本地化文件夹并打印 JSON 检查。
结果:开发者可以证明每个本地化版本都包含 25 个 HTML 文件、24 个文章页、移动端 CSS,并且每篇文章有 40 张卡片。
审核检查:构建输出是否显示所有三个语言版本的预期数量?
技术分享角度
对开发者演讲来说,最重要的经验是:本地化可以被工程化为一条流水线:
语言配置
↓
文章元数据
↓
句子卡片数据
↓
本地化标签和示例
↓
静态 HTML 渲染器
↓
按语言区分的文件夹
↓
ZIP 打包
↓
JSON 验证检查
这条流程易于解释、易于 demo,也易于迁移到其他教育应用。
收尾反思
这个本地化项目中的深层技能,不只是把英文翻译成中文。真正的技能是设计一套可重复的本地化系统。项目将 UI 文本与学习内容分离,将 Tagalog 保留为目标语言,为每个中文变体赋予自己的表达风格,复用同一个响应式静态布局,独立打包每个版本,并验证输出。因此,最终结果足够扎实,可以用于技术分享。
