# Multilingual Text Maintenance Smoke Run this manual smoke from the repository root after changing Confirm UI language handling, DrawingML text export, native tables/charts, notes, or document metadata. It uses only temporary files and does not add an automated test suite. ```bash PYTHONPATH="skills/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(f"invalid 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(prefix="ppt-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(parents=True) 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), encoding="utf-8", ) app = create_app(str(project), idle_timeout=0) 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", "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(encoding="utf-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"), encoding="utf-8", ) assert _declared_primary_language(lock_project) == "es-MX" lock.write_text(lock_template.format(language=""), encoding="utf-8") assert _declared_primary_language(lock_project) is None for index, (language, sample) in enumerate(samples.items(), 1): stem = f"slide-{index}" svg = root / f"{stem}.svg" svg.write_text( '' '' f'{sample}' "", encoding="utf-8", ) slide_xml, *_ = convert_svg_to_slide_shapes( svg, index, verbose=False, primary_language=language, ) assert f'lang="{language}"' in slide_xml assert ("rtl=\"1\"" in slide_xml) == (language in rtl_languages) assert ("" in slide_xml) == ( language in rtl_languages ) ascii_svg = root / f"{stem}-ascii.svg" ascii_svg.write_text( '' '2026 AI' "", encoding="utf-8", ) ascii_xml, *_ = convert_svg_to_slide_shapes( ascii_svg, index + 20, verbose=False, primary_language=language, ) assert f'lang="{language}"' in ascii_xml assert 'rtl="1"' not in ascii_xml assert "" not in ascii_xml context = ConvertContext(primary_language=language) marker = ET.Element("g") table = _build_native_table( marker, context, { "x": 10, "y": 10, "width": 600, "height": 180, "columns": [sample, "2026 AI"], "rows": [["A", "B"]], "style": {"font_family": "Arial"}, }, ) assert f'lang="{language}"' in table.xml assert all(slot in table.xml for slot in ("' in chart_xml assert f'lang="{language}"' in chart_xml notes_xml = create_notes_slide_xml( 1, sample + "\n2026 AI", language, ) assert f'lang="{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_format="ppt169", verbose=False, transition=None, notes={stem: sample + "\n2026 AI"}, pptx_structure="flat", structure_name="multilingual-smoke", primary_language=language, ) 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 f'lang="{language}"' in packaged_slide assert f'lang="{language}"' in packaged_notes assert f"{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( '' '한국어 2026' "", encoding="utf-8", ) legacy_output = root / "legacy.pptx" assert create_pptx_with_native_svg( [legacy_svg], legacy_output, canvas_format="ppt169", verbose=False, transition=None, pptx_structure="flat", structure_name="legacy-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 'lang="ko-KR"' in legacy_slide print("Multilingual text smoke: passed") PY ``` Expected output: ```text Multilingual text smoke: passed ``` The RTL contract is paragraph `a:pPr rtl="1"` plus run-level `a:rtl` only when the run contains strong RTL characters. Do not use `rtlCol`; it controls column order, not paragraph direction.