fix(direction): le sens du document traduit suit la langue cible, dans les deux sens
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m3s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3m3s
Persan -> francais : le document traduit restait en lecture de droite a gauche, heritee de la source. Trois causes : - PDF : regression du renommage precedent — le choix d'alignement testait la fonction is_rtl (toujours vraie) au lieu du parametre : tout texte traduit s'alignait a droite, quelle que soit la cible. Corrige + test epinglant l'alignement gauche pour une cible latine. - Word : les marques RTL heritees (bidi, rtl, bidiVisual, notes et commentaires compris) sont desormais retirees quand la cible est latine ; retrait fait sur une liste figee (l'iterateur lxml sautait des elements pendant la suppression). - Excel : les feuilles heritees d'un affichage droite-a-gauche sont remises en lecture gauche-a-droite pour une cible latine. - PowerPoint : les attributs rtl herites sont retires pour une cible latine (alignements visuels conserves). 3 tests de bout en bout nouveaux : document RTL traduit vers le francais ressort en lecture gauche-a-droite dans les trois formats.
This commit is contained in:
@@ -282,6 +282,94 @@ def _apply_bidi_to_part_tree(root) -> int:
|
||||
return switched
|
||||
|
||||
|
||||
def _normalize_part_direction(root, rtl: bool) -> int:
|
||||
"""
|
||||
Normalize the direction of a footnotes/endnotes/comments tree.
|
||||
|
||||
RTL target: every paragraph/run/table switches to RTL (same marks as
|
||||
the document body). Latin target: inherited RTL marks from an RTL
|
||||
source are stripped so the translated notes read left-to-right.
|
||||
Returns the number of paragraphs switched (RTL) or marks removed (LTR).
|
||||
"""
|
||||
if rtl:
|
||||
return _apply_bidi_to_part_tree(root)
|
||||
return _strip_rtl_marks(root)
|
||||
|
||||
|
||||
def _strip_rtl_marks(root) -> int:
|
||||
"""
|
||||
Remove every RTL direction mark under an element tree (LTR targets).
|
||||
|
||||
Translating a Persian/Arabic source into a Latin target must not keep
|
||||
the source's right-to-left layout: w:bidi (paragraph/section
|
||||
direction), w:rtl (run-level marks, including paragraph marks inside
|
||||
w:pPr/w:rPr) and w:bidiVisual (mirrored tables) are stripped so the
|
||||
translated document reads left-to-right, exactly like a document
|
||||
written in the target language.
|
||||
|
||||
Works on python-docx trees and plain lxml trees (notes/comments
|
||||
parts) alike. Returns the number of marks removed.
|
||||
"""
|
||||
removed = 0
|
||||
# Materialize the iteration first: removing children while a live
|
||||
# lxml iterator walks the tree makes it skip elements (seen in
|
||||
# practice: some w:bidi survived the strip).
|
||||
for el in list(root.iter()):
|
||||
if el.tag == qn("w:pPr") or el.tag == qn("w:sectPr"):
|
||||
bidi = el.find(qn("w:bidi"))
|
||||
if bidi is not None:
|
||||
el.remove(bidi)
|
||||
removed += 1
|
||||
elif el.tag == qn("w:rPr"):
|
||||
rtl = el.find(qn("w:rtl"))
|
||||
if rtl is not None:
|
||||
el.remove(rtl)
|
||||
removed += 1
|
||||
elif el.tag == qn("w:tblPr"):
|
||||
bidi_visual = el.find(qn("w:bidiVisual"))
|
||||
if bidi_visual is not None:
|
||||
el.remove(bidi_visual)
|
||||
removed += 1
|
||||
return removed
|
||||
|
||||
|
||||
def _apply_ltr_to_document(document: Document) -> None:
|
||||
"""
|
||||
Force left-to-right direction on the whole document (Latin targets).
|
||||
|
||||
Mirror of _apply_rtl_to_document: an RTL source translated to a Latin
|
||||
target must end up reading left-to-right, not keep the source's RTL
|
||||
layout. Stripping alone is enough — with w:bidi gone, paragraphs fall
|
||||
back to the natural LTR start edge, and explicit alignments (center,
|
||||
justify) keep their meaning.
|
||||
"""
|
||||
stripped = _strip_rtl_marks(document.element)
|
||||
|
||||
for section in document.sections:
|
||||
headers_footers = (
|
||||
section.header,
|
||||
section.footer,
|
||||
getattr(section, "first_page_header", None),
|
||||
getattr(section, "first_page_footer", None),
|
||||
getattr(section, "even_page_header", None),
|
||||
getattr(section, "even_page_footer", None),
|
||||
)
|
||||
for hf in headers_footers:
|
||||
if hf is None:
|
||||
continue
|
||||
root = getattr(hf, "_element", None)
|
||||
if root is None:
|
||||
paras = hf.paragraphs
|
||||
if paras:
|
||||
root = paras[0]._p.getroottree().getroot()
|
||||
if root is not None:
|
||||
stripped += _strip_rtl_marks(root)
|
||||
|
||||
if stripped:
|
||||
from core.logging import get_logger as _gl
|
||||
_gl(__name__).info("word_ltr_normalized", marks_removed=stripped)
|
||||
|
||||
|
||||
def _apply_rtl_to_document(document: Document) -> None:
|
||||
"""Apply RTL direction to every paragraph and section in the document."""
|
||||
body = document.element.body
|
||||
@@ -577,9 +665,14 @@ class WordTranslator:
|
||||
index=i,
|
||||
)
|
||||
|
||||
# Apply RTL layout when the target language is written right-to-left.
|
||||
# Normalize the document direction to the TARGET language:
|
||||
# RTL targets switch to right-to-left, Latin targets strip the
|
||||
# RTL marks inherited from an RTL source (Persian → French
|
||||
# must read left-to-right, not keep the source's layout).
|
||||
if is_rtl(target_language):
|
||||
_apply_rtl_to_document(document)
|
||||
else:
|
||||
_apply_ltr_to_document(document)
|
||||
|
||||
# CJK / Arabic-script font hints so Word renders the target
|
||||
# script with a proper typeface instead of per-run fallbacks.
|
||||
@@ -1046,8 +1139,7 @@ class WordTranslator:
|
||||
# setters hold references to t_elems inside this tree.
|
||||
def write_footnotes_back(output_path: Path) -> None:
|
||||
try:
|
||||
if rtl:
|
||||
_apply_bidi_to_part_tree(footnotes_xml)
|
||||
_normalize_part_direction(footnotes_xml, rtl)
|
||||
new_blob = etree.tostring(
|
||||
footnotes_xml,
|
||||
xml_declaration=True,
|
||||
@@ -1114,8 +1206,7 @@ class WordTranslator:
|
||||
if text_elements and post_save_callbacks is not None:
|
||||
def write_endnotes_back(output_path: Path) -> None:
|
||||
try:
|
||||
if rtl:
|
||||
_apply_bidi_to_part_tree(endnotes_xml)
|
||||
_normalize_part_direction(endnotes_xml, rtl)
|
||||
new_blob = etree.tostring(
|
||||
endnotes_xml,
|
||||
xml_declaration=True,
|
||||
@@ -1170,8 +1261,7 @@ class WordTranslator:
|
||||
if collected and post_save_callbacks is not None:
|
||||
def write_comments_back(output_path: Path) -> None:
|
||||
try:
|
||||
if rtl:
|
||||
_apply_bidi_to_part_tree(comments_xml)
|
||||
_normalize_part_direction(comments_xml, rtl)
|
||||
new_blob = etree.tostring(
|
||||
comments_xml,
|
||||
xml_declaration=True,
|
||||
|
||||
Reference in New Issue
Block a user