Skip to content

Commit fd94737

Browse files
committed
fix(formats): corrected two field CSV handling
Ignore the headers and update source if context is not present in the file. Fixes #16835 Supersedes #16884
1 parent 1e26134 commit fd94737

File tree

5 files changed

+95
-0
lines changed

5 files changed

+95
-0
lines changed

docs/changes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Weblate 5.15
2020
.. rubric:: Bug fixes
2121

2222
* :ref:`mt-deepl` integration now correctly handles translating to Chinese variants.
23+
* :doc:`/formats/csv` format saving translations with empty source fields when using monolingual base files.
2324

2425
.. rubric:: Compatibility
2526

weblate/formats/tests/test_formats.py

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

88
from __future__ import annotations
99

10+
import csv
1011
import os.path
1112
import shutil
1213
from abc import ABC, abstractmethod
@@ -27,6 +28,7 @@
2728
CatkeysFormat,
2829
CSVFormat,
2930
CSVSimpleFormat,
31+
CSVUtf8SimpleFormat,
3032
DTDFormat,
3133
FlatXMLFormat,
3234
FluentFormat,
@@ -69,6 +71,8 @@
6971
TEST_PO = get_test_file("cs.po")
7072
TEST_CSV = get_test_file("cs-mono.csv")
7173
TEST_CSV_NOHEAD = get_test_file("cs.csv")
74+
TEST_CSV_SIMPLE_EN = get_test_file("en-simple.csv")
75+
TEST_CSV_SIMPLE_PL = get_test_file("pl-simple.csv")
7276
TEST_FLATXML = get_test_file("cs-flat.xml")
7377
TEST_CUSTOM_FLATXML = get_test_file("cs-flat-custom.xml")
7478
TEST_RESOURCEDICTIONARY = get_test_file("cs.xaml")
@@ -1088,6 +1092,79 @@ class CSVSimpleFormatNoHeadTest(CSVFormatNoHeadTest):
10881092
EXPECTED_FLAGS: ClassVar[str | list[str]] = ""
10891093

10901094

1095+
class CSVUtf8SimpleFormatMonolingualTest(FixtureTestCase, TempDirMixin):
1096+
"""Test for CSV Simple UTF-8 format with monolingual base file."""
1097+
1098+
def setUp(self) -> None:
1099+
super().setUp()
1100+
self.create_temp()
1101+
1102+
def tearDown(self) -> None:
1103+
super().tearDown()
1104+
self.remove_temp()
1105+
1106+
def test_save_preserves_source_field(self) -> None:
1107+
"""
1108+
Test that saving a CSV Simple file preserves source fields.
1109+
1110+
This reproduces the issue where translations are saved with empty source
1111+
fields instead of preserving the context/key from the base file.
1112+
1113+
Relies on translate-toolkit's monolingual CSV support (PR #5830).
1114+
See: https://github.com/WeblateOrg/weblate/issues/16835
1115+
"""
1116+
# Create a temporary copy of the translation file
1117+
translation_file = os.path.join(self.tempdir, "pl.csv")
1118+
content = Path(TEST_CSV_SIMPLE_PL).read_bytes()
1119+
Path(translation_file).write_bytes(content)
1120+
1121+
# Load the base file (template)
1122+
template_store = CSVUtf8SimpleFormat(TEST_CSV_SIMPLE_EN, is_template=True)
1123+
1124+
# Load the translation file with the template
1125+
store = CSVUtf8SimpleFormat(
1126+
translation_file, template_store=template_store, language_code="pl"
1127+
)
1128+
1129+
# Verify we have the expected units
1130+
units = list(store.content_units)
1131+
self.assertEqual(len(units), 5)
1132+
1133+
# Find units and add translations
1134+
for unit in units:
1135+
if unit.context == "objectAccessDenied":
1136+
pounit, add = store.find_unit(unit.context, unit.source)
1137+
self.assertTrue(add)
1138+
store.add_unit(pounit)
1139+
pounit.set_target("Nie masz uprawnien do modyfikowania obiektu '%s'")
1140+
elif unit.context == "propAccessDenied":
1141+
pounit, add = store.find_unit(unit.context, unit.source)
1142+
self.assertTrue(add)
1143+
store.add_unit(pounit)
1144+
pounit.set_target(
1145+
"Nie masz uprawnien do modyfikowania wlasciwosci: %s (tytul obiektu: %s)"
1146+
)
1147+
elif unit.context == "noReadPermission":
1148+
pounit, add = store.find_unit(unit.context, unit.source)
1149+
self.assertTrue(add)
1150+
store.add_unit(pounit)
1151+
pounit.set_target("Nie masz uprawnien do odczytu obiektu '%s'")
1152+
1153+
# Save the file
1154+
store.save()
1155+
1156+
# Verify the saved content
1157+
with open(translation_file, encoding="utf-8") as handle:
1158+
reader = csv.reader(handle, delimiter=";", quotechar='"')
1159+
for row in reader:
1160+
self.assertEqual(len(row), 2)
1161+
self.assertNotEqual(
1162+
row[0],
1163+
"",
1164+
f"Source field is empty for target: {row[1]}",
1165+
)
1166+
1167+
10911168
class FlatXMLFormatTest(BaseFormatTest):
10921169
format_class = FlatXMLFormat
10931170
FILE = TEST_FLATXML

weblate/formats/ttkit.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,13 @@ def set_target(self, target: str | list[str]) -> None:
10451045
):
10461046
# Update source for bilingual as CSV fields can contain just source
10471047
self.unit.source = self.unit.target
1048+
if (
1049+
self.template is not None
1050+
and not self.parent.is_template
1051+
and "context" not in self.parent.store.fieldnames
1052+
):
1053+
# Update source for monolingual fields without context field
1054+
self.unit.source = self.unit.context
10481055

10491056
def is_fuzzy(self, fallback=False):
10501057
# Report fuzzy state only if present in the fields
@@ -1741,6 +1748,8 @@ def parse_store(self, storefile, *, dialect: str | None = None):
17411748

17421749
def parse_simple_csv(self, content, filename, header: list[str] | None = None):
17431750
fieldnames = ["source", "target"]
1751+
# Prefer detected header if available (translate-toolkit PR #5830 adds
1752+
# monolingual CSV support with proper handling of context/id/target columns)
17441753
if header and all(
17451754
field in {"source", "target", "context", "id"} for field in header
17461755
):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"objectAccessDenied";"You do not have a permission to modify the object '%s'"
2+
"propAccessDenied";"You do not have a permission to modify the property: %s (object title: %s)"
3+
"createObjectInParentAccessDenied";"You do not have a permission to create a new object in '%s'"
4+
"createObjectAccessDenied";"You do not have a permission to create a new object of this type"
5+
"noReadPermission";"You do not have a permission to read the object '%s'"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"source";"target"
2+
"createObjectInParentAccessDenied";"Nie masz uprawnien do tworzenia nowego obiektu w '%s'"
3+
"createObjectAccessDenied";"Nie masz uprawnien do tworzenia nowego obiektu tego typu"

0 commit comments

Comments
 (0)