diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 0e7417020..6074ea905 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -1000,6 +1000,8 @@ def _merge( else: oldmsg = remaining.pop(oldkey, None) assert oldmsg is not None + if 'fuzzy' in oldmsg.flags: + message.previous_id = list(oldmsg.previous_id) message.string = oldmsg.string if keep_user_comments and oldmsg.user_comments: diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index 63401f10d..273c7e7aa 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -178,6 +178,7 @@ def _reset_message_state(self) -> None: self.flags = [] self.user_comments = [] self.auto_comments = [] + self.previous = [] self.context = None self.obsolete = False self.in_msgid = False @@ -215,6 +216,7 @@ def _add_message(self) -> None: self.user_comments, lineno=self.offset + 1, context=msgctxt, + previous_id=[p.denormalize() for p in self.previous], ) if self.obsolete: if not self.ignore_obsolete: @@ -322,6 +324,17 @@ def _process_comment(self, line) -> None: self.auto_comments.append(comment) return + if prefix == '#|': + # The previous msgid of a fuzzy message (msgmerge --previous). Message has + # no field for a previous msgctxt, so that line is dropped. + content = line[2:].strip() + keyword, _, arg = content.partition(' ') + if keyword in ('msgid', 'msgid_plural'): + self.previous.append(_NormalizedString(arg)) + elif content.startswith('"') and self.previous: + self.previous[-1].append(content) + return + # These are called user comments self.user_comments.append(line[1:].strip()) @@ -644,6 +657,14 @@ def _format_comment(comment, prefix=''): for line in comment_wrapper.wrap(comment): yield f"#{prefix} {line.strip()}\n" + def _format_previous(keyword, value): + # One "#| " line per line of the normalized string, as msgmerge writes it; + # wrapping it like a comment would split the quoted strings. + first, *rest = normalize(value, width=width).splitlines() + yield f"#| {keyword} {first}\n" + for line in rest: + yield f"#| {line}\n" + def _format_conflict_comment(file, project, version, prefix=''): comment = f"#-#-#-#-# {file} ({project} {version}) #-#-#-#-#" yield f"{normalize(comment, prefix=prefix, width=width)}\n" @@ -737,13 +758,9 @@ def _format_message(message, prefix=''): yield f"#{', '.join(['', *sorted(message.flags)])}\n" if message.previous_id and include_previous: - yield from _format_comment( - f'msgid {normalize(message.previous_id[0], width=width)}', - prefix='|', - ) + yield from _format_previous('msgid', message.previous_id[0]) if len(message.previous_id) > 1: - norm_previous_id = normalize(message.previous_id[1], width=width) - yield from _format_comment(f'msgid_plural {norm_previous_id}', prefix='|') + yield from _format_previous('msgid_plural', message.previous_id[1]) if len(conflicts := catalog.get_conflicts(message.id)) > 0: yield from _format_conflict(message.id, conflicts) diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py index 2eaa508cb..71d4da3dc 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -192,6 +192,15 @@ def test_catalog_update_fuzzy_matching_with_char_change(): assert cat['foo'].fuzzy is True +def test_catalog_update_keeps_previous_id_of_fuzzy_message(): + cat = catalog.Catalog() + cat.add('foo', 'Voh', flags=['fuzzy'], previous_id='fo') + tmpl = catalog.Catalog() + tmpl.add('foo') + cat.update(tmpl) + assert cat['foo'].previous_id == ['fo'] + + def test_catalog_update_fuzzy_matching_no_msgstr(): cat = catalog.Catalog() cat.add('fo', '') diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index 00e439b7c..cf5c19609 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -51,6 +51,30 @@ def test_wrapped_comments_roundtrip(attribute): assert second.getvalue() == first.getvalue() +def test_previous_msgid_roundtrip(): + catalog = Catalog() + catalog.add('Save this file', 'Datei speichern', flags=['fuzzy'], previous_id='Save the file') + first = BytesIO() + pofile.write_po(first, catalog, omit_header=True, include_previous=True) + first.seek(0) + + restored = pofile.read_po(first) + assert restored['Save this file'].previous_id == ['Save the file'] + assert restored['Save this file'].user_comments == [] + + +def test_long_previous_msgid_roundtrip(): + previous = 'Save the file to the disk before closing the application window so nothing is lost' + catalog = Catalog() + catalog.add('Save this file', 'Datei speichern', flags=['fuzzy'], previous_id=previous) + first = BytesIO() + pofile.write_po(first, catalog, omit_header=True, include_previous=True) + first.seek(0) + + restored = pofile.read_po(first) + assert restored['Save this file'].previous_id == [previous] + + def test_enclosed_filenames_in_location_comment(): catalog = Catalog() catalog.add("foo", lineno=2, locations=[("main 1.py", 1)], string="")