尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ppt-master 多语言文本导出冒烟测试:BCP-47 语言标签到 DrawingML rtl 契约的全链路验证

ppt-master 多语言文本导出冒烟测试:BCP-47 语言标签到 DrawingML rtl 契约的全链路验证 ppt-master 多语言文本导出冒烟测试BCP-47 语言标签到 DrawingML rtl 契约的全链路验证【免费下载链接】ppt-masterAI turns documents or topics into real, native PowerPoint decks—with native shapes, transitions and animations,>项目地址: https://gitcode.com/GitHub_Trending/ppt/ppt-master本文围绕 multilingual-text-smoke.md 讲解 ppt-master 的多语言文本维护冒烟测试一条可复制的 heredoc 命令在临时目录中串联 Confirm UI 语言归一化、spec_lock.md执行锁、SVG→DrawingML 文本导出、原生表格/图表、演讲者备注与docProps/core.xml元数据验证从 BCP-47 语言标签到 PPTX 包内lang/rtl属性的完整契约。读完并亲手跑通后你能理解该项目执行锁是唯一导出语言源的设计以及段落级rtl1、run 级a:rtl的双层 RTL 契约从而在改动语言相关代码时知道如何自检。冒烟测试的定位与运行方式这是一份手动冒烟manual smoke不是自动化测试套件——文档开篇即说明它只使用临时文件tempfile.TemporaryDirectory不会向仓库新增任何测试资产。适用时机是修改了以下任一模块之后Confirm UI 的语言处理Stage 1 推荐与确认流程DrawingML 文本导出SVG 文本 → 原生a:rPr原生表格 / 原生图表的构建演讲者备注notes slide文档元数据docProps/core.xml。从仓库根目录直接执行注意PYTHONPATH同时包含scripts与confirm_ui两个目录脚本内会 import 二者PYTHONPATHskills/ppt-master/scripts:skills/ppt-master/scripts/confirm_ui python3 - PY import json import zipfile from pathlib import Path from tempfile import TemporaryDirectory from xml.etree import ElementTree as ET from pptx import Presentation from confirm_ui.server import create_app from language_tags import ( LanguageTagError, language_uses_rtl, normalize_language_tag, ) from svg_to_pptx.drawingml.context import ConvertContext from svg_to_pptx.drawingml.converter import convert_svg_to_slide_shapes from svg_to_pptx.native_objects import _build_native_chart from svg_to_pptx.native_objects.table import _build_native_table from svg_to_pptx.pptx_package.builder import create_pptx_with_native_svg from svg_to_pptx.pptx_package.cli import _declared_primary_language from svg_to_pptx.pptx_package.notes import create_notes_slide_xml def reject_language(value): try: normalize_language_tag(value) except LanguageTagError: return raise AssertionError(finvalid language accepted: {value}) canonical { ES_mx: es-MX, RU_ru: ru-RU, AR_sa: ar-SA, HE_il: he-IL, HI_in: hi-IN, TH_th: th-TH, KO_kr: ko-KR, fil_ph: fil-PH, zh_hans: zh-Hans, de-CH-1901: de-CH-1901, en-u-nu-latn: en-u-nu-latn, } for raw, expected in canonical.items(): assert normalize_language_tag(raw) expected for raw in (und, zh, en--US, Arabic, x-private): reject_language(raw) assert language_uses_rtl(ar-Arab-SA) assert not language_uses_rtl(ar-Latn-SA) assert language_uses_rtl(en-Arab-US) samples { en-US: Summary 2026, zh-Hans: 年度总结 2026, ja-JP: 年間まとめ 2026, ko-KR: 연간 요약 2026, es-ES: Resumen 2026, ru-RU: Итоги 2026, ar-SA: ملخص 2026, he-IL: סיכום 2026, hi-IN: सारांश 2026, th-TH: สรุป 2026, } rtl_languages {ar-SA, he-IL} with TemporaryDirectory(prefixppt-master-multilingual-smoke-) as tmp: root Path(tmp) # Confirm UI canonicalizes Stage 1 and persists the same project language. project root / confirm-project confirm project / confirm_ui confirm.mkdir(parentsTrue) recommendation { stage: stage1, lang: en, primary_language: AR_sa, audience: {value: Team}, communication_intent: {value: Explain}, audience_outcome: {value: Understand}, core_message: {value: Result}, delivery_context: {value: Meeting}, artifact_afterlife: {value: }, content_divergence: {value: }, recommend: {canvas: ppt169}, } (confirm / recommendations.stage1.json).write_text( json.dumps(recommendation), encodingutf-8, ) (confirm / template_options.json).write_text( json.dumps( { schema_version: 1, phase: template, default_mode: free_design, explicit_workspace_roots: [], } ), encodingutf-8, ) app create_app(str(project), idle_timeout0) app.testing True client app.test_client() response client.get(/api/recommendations) assert response.status_code 200 assert response.get_json()[primary_language] ar-SA response client.post( /api/confirm, json{ stage: stage1, template_selection: { mode: free_design, selection_keys: [], }, primary_language: ar-SA, canvas: ppt169, audience: Team, communication_intent: Explain, audience_outcome: Understand, core_message: Result, delivery_context: Meeting, artifact_afterlife: , content_divergence: , }, ) assert response.status_code 200 result json.loads((confirm / result.json).read_text(encodingutf-8)) assert result[primary_language] ar-SA # The execution lock is the only export-time project-language source. lock_project root / lock-project lock_project.mkdir() lock_template # Execution Lock ## communication {language}- audience: team - objective: explain - core_message: result lock lock_project / spec_lock.md lock.write_text( lock_template.format(language- primary_language: ES_mx\n), encodingutf-8, ) assert _declared_primary_language(lock_project) es-MX lock.write_text(lock_template.format(language), encodingutf-8) assert _declared_primary_language(lock_project) is None for index, (language, sample) in enumerate(samples.items(), 1): stem fslide-{index} svg root / f{stem}.svg svg.write_text( svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 1280 720 rect x0 y0 width1280 height720 fill#FFFFFF/ ftext x100 y140 font-size42 ffont-familyArial fill#111111{sample}/text /svg, encodingutf-8, ) slide_xml, *_ convert_svg_to_slide_shapes( svg, index, verboseFalse, primary_languagelanguage, ) assert flang{language} in slide_xml assert (rtl\1\ in slide_xml) (language in rtl_languages) assert (a:rtl val\1\/ in slide_xml) ( language in rtl_languages ) ascii_svg root / f{stem}-ascii.svg ascii_svg.write_text( svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 1280 720 text x100 y140 font-size422026 AI/text /svg, encodingutf-8, ) ascii_xml, *_ convert_svg_to_slide_shapes( ascii_svg, index 20, verboseFalse, primary_languagelanguage, ) assert flang{language} in ascii_xml assert rtl1 not in ascii_xml assert a:rtl val\1\/ not in ascii_xml context ConvertContext(primary_languagelanguage) marker ET.Element(g) table _build_native_table( marker, context, { schema: ppt-master.semantic-table.v2, x: 10, y: 10, width: 600, height: 180, columns: [sample, 2026 AI], rows: [[A, B]], style: {font_family: Arial}, }, ) assert flang{language} in table.xml assert all(slot in table.xml for slot in (a:latin , a:ea , a:cs )) chart_context ConvertContext(primary_languagelanguage) _build_native_chart( marker, chart_context, { x: 10, y: 220, width: 600, height: 300, type: column, title: sample, categories: [A, B], series: [{name: sample, values: [1, 2]}], style: {font_family: Arial}, show_legend: True, }, ) chart_xml next( value.decode(utf-8) for part, value in chart_context.package_files.items() if part.startswith(ppt/charts/chart) and not part.endswith(.rels) ) assert fc:lang val{language}/ in chart_xml assert flang{language} in chart_xml notes_xml create_notes_slide_xml( 1, sample \n2026 AI, language, ) assert flang{language} in notes_xml assert (rtl\1\ in notes_xml) (language in rtl_languages) output root / f{index}.pptx assert create_pptx_with_native_svg( [svg], output, canvas_formatppt169, verboseFalse, transitionNone, notes{stem: sample \n2026 AI}, pptx_structureflat, structure_namemultilingual-smoke, primary_languagelanguage, ) with zipfile.ZipFile(output) as archive: assert archive.testzip() is None packaged_slide archive.read( ppt/slides/slide1.xml ).decode(utf-8) packaged_notes archive.read( ppt/notesSlides/notesSlide1.xml ).decode(utf-8) core archive.read(docProps/core.xml).decode(utf-8) assert flang{language} in packaged_slide assert flang{language} in packaged_notes assert fdc:language{language}/dc:language in core assert len(Presentation(str(output)).slides) 1 # A legacy lock with no language field keeps the previous per-run path. legacy_svg root / legacy.svg legacy_svg.write_text( svg xmlnshttp://www.w3.org/2000/svg viewBox0 0 1280 720 text x100 y140 font-size42한국어 2026/text /svg, encodingutf-8, ) legacy_output root / legacy.pptx assert create_pptx_with_native_svg( [legacy_svg], legacy_output, canvas_formatppt169, verboseFalse, transitionNone, pptx_structureflat, structure_namelegacy-smoke, ) with zipfile.ZipFile(legacy_output) as archive: assert archive.testzip() is None legacy_slide archive.read( ppt/slides/slide1.xml ).decode(utf-8) assert langko-KR in legacy_slide print(Multilingual text smoke: passed) PY预期输出只有一行Multilingual text smoke: passed任何一条assert失败即代表某条语言契约被破坏应停止合入对应改动。第一环BCP-47 语言标签的归一化与拒收规则冒烟脚本开头直接对 language_tags.py 的normalize_language_tag做正/反用例断言覆盖了两类输入归一化正向用例——ES_mx → es-MX、AR_sa → ar-SA、zh_hans → zh-Hans、de-CH-1901与en-u-nu-latn等带变体/扩展子标签的完整形态。对照源码可以确认其规则language_tags.py#L159-L265语言子标签统一小写_LANGUAGE_RE为 2–8 位字母下划线会被替换为连字符ES_mx→ES-mx→es-MX脚本script子标签转 Title Casehans→Hans区域region子标签转大写mx→MX变体variant5–8 位数字字母或数字3位与单字母扩展如u-nu-latn中的-u-扩展需按序合法且不可重复内置一组历史自然语言别名如Chinese、中文、日本語、한국어会被映射为规范标签见 _LANGUAGE_ALIASES特例zh必须带脚本或区域zh-Hans、zh-CN等裸zh会抛错。拒收反向用例——und未定语言不可用作 deck 主语言、裸zh、空子标签的en--US、纯自然语言名Arabic不在别名表内落入_NATURAL_LANGUAGE_NAMES黑名单被拒、无子标签的私有域x-private。RTL 判定同样在冒烟中显式验证了三行assert language_uses_rtl(ar-Arab-SA) # 显式 Arab 脚本 → RTL assert not language_uses_rtl(ar-Latn-SA) # 显式 Latin 脚本 → 非 RTL assert language_uses_rtl(en-Arab-US) # 英语内容但声明 Arab 脚本 → RTL对应实现见 language_uses_rtl优先看显式 script 子标签是否属于_RTL_SCRIPTSArab、Hebr、Thaa等 9 个没有脚本子标签时才回落到_RTL_BASE_LANGUAGESar、he、fa、ur等 11 个基础语言。这个脚本优先于语言的顺序正是ar-Latn-SA判为非 RTL 的原因。第二环Confirm UI 的语言归一化与持久化冒烟脚本在临时项目confirm-project/confirm_ui/下写入两份 JSON然后以 Flask test client 驱动 confirm_ui/server.pyrecommendations.stage1.json中故意声明primary_language: AR_sa非规范写法同时lang: en——这是刻意设置的对照lang只控制 Confirm UI 界面语言primary_language才是 deck 内容语言template_options.json声明default_mode: free_design满足 Stage 1 确认的模板选择前置条件。随后两条断言链验证的是同一个契约response client.get(/api/recommendations) assert response.get_json()[primary_language] ar-SA # 读取时被归一化 # POST /api/confirm 之后 result json.loads((confirm / result.json).read_text(encodingutf-8)) assert result[primary_language] ar-SA # 确认时持久化同一标签源码层面Stage 1 推荐缺失或不合法primary_language会直接报出错误信息 Stage 1 recommendations must declare a valid primary_language BCP-47 tag; lang controls only the Confirm UI language归一化入口是 _canonicalize_primary_language。这一步保证了用户看到的确认语言与最终写入导出链的语言始终是同一个规范标签。第三环spec_lock.md是导出期唯一的项目语言源文档注释写得很明确The execution lock is the only export-time project-language source. 冒烟脚本构造了两个执行锁场景调用 _declared_primary_languagelock.write_text(lock_template.format(language- primary_language: ES_mx\n)) assert _declared_primary_language(lock_project) es-MX # 锁内标签也被归一化 lock.write_text(lock_template.format(language)) assert _declared_primary_language(lock_project) is None # 无该字段 → None实现上它通过update_spec.parse_lock解析spec_lock.md的communication.primary_language字段值非空则归一化后返回字段缺失返回None值非法则升级为带上下文的LanguageTagError提示spec_lock.md communication.primary_language is invalid: ...。执行期行为在 cli.py 导出主流程 可见锁内声明了语言 → 直接作为primary_language传入构建管线锁内没有该字段 → 打印警告 spec_lock.md has no communication.primary_language; using legacy per-run language detection.走遗留的逐行脚本启发式见后文 legacy 段。执行锁模板可参考 spec_lock.md 脚手架。第四环SVG → DrawingML 文本导出的 lang 与 rtl 断言核心循环对 10 种语言en-US、zh-Hans、ja-JP、ko-KR、es-ES、ru-RU、ar-SA、he-IL、hi-IN、th-TH各生成一张 1280×720 的 SVG调用convert_svg_to_slide_shapes后断言assert flang{language} in slide_xml # run 级 lang assert (rtl\1\ in slide_xml) (language in rtl_languages) # 段落级 rtl assert (a:rtl val\1\/ in slide_xml) (language in rtl_languages) # run 级 a:rtl三层断言对应源码中的具体产出点a:rPr lang...由detect_text_lang(text, ctx.primary_language)解析——优先使用项目语言契约而不是逐段猜语言见 elements.py run 属性构建段落级a:pPr rtl1与 run 级a:rtl val1/分别由text_uses_rtl与text_has_rtl_characters决定utils.py前者按首个强方向字符判定段落方向无强方向字符时回落到language_uses_rtl(primary_language)后者只检查文本中是否含unicodedata.bidirectional为R/AL的强 RTL 字符字体三槽a:latin/、a:ea/、a:cs/始终成对出现在 run 属性中保证 CJKea 槽与复杂文种cs 槽覆盖阿拉伯、希伯来、天城文等有独立字体解析路径。ASCII 对照组是这套契约的关键细节同一语言下换一张只含2026 AI的 SVG断言lang{language}仍然成立语言跟着项目走但rtl1与a:rtl val1/均不得出现。也就是说 rtl 属性由文本实际内容 项目语言共同决定而不是简单地对 RTL 语言项目全局开启——这与 ConvertContext.primary_language 的注释一致None时保留legacy per-run script heuristic。第五环原生表格、原生图表与备注的语言注入冒烟脚本接着对每种语言构建语义表、柱状图与备注页验证语言标签渗透到 PPTX 的各个部件原生表格_build_native_tabletable _build_native_table(marker, context, { schema: ppt-master.semantic-table.v2, ... columns: [sample, 2026 AI], rows: [[A, B]], style: {font_family: Arial}, }) assert flang{language} in table.xml assert all(slot in table.xml for slot in (a:latin , a:ea , a:cs ))表格单元格文本的语言解析在 table.pyresolved_language language or detect_text_lang(text, default_language)即显式传入的ctx.primary_language优先、逐格检测结果兜底。原生图表_build_native_chart→chart_context.package_files中的ppt/charts/chartN.xml断言两个事实assert fc:lang val{language}/ in chart_xml # 图表部件级语言声明 assert flang{language} in chart_xml # 图表文本 run 级语言前者对应 chart_xml.py 中图表语言声明chart_language primary_language or en-US写入图表部件自身的c:lang元素供 PowerPoint 图表编辑器按该语言做校对与字体回退后者来自图表标题、轴标签等文本节点的detect_text_lang。备注页create_notes_slide_xml逐段应用方向判定非空段落用text_uses_rtl(para, primary_language)决定是否加a:pPr rtl1再用text_has_rtl_characters(para)决定 run 属性内是否追加a:rtl val1/空段落则按项目语言决定是否整段 RTL。未传语言时默认回落en-US。第六环打包后的 .pptx 包内校验对每种语言冒烟脚本通过create_pptx_with_native_svg产出真实pptx参数包括canvas_formatppt169、pptx_structureflat、notes{stem: ...}与primary_languagelanguage然后打开 zip 做四重检查with zipfile.ZipFile(output) as archive: assert archive.testzip() is None # 包完整性 ... assert flang{language} in packaged_slide # ppt/slides/slide1.xml assert flang{language} in packaged_notes # ppt/notesSlides/notesSlide1.xml assert fdc:language{language}/dc:language in core # docProps/core.xml assert len(Presentation(str(output)).slides) 1 # python-pptx 可读回dc:language的写入点在 builder.py 的 core 属性生成同时该构建函数会把primary_language透传给 notes masterbuilder.py 中create_notes_master_xml(primary_language)保证备忘记录版式占位符的语言属性与内容语言一致。最后用python-pptx的Presentation读回幻灯片数验证包不仅 XML 合法、且能被标准库打开。遗留路径无语言字段的旧锁脚本末尾验证向后兼容一张不含communication.primary_language的legacy 锁场景直接不传primary_language调用构建入口SVG 文本为한국어 2026最终slide1.xml中应出现assert langko-KR in legacy_slide这验证了遗留的逐行脚本启发式per-run script heuristic仍然工作——韩文Hangul文本在没有项目语言契约时被检出为ko-KR。与导出主流程的警告提示cli.py#L2400-L2406呼应旧项目、无锁快速生成仍可出片只是语言精度降级为逐行检测。RTL 方向契约为什么不用rtlCol文档最后一段定义了必须遵守的 RTL 契约这也是整套冒烟断言的语义基础段落方向用a:pPr rtl1run 级a:rtl val1/仅在该 run 含强 RTL 字符时输出不要使用rtlCol——它控制的是文本框内段落条目的列顺序不是段落文字方向。对照源码即可逐条对上段落级text_uses_rtl决定a:pPr的rtl属性notes 生成器 notes.py#L94-L102 与 DrawingML 转换器均如此run 级仅text_has_rtl_characters为真时追加a:rtl val1/elements.py#L2916-L2920所以英文项目里混入的阿拉伯语短语会在各自 run 上获得方向提示而纯拉丁文本 run 保持 LTRrtlCol全仓库未用于此目的——冒烟断言中若误加rtlColASCII 对照组不出现 rtl 的检查不会覆盖它但文档把它列为明确的反模式避免用列序开关冒充段落方向开关。何时运行与如何解读失败触发时机按文档约定改动 Confirm UI 语言处理、DrawingML 文本导出、原生表格/图表、notes 或文档元数据之后从仓库根目录运行该命令。成功标志是 stdout 最后一行Multilingual text smoke: passed且无AssertionError。常见失败定位路径失败断言优先排查文件归一化正/反用例language_tags.py/api/recommendations、result.json的primary_languageconfirm_ui/server.py_declared_primary_language取值pptx_package/cli.py、update_spec.py 的parse_lock幻灯片 XML 的lang/rtldrawingml/elements.py、drawingml/utils.py表格lang/三字体槽native_objects/table.pyc:lang或图表文本语言native_objects/chart_xml.pynotes 的lang/rtlpptx_package/notes.py包内slide1.xml、dc:languagepptx_package/builder.py小结这条冒烟测试把 ppt-master 的多语言导出契约压缩成了一次可执行的端到端检查BCP-47 归一化language_tags.py→ Confirm UI 持久化confirm_ui/server.py→ 执行锁声明spec_lock.md_declared_primary_language→ DrawingML 文本/原生表格/原生图表/备注的语言与方向属性 → 打包后 zip 内逐部件复核。理解这条链后改动任一环节时都能从失败的断言快速反推出契约被破坏的具体位置。【免费下载链接】ppt-masterAI turns documents or topics into real, native PowerPoint decks—with native shapes, transitions and animations,>项目地址: https://gitcode.com/GitHub_Trending/ppt/ppt-master创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表