Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion babel/messages/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ def update(
update_header_comment: bool = False,
keep_user_comments: bool = True,
update_creation_date: bool = True,
no_fuzzy_flags: bool = False,
) -> None:
"""Update the catalog based on the given template catalog.

Expand Down Expand Up @@ -961,6 +962,9 @@ def update(
:param update_header_comment: whether to copy the header comment from the template
:param keep_user_comments: whether to keep user comments from the old catalog
:param update_creation_date: whether to copy the creation date from the template
:param no_fuzzy_flags: do not mark messages as fuzzy solely because their
IDs were matched approximately. Existing fuzzy
flags and flags caused by plural changes are kept.
"""
messages = self._messages
remaining = messages.copy()
Expand All @@ -984,7 +988,7 @@ def _merge(
message = message.clone()
fuzzy = False
if oldkey != newkey:
fuzzy = True
fuzzy = not no_fuzzy_flags
fuzzy_matches.add(oldkey)
oldmsg = messages.get(oldkey)
assert oldmsg is not None
Expand Down
5 changes: 5 additions & 0 deletions babel/messages/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,8 @@ class UpdateCatalog(CommandMixin):
'do not use fuzzy matching'),
('update-header-comment', None,
'update target header comment'),
('no-fuzzy-flags', None,
'do not mark fuzzy matches as fuzzy'),
('previous', None,
'keep previous msgids of translated messages'),
('check=', None,
Expand All @@ -730,6 +732,7 @@ class UpdateCatalog(CommandMixin):
'no-fuzzy-matching',
'previous',
'update-header-comment',
'no-fuzzy-flags',
'check',
'ignore-pot-creation-date',
]
Expand All @@ -747,6 +750,7 @@ def initialize_options(self):
self.init_missing = False
self.no_fuzzy_matching = False
self.update_header_comment = False
self.no_fuzzy_flags = False
self.previous = False
self.check = False
self.ignore_pot_creation_date = False
Expand Down Expand Up @@ -830,6 +834,7 @@ def run(self):
template,
no_fuzzy_matching=self.no_fuzzy_matching,
update_header_comment=self.update_header_comment,
no_fuzzy_flags=self.no_fuzzy_flags,
update_creation_date=not self.ignore_pot_creation_date,
)

Expand Down
11 changes: 11 additions & 0 deletions docs/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ a PO template file::
do not use fuzzy matching
--update-header-comment
update target header comment
--no-fuzzy-flags do not mark fuzzy matches as fuzzy
--previous keep previous msgids of translated messages


Expand All @@ -248,6 +249,16 @@ If neither the ``output_file`` nor the ``locale`` option is set, this command
looks for all catalog files in the base directory that match the given domain,
and updates each of them.

When correcting source text without changing its meaning, such as fixing a
spelling error, ``--no-fuzzy-flags`` reuses approximately matched translations
without adding a ``fuzzy`` flag just because the message ID changed. Use this
option only when the existing translations are known to remain valid. Existing
``fuzzy`` flags are preserved, and changes to the singular/plural structure or
the number of plural translations still mark a message as fuzzy.

This differs from ``--no-fuzzy-matching``, which disables approximate matching
and therefore does not reuse translations for changed message IDs.

concat
======

Expand Down
31 changes: 30 additions & 1 deletion tests/messages/frontend/test_frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
ExtractMessages,
UpdateCatalog,
)
from babel.messages.pofile import write_po
from babel.messages.pofile import read_po, write_po
from tests.messages.consts import project_dir
from tests.messages.utils import CUSTOM_EXTRACTOR_COOKIE

Expand Down Expand Up @@ -239,6 +239,35 @@ def test_update_catalog_boolean_args():
assert cmdinst.previous is False # Mutually exclusive with no_fuzzy_matching


@pytest.mark.parametrize('no_fuzzy_flags', [False, True])
def test_update_catalog_no_fuzzy_flags(tmp_path, no_fuzzy_flags):
template = Catalog()
template.add('Save changes')
template.add('Existing fuzzy')
cat = Catalog(locale='de', fuzzy=False)
cat.add('Save chnages', 'Speichern')
cat.add('Existing fuzzy', 'Ungeprüft', flags=['fuzzy'])
pot = tmp_path / 'messages.pot'
po = tmp_path / 'de.po'
for path, value in [(pot, template), (po, cat)]:
with path.open('wb') as file:
write_po(file, value)

cmd = configure_cli_command([
'update', '-i', str(pot), '-o', str(po), '-l', 'de', '--previous',
*(['--no-fuzzy-flags'] if no_fuzzy_flags else []),
])
cmd.run()

with po.open('rb') as file:
result = read_po(file)
assert result['Save changes'].string == 'Speichern'
assert result['Save changes'].fuzzy is (not no_fuzzy_flags)
assert result['Existing fuzzy'].fuzzy
assert not result.obsolete
assert '#| msgid "Save chnages"' in po.read_text(encoding='utf-8')


def test_compile_catalog_dir(tmp_path):
"""
Test that `compile` can compile all locales in a directory.
Expand Down
52 changes: 52 additions & 0 deletions tests/messages/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -158,6 +160,56 @@ def test_catalog_update_fuzzy_matching_with_case_change():
assert cat['foo'].fuzzy is True


@pytest.mark.parametrize('no_fuzzy_flags', [False, True])
@pytest.mark.parametrize('already_fuzzy', [False, True])
@pytest.mark.parametrize('context', [None, 'button'])
@pytest.mark.parametrize('plural', [False, True])
def test_catalog_update_no_fuzzy_flags(no_fuzzy_flags, already_fuzzy, context, plural):
cat = catalog.Catalog(locale='de')
old_id = ('Save chnages', 'Save all chnages') if plural else 'Save chnages'
new_id = ('Save changes', 'Save all changes') if plural else 'Save changes'
translation = ('Speichern', 'Alle speichern') if plural else 'Speichern'
flags = {'fuzzy'} if already_fuzzy else set()
cat.add(old_id, translation, context=context, flags=flags, user_comments=['Keep this'])
tmpl = catalog.Catalog()
tmpl.add(new_id, context=context, locations=[('main.py', 42)])

cat.update(tmpl, no_fuzzy_flags=no_fuzzy_flags)

message = cat.get(new_id, context=context)
assert message.string == translation
assert message.fuzzy is (already_fuzzy or not no_fuzzy_flags)
assert message.previous_id == (list(old_id) if plural else [old_id])
assert message.user_comments == ['Keep this']
assert message.locations == [('main.py', 42)]
assert not cat.obsolete
assert not tmpl.get(new_id, context=context).fuzzy


@pytest.mark.parametrize(('old_id', 'translation', 'new_id'), [
('item', 'Ding', ('item', 'items')),
(('item', 'items'), ('Ding', 'Dinge'), 'item'),
(('item', 'items'), ('Ding',), ('item', 'items')),
])
def test_catalog_update_no_fuzzy_flags_preserves_plural_checks(old_id, translation, new_id):
cat = catalog.Catalog(locale='de')
cat.add(old_id, translation)
tmpl = catalog.Catalog()
tmpl.add(new_id)
cat.update(tmpl, no_fuzzy_flags=True)
assert cat[new_id].fuzzy


def test_catalog_update_no_fuzzy_flags_with_no_matching():
cat = catalog.Catalog(locale='de')
cat.add('Save chnages', 'Speichern')
tmpl = catalog.Catalog()
tmpl.add('Save changes')
cat.update(tmpl, no_fuzzy_matching=True, no_fuzzy_flags=True)
assert not cat['Save changes'].string
assert 'Save chnages' in cat.obsolete


def test_catalog_update_fuzzy_matching_with_char_change():
cat = catalog.Catalog()
cat.add('fo', 'Voh')
Expand Down