Skip to content

Commit 1e8ce75

Browse files
committed
Drop PO references when source locations change
1 parent 34ba41f commit 1e8ce75

2 files changed

Lines changed: 46 additions & 14 deletions

File tree

‎scripts/translations.py‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def po_references(content):
4949

5050

5151
def normalize_po_references(original, incoming):
52-
"""Drop references for new/changed messages; ignore line-number updates."""
52+
"""Drop references for changed messages or source paths; ignore line-number updates."""
5353
old_lines, old_references = po_references(original)
5454
lines, references = po_references(incoming)
5555
for key, (entry, positions) in references.items():
@@ -59,17 +59,16 @@ def normalize_po_references(original, incoming):
5959
if old_entry is None:
6060
changed = True
6161
else:
62-
changed = (entry.msgstr, entry.msgstr_plural) != (old_entry.msgstr, old_entry.msgstr_plural)
62+
changed = (
63+
(entry.msgstr, entry.msgstr_plural)
64+
!= (old_entry.msgstr, old_entry.msgstr_plural)
65+
or [path for path, _ in entry.occurrences]
66+
!= [path for path, _ in old_entry.occurrences]
67+
)
6368
if changed or (old_entry is not None and not old_positions):
6469
for pos in positions:
6570
lines[pos] = b''
6671
continue
67-
# Keep added/removed references and file renames for unchanged translations.
68-
if not old_positions or (
69-
[path for path, _ in entry.occurrences]
70-
!= [path for path, _ in old_entry.occurrences]
71-
):
72-
continue
7372
lines[positions[0]] = b"".join(old_lines[pos] for pos in old_positions)
7473
for pos in positions[1:]:
7574
lines[pos] = b""
@@ -79,7 +78,8 @@ def normalize_po_references(original, incoming):
7978
def restore_timestamp_only_changes(root, *, normalize_references=False):
8079
"""Restore PO files whose unstaged changes only affect header dates.
8180
82-
Optionally drop references for new/changed messages and restore other line numbers.
81+
Optionally drop references for new/changed messages or source paths and restore
82+
other line numbers.
8383
Run after wrapping so formatting differences do not mask date-only changes.
8484
"""
8585
result = subprocess.run(
@@ -247,7 +247,7 @@ def main():
247247
restore_parser.add_argument("root", type=Path)
248248
restore_parser.add_argument(
249249
"--references", action="store_true",
250-
help="drop references for new/changed messages and ignore other line-number updates",
250+
help="drop references for new/changed messages or source paths and ignore other line-number updates",
251251
)
252252
args = parser.parse_args()
253253
if args.command == "sync":

‎tests/test_translations.py‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,18 @@ def test_normalize_references_and_dates_before_staging(self):
156156
before = DATED_PO.replace(b"\n", newline)
157157
changed = before.replace(b"example.rst:10", b"example.rst:200")
158158
changed = changed.replace(b"2026-09-29", b"2026-10-24")
159-
for name in ("unchanged.po", "translated.po", "deleted.po"):
159+
for name in ("unchanged.po", "translated.po", "locations.po", "deleted.po"):
160160
(self.destination / name).write_bytes(before)
161161
self.track()
162162
(self.destination / "unchanged.po").write_bytes(changed)
163163
(self.destination / "translated.po").write_bytes(
164164
changed.replace('翻訳'.encode(), '新しい訳'.encode())
165165
)
166+
locations = changed.replace(
167+
b"#: example.rst:200" + newline,
168+
b"#: example.rst:200 example.rst:300" + newline,
169+
)
170+
(self.destination / "locations.po").write_bytes(locations)
166171
(self.destination / "deleted.po").unlink()
167172
new = self.destination / ("new-crlf.po" if newline == b"\r\n" else "new-lf.po")
168173
new.write_bytes(changed)
@@ -176,6 +181,10 @@ def test_normalize_references_and_dates_before_staging(self):
176181
expected = expected.replace(b"2026-09-29", b"2026-10-24")
177182
expected = expected.replace(b"#: example.rst:10" + newline, b"")
178183
self.assertEqual((self.destination / "translated.po").read_bytes(), expected)
184+
self.assertEqual(
185+
(self.destination / "locations.po").read_bytes(),
186+
locations.replace(b"#: example.rst:200 example.rst:300" + newline, b""),
187+
)
179188
self.assertEqual(new.read_bytes(), changed.replace(b"#: example.rst:200" + newline, b""))
180189
self.assertFalse((self.destination / "deleted.po").exists())
181190
self.track()
@@ -189,8 +198,8 @@ def test_normalize_references_preserves_other_entry_changes(self):
189198
for old, new, action in [
190199
(b'msgid "example"', b'msgid "new source"', "drop"),
191200
(b'msgid "example"', b'msgctxt "new context"\nmsgid "example"', "drop"),
192-
(b"example.rst:200", b"renamed.rst:200", "keep"),
193-
(b"example.rst:200", b"example.rst:200 other.rst:30", "keep"),
201+
(b"example.rst:200", b"renamed.rst:200", "drop"),
202+
(b"example.rst:200", b"example.rst:200 other.rst:30", "drop"),
194203
(b'#: example.rst:200\n', b'', "keep"),
195204
(b'msgid "example"', b'#, fuzzy\nmsgid "example"', "restore"),
196205
(b'msgid "example"', b'#. new comment\nmsgid "example"', "restore"),
@@ -200,12 +209,35 @@ def test_normalize_references_preserves_other_entry_changes(self):
200209
incoming = changed.replace(old, new)
201210
expected = incoming
202211
if action == "drop":
203-
expected = incoming.replace(b"#: example.rst:200\n", b"")
212+
expected = b"".join(
213+
line for line in incoming.splitlines(keepends=True)
214+
if not line.startswith(b"#:")
215+
)
204216
elif action == "restore":
205217
expected = incoming.replace(b"example.rst:200", b"example.rst:10")
206218
self.assertEqual(translations.normalize_po_references(DATED_PO, incoming),
207219
expected)
208220

221+
def test_changed_source_occurrence_counts_drop_all_reference_lines(self):
222+
header = DATED_PO.split(b"#: example.rst:10")[0]
223+
message = b'msgid "yes"\nmsgstr "yes"\n'
224+
old_references = b"#: ../../c-api/buffer.rst:292 ../../c-api/buffer.rst:294\n"
225+
for new_references in [
226+
b"#: ../../c-api/buffer.rst:304\n",
227+
(b"#: ../../c-api/buffer.rst:304 ../../c-api/buffer.rst:305\n"
228+
b"#: ../../c-api/buffer.rst:308 ../../c-api/buffer.rst:309\n"),
229+
]:
230+
for newline in (b"\n", b"\r\n"):
231+
with self.subTest(references=new_references, newline=newline):
232+
original = (header + old_references + message).replace(b"\n", newline)
233+
incoming = (header + new_references + message).replace(b"\n", newline)
234+
expected = (header + message).replace(b"\n", newline)
235+
normalized = translations.normalize_po_references(original, incoming)
236+
self.assertEqual(normalized, expected)
237+
self.assertEqual(
238+
translations.normalize_po_references(normalized, incoming), expected
239+
)
240+
209241
def test_restore_line_numbers_matches_context_plural_and_reordered_entries(self):
210242
header = DATED_PO.split(b"#: example.rst:10")[0]
211243
first = (b'#: one.rst:10 two.rst:20\nmsgctxt "first"\n'

0 commit comments

Comments
 (0)