All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m19s
Multiple translation bugs in Word/Excel/PPTX that caused chart elements
to be left untranslated or charts to render as empty series.
Backend
-------
* providers (deepseek/openai/minimax): tighten system prompt so the LLM
actually translates chart titles/axis labels/legend/category labels,
month abbreviations, and clarifies what counts as a 'real' proper noun
(people/place/company/product names) vs. technical labels. Old rule
'keep proper nouns unchanged' was being read too broadly by the model
and caused chart text to be skipped.
* excel_translator.py:
- Sheet reference rewrite: when a sheet is renamed, the chart XML's
c:f refs (e.g. 'Sales 2024'!$D$2:$D$61) are now rewritten to the
new name with proper apostrophe escaping (Chiffre d'affaires ->
'Chiffre d''affaires') and auto-quoting when the new name contains
spaces or special chars. Without this, the chart points at a sheet
that no longer exists and renders 0/empty series.
- Sheet name offset bug: sheet_name_offset was computed after chart
text was appended to text_elements, causing sheet names to receive
chart text translations. Now captured BEFORE sheet names are added.
* New tests:
- test_excel_chart_sheet_refs.py (10 unit tests, synthetic inputs)
- test_chart_translation_prompt.py (3 contract tests on the prompt)
Frontend
--------
* DashboardSidebar / translate/page: hide the Memento promo section
for paying users (tier != 'free').
* constants.ts: temporarily comment out the 'CLES API' nav item.
Update constants.test.ts to match the new state.
All fixes are generic - no file-specific hardcoding, edge cases covered
(empty mapping, missing bang, apostrophe escaping, partial renames,
multi-series).
139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
"""Regression tests for the bug where translated Excel chart <c:f> references
|
|
kept pointing at the original (now-renamed) sheet name, causing the chart
|
|
to render as zero/empty series after translation.
|
|
|
|
This is a real bug: see e.g. test_excel.xlsx where the chart references
|
|
'Sales 2024'!$D$2:$D$61 but the sheet is renamed to "Chiffre d'affaires
|
|
par produit" — the chart then shows 0 for every data point.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from lxml import etree
|
|
|
|
from translators.excel_translator import ExcelTranslator
|
|
|
|
NS_C = "http://schemas.openxmlformats.org/drawingml/2006/chart"
|
|
|
|
|
|
def _build_chart_xml(series_refs):
|
|
"""Helper: build a chart XML with the given <c:f> strings."""
|
|
sers = "\n".join(
|
|
f"<c:ser><c:tx><c:strRef><c:f>{ref}</c:f></c:strRef></c:tx>"
|
|
f"<c:cat><c:strRef><c:f>{ref}</c:f></c:strRef></c:cat>"
|
|
f"<c:val><c:numRef><c:f>{ref}</c:f></c:numRef></c:val></c:ser>"
|
|
for ref in series_refs
|
|
)
|
|
return f"""<c:chartSpace xmlns:c="{NS_C}" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
|
<c:chart>
|
|
<c:plotArea><c:lineChart>{sers}</c:lineChart></c:plotArea>
|
|
</c:chart>
|
|
</c:chartSpace>"""
|
|
|
|
|
|
def test_quoted_sheet_name_with_apostrophe_in_new_name():
|
|
r = ExcelTranslator._rewrite_sheet_ref(
|
|
"'Sales 2024'!$D$2:$D$61",
|
|
{"Sales 2024": "Chiffre d'affaires par produit"},
|
|
)
|
|
assert r == "'Chiffre d''affaires par produit'!$D$2:$D$61", f"got: {r!r}"
|
|
|
|
|
|
def test_unquoted_sheet_name_simple():
|
|
r = ExcelTranslator._rewrite_sheet_ref(
|
|
"Sheet1!$A$1",
|
|
{"Sheet1": "Feuille1"},
|
|
)
|
|
assert r == "Feuille1!$A$1", f"got: {r!r}"
|
|
|
|
|
|
def test_new_name_with_space_gets_quoted():
|
|
r = ExcelTranslator._rewrite_sheet_ref(
|
|
"Sheet1!$A$1",
|
|
{"Sheet1": "Ma Feuille"},
|
|
)
|
|
assert r == "'Ma Feuille'!$A$1", f"got: {r!r}"
|
|
|
|
|
|
def test_name_not_in_mapping_unchanged():
|
|
r = ExcelTranslator._rewrite_sheet_ref(
|
|
"Other!$A$1",
|
|
{"Sheet1": "Feuille1"},
|
|
)
|
|
assert r == "Other!$A$1", f"got: {r!r}"
|
|
|
|
|
|
def test_empty_mapping_is_noop():
|
|
r = ExcelTranslator._rewrite_sheet_ref("Sheet1!$A$1", {})
|
|
assert r == "Sheet1!$A$1", f"got: {r!r}"
|
|
|
|
|
|
def test_no_bang_is_noop():
|
|
r = ExcelTranslator._rewrite_sheet_ref("just_a_label", {"x": "y"})
|
|
assert r == "just_a_label", f"got: {r!r}"
|
|
|
|
|
|
def test_real_chart_xml_all_refs_rewritten():
|
|
chart_xml = etree.fromstring(_build_chart_xml(["Sheet1!$B$1"]))
|
|
n = ExcelTranslator._rewrite_sheet_refs_in_chart(chart_xml, {"Sheet1": "Ma Feuille"})
|
|
assert n == 3, f"expected 3 rewrites, got {n}"
|
|
refs = [f.text for f in chart_xml.iter(f"{{{NS_C}}}f")]
|
|
assert refs == [
|
|
"'Ma Feuille'!$B$1",
|
|
"'Ma Feuille'!$B$1",
|
|
"'Ma Feuille'!$B$1",
|
|
], f"got: {refs}"
|
|
|
|
|
|
def test_real_chart_xml_apostrophe_escaped_in_new_name():
|
|
chart_xml = etree.fromstring(_build_chart_xml(["Sheet1!$B$1"]))
|
|
n = ExcelTranslator._rewrite_sheet_refs_in_chart(
|
|
chart_xml, {"Sheet1": "Chiffre d'affaires"}
|
|
)
|
|
assert n == 3
|
|
refs = [f.text for f in chart_xml.iter(f"{{{NS_C}}}f")]
|
|
assert all(r == "'Chiffre d''affaires'!$B$1" for r in refs), f"got: {refs}"
|
|
|
|
|
|
def test_partial_mapping_leaves_other_refs_intact():
|
|
"""When only one sheet is renamed, refs to other sheets must stay."""
|
|
chart_xml = etree.fromstring(
|
|
_build_chart_xml(["Sheet1!$A$1", "Sheet2!$B$1"])
|
|
)
|
|
n = ExcelTranslator._rewrite_sheet_refs_in_chart(
|
|
chart_xml, {"Sheet1": "Feuille1"}
|
|
)
|
|
# 3 refs to Sheet1 (one series) are rewritten; 3 refs to Sheet2 stay.
|
|
assert n == 3, f"expected 3 rewrites, got {n}"
|
|
refs = [f.text for f in chart_xml.iter(f"{{{NS_C}}}f")]
|
|
assert "Feuille1!$A$1" in refs
|
|
assert "Sheet2!$B$1" in refs
|
|
|
|
|
|
def test_multiple_series_renamed():
|
|
"""When several sheets are renamed, refs to any of them get updated."""
|
|
chart_xml = etree.fromstring(
|
|
_build_chart_xml(["Sheet1!$A$1", "Sheet2!$B$1", "Sheet3!$C$1"])
|
|
)
|
|
n = ExcelTranslator._rewrite_sheet_refs_in_chart(
|
|
chart_xml,
|
|
{"Sheet1": "F1", "Sheet2": "F2", "Sheet3": "F3"},
|
|
)
|
|
assert n == 9, f"expected 9 rewrites (3 series x 3 refs each), got {n}"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_quoted_sheet_name_with_apostrophe_in_new_name()
|
|
test_unquoted_sheet_name_simple()
|
|
test_new_name_with_space_gets_quoted()
|
|
test_name_not_in_mapping_unchanged()
|
|
test_empty_mapping_is_noop()
|
|
test_no_bang_is_noop()
|
|
test_real_chart_xml_all_refs_rewritten()
|
|
test_real_chart_xml_apostrophe_escaped_in_new_name()
|
|
test_partial_mapping_leaves_other_refs_intact()
|
|
test_multiple_series_renamed()
|
|
print("All chart sheet-ref rewrite tests passed.")
|