From 155c5998ad1b0398356f87af849f221f5699bbfb Mon Sep 17 00:00:00 2001 From: berkay-byte Date: Fri, 18 Sep 2026 11:56:48 +0300 Subject: [PATCH] Preserve nonconsecutive repeated comment lines --- babel/messages/catalog.py | 6 ++++-- tests/messages/test_catalog.py | 19 +++++++++++++++++++ tests/messages/test_pofile.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 5e6c28255..05736905f 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -18,6 +18,7 @@ from difflib import SequenceMatcher from email import message_from_string from heapq import nlargest +from itertools import groupby from string import Formatter from typing import TYPE_CHECKING, TypedDict @@ -181,8 +182,9 @@ def __init__( self.flags.add('python-brace-format') else: self.flags.discard('python-brace-format') - self.auto_comments = list(dict.fromkeys(auto_comments)) if auto_comments else [] - self.user_comments = list(dict.fromkeys(user_comments)) if user_comments else [] + # Collapse consecutive repeats without losing repeated wrapped fragments. + self.auto_comments = [comment for comment, _ in groupby(auto_comments)] if auto_comments else [] + self.user_comments = [comment for comment, _ in groupby(user_comments)] if user_comments else [] if previous_id: if isinstance(previous_id, str): self.previous_id = [previous_id] diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py index 7c730d325..0db4af7b9 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -15,6 +15,8 @@ import pickle from io import StringIO +import pytest + from babel.dates import UTC, format_datetime from babel.messages import catalog, pofile from babel.util import FixedOffsetTimezone @@ -66,6 +68,23 @@ def test_message_translator_comments(): assert mess.auto_comments == ['Comment 1 About `foo`', 'Comment 2 About `foo`'] +@pytest.mark.parametrize('attribute', ['auto_comments', 'user_comments']) +@pytest.mark.parametrize( + ('comments', 'expected'), + [ + ([], []), + (['KEY:', 'KEY:', 'KEY:'], ['KEY:']), + (['KEY:', 'first', 'KEY:', 'second'], ['KEY:', 'first', 'KEY:', 'second']), + (['KEY:', 'KEY:', 'first', 'KEY:', 'KEY:'], ['KEY:', 'first', 'KEY:']), + (['', '', 'text', ''], ['', 'text', '']), + ], +) +def test_message_collapses_only_consecutive_comment_lines(attribute, comments, expected): + message = catalog.Message('foo', **{attribute: iter(comments)}) + assert getattr(message, attribute) == expected + assert getattr(message.clone(), attribute) == expected + + def test_message_clone_message_object(): msg = catalog.Message('foo', locations=[('foo.py', 42)]) clone = msg.clone() diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index 0f4b483cf..3d1235dfa 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -20,6 +20,37 @@ from babel.messages.pofile import _enclose_filename_if_necessary, _extract_locations +@pytest.mark.parametrize( + ('prefix', 'attribute'), + [('#.', 'auto_comments'), ('#', 'user_comments')], +) +@pytest.mark.parametrize('obsolete', [False, True]) +def test_read_po_preserves_repeated_comment_lines(prefix, attribute, obsolete): + comments = ['KEY:', 'first.long.key', 'KEY:', 'second.long.key'] + source = '\n'.join(f'{prefix} {line}' for line in comments) + '\n' + message_prefix = '#~ ' if obsolete else '' + source += f'{message_prefix}msgid "Example"\n{message_prefix}msgstr ""\n' + + catalog = pofile.read_po(StringIO(source)) + message = catalog.obsolete['Example'] if obsolete else catalog['Example'] + assert getattr(message, attribute) == comments + + +@pytest.mark.parametrize('attribute', ['auto_comments', 'user_comments']) +def test_wrapped_comments_roundtrip(attribute): + catalog = Catalog() + comments = ['KEY: ' + 'foo.' * 20, 'KEY: ' + 'bar.' * 20] + catalog.add('Example', **{attribute: comments}) + first = BytesIO() + pofile.write_po(first, catalog, omit_header=True) + first.seek(0) + + restored = pofile.read_po(first) + second = BytesIO() + pofile.write_po(second, restored, omit_header=True) + assert second.getvalue() == first.getvalue() + + def test_enclosed_filenames_in_location_comment(): catalog = Catalog() catalog.add("foo", lineno=2, locations=[("main 1.py", 1)], string="")