Skip to content

Commit 1f6d5b9

Browse files
committed
Localise deck truncation marker and preserve over-cap bullets
The "(+N more)" over-cap marker was hard-coded English, wrong on a zh-tw / ja deck. Add a more_items key across all 14 locales and thread the deck language into _cap_bullets and its multi-column callers. When an evaluation or limitations section exceeds the per-cell bullet cap, render it full-width paginated (at most cap bullets per page) instead of dropping the overflow behind the marker, so no author content is lost.
1 parent bf74306 commit 1f6d5b9

3 files changed

Lines changed: 190 additions & 13 deletions

File tree

tests/test_exporters.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,92 @@ def test_pptx_own_thesis_drops_source_slide_bibtex_and_self_reference(tmp_path):
362362
assert "BibTeX" not in every_run_text
363363

364364

365+
def test_pptx_over_cap_marker_is_localised(tmp_path):
366+
"""When a section exceeds the per-slide bullet cap, the "+N more" marker is
367+
rendered in the deck's language — never a hard-coded English "(+N more)".
368+
369+
Why: `_cap_bullets` appends a count marker (slide-deck-rules §3) instead of
370+
silently dropping bullets; before this fix the marker was English on every
371+
deck, so a zh-tw deck showed "(+2 more)" amid Traditional Chinese body text.
372+
"""
373+
from pptx import Presentation
374+
375+
from thesisagents.core.models import Paper, PaperSummary
376+
377+
summary = PaperSummary(
378+
language="zh-tw",
379+
# Eight bullets in one method section overruns the 6-per-subsection cap.
380+
method_sections=(("步驟", tuple(f"第{i}步驟說明文字" for i in range(1, 9))),),
381+
core_observation="核心觀察。",
382+
model="test",
383+
)
384+
paper = Paper(
385+
source="local", source_id="t", title="標註在地化測試",
386+
authors=("A",), year=2026, venue="T", abstract="摘要", url="", summary=summary,
387+
)
388+
collection = PaperCollection(
389+
query=Query(keywords="x", sources=("local",)), papers=(paper,),
390+
)
391+
options = ExportOptions(
392+
formats=("pptx",), out_dir=str(tmp_path), filename_stem="marker",
393+
language="zh-tw",
394+
)
395+
written = export_collection(collection, options)
396+
prs = Presentation(str(written["pptx"]))
397+
every_run_text = " ".join(
398+
run.text
399+
for slide in prs.slides
400+
for shape in slide.shapes
401+
if shape.has_text_frame
402+
for para in shape.text_frame.paragraphs
403+
for run in para.runs
404+
)
405+
assert "(還有 2 項)" in every_run_text, "localised over-cap marker missing"
406+
assert "more)" not in every_run_text, "English marker leaked into a zh-tw deck"
407+
408+
409+
def test_pptx_long_evaluation_section_preserves_all_bullets(tmp_path):
410+
"""An evaluation section with more bullets than a 2-column grid cell holds
411+
renders as height-paginated stacks so EVERY bullet survives — none is dropped
412+
behind a "(+N more)" marker — and the result still clears the footer guard.
413+
"""
414+
from pptx import Presentation
415+
416+
from thesisagents.core.models import Paper, PaperSummary
417+
from thesisagents.exporters.overflow import check_pptx
418+
419+
bullets = tuple(f"Finding number {i} with its own distinct text" for i in range(1, 10))
420+
summary = PaperSummary(
421+
language="en",
422+
evaluation_sections=(("Benchmark results", bullets),), # 9 > the 6 cell cap
423+
core_observation="Stacked rendering preserves every evaluation bullet.",
424+
model="test",
425+
)
426+
paper = Paper(
427+
source="local", source_id="t", title="Evaluation Heavy",
428+
authors=("A",), year=2026, venue="V", abstract="", url="", summary=summary,
429+
)
430+
collection = PaperCollection(
431+
query=Query(keywords="x", sources=("local",)), papers=(paper,),
432+
)
433+
written = export_collection(collection, ExportOptions(
434+
formats=("pptx",), out_dir=str(tmp_path), filename_stem="evalheavy", language="en",
435+
))
436+
prs = Presentation(str(written["pptx"]))
437+
every_run_text = " ".join(
438+
run.text
439+
for slide in prs.slides
440+
for shape in slide.shapes
441+
if shape.has_text_frame
442+
for para in shape.text_frame.paragraphs
443+
for run in para.runs
444+
)
445+
for i in range(1, 10):
446+
assert f"Finding number {i} " in every_run_text, f"evaluation bullet {i} dropped"
447+
assert "more)" not in every_run_text, "content was truncated instead of paginated"
448+
assert check_pptx(written["pptx"]) == [], "stacked evaluation overflowed"
449+
450+
365451
def _find_run_color(prs, target_rgb: tuple[int, int, int]) -> bool:
366452
for slide in prs.slides:
367453
for shape in slide.shapes:

thesisagents/exporters/i18n.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
_TABLE: dict[str, dict[str, str]] = {
2323
"en": {
24+
"more_items": "(+{n} more)",
2425
"agenda": "Agenda",
2526
"references": "References",
2627
"paper_review_prefix": "Paper Review:",
@@ -70,6 +71,7 @@
7071
"no_authors_dash": "—",
7172
},
7273
"zh-tw": {
74+
"more_items": "(還有 {n} 項)",
7375
"agenda": "議程",
7476
"references": "參考文獻",
7577
"paper_review_prefix": "論文回顧:",
@@ -119,6 +121,7 @@
119121
"no_authors_dash": "—",
120122
},
121123
"zh-cn": {
124+
"more_items": "(还有 {n} 项)",
122125
"agenda": "议程",
123126
"references": "参考文献",
124127
"paper_review_prefix": "论文回顾:",
@@ -168,6 +171,7 @@
168171
"no_authors_dash": "—",
169172
},
170173
"ja": {
174+
"more_items": "(他 {n} 件)",
171175
"agenda": "目次",
172176
"references": "参考文献",
173177
"paper_review_prefix": "論文レビュー:",
@@ -217,6 +221,7 @@
217221
"no_authors_dash": "—",
218222
},
219223
"es": {
224+
"more_items": "(+{n} más)",
220225
"agenda": "Índice",
221226
"references": "Referencias",
222227
"paper_review_prefix": "Revisión del artículo:",
@@ -266,6 +271,7 @@
266271
"no_authors_dash": "—",
267272
},
268273
"fr": {
274+
"more_items": "(+{n} autres)",
269275
"agenda": "Sommaire",
270276
"references": "Références",
271277
"paper_review_prefix": "Revue d'article :",
@@ -315,6 +321,7 @@
315321
"no_authors_dash": "—",
316322
},
317323
"de": {
324+
"more_items": "(+{n} weitere)",
318325
"agenda": "Inhalt",
319326
"references": "Literatur",
320327
"paper_review_prefix": "Paper-Review:",
@@ -364,6 +371,7 @@
364371
"no_authors_dash": "—",
365372
},
366373
"ko": {
374+
"more_items": "(외 {n}개)",
367375
"agenda": "목차",
368376
"references": "참고문헌",
369377
"paper_review_prefix": "논문 리뷰:",
@@ -413,6 +421,7 @@
413421
"no_authors_dash": "—",
414422
},
415423
"pt": {
424+
"more_items": "(+{n} mais)",
416425
"agenda": "Sumário",
417426
"references": "Referências",
418427
"paper_review_prefix": "Análise do artigo:",
@@ -462,6 +471,7 @@
462471
"no_authors_dash": "—",
463472
},
464473
"ru": {
474+
"more_items": "(ещё {n})",
465475
"agenda": "Содержание",
466476
"references": "Список литературы",
467477
"paper_review_prefix": "Обзор статьи:",
@@ -511,6 +521,7 @@
511521
"no_authors_dash": "—",
512522
},
513523
"it": {
524+
"more_items": "(+{n} altri)",
514525
"agenda": "Indice",
515526
"references": "Bibliografia",
516527
"paper_review_prefix": "Recensione dell'articolo:",
@@ -560,6 +571,7 @@
560571
"no_authors_dash": "—",
561572
},
562573
"vi": {
574+
"more_items": "(+{n} mục nữa)",
563575
"agenda": "Mục lục",
564576
"references": "Tài liệu tham khảo",
565577
"paper_review_prefix": "Tổng quan bài báo:",
@@ -609,6 +621,7 @@
609621
"no_authors_dash": "—",
610622
},
611623
"hi": {
624+
"more_items": "(+{n} और)",
612625
"agenda": "विषय-सूची",
613626
"references": "संदर्भ",
614627
"paper_review_prefix": "शोध-पत्र समीक्षा:",
@@ -658,6 +671,7 @@
658671
"no_authors_dash": "—",
659672
},
660673
"id": {
674+
"more_items": "(+{n} lainnya)",
661675
"agenda": "Daftar Isi",
662676
"references": "Referensi",
663677
"paper_review_prefix": "Tinjauan Makalah:",

0 commit comments

Comments
 (0)