Skip to content

Catalog.is_identical() is always False when the catalog contains a msgctxt, so pybabel update --check can never pass #1307

Description

@yhay81

Summary

Catalog.is_identical() returns False for any catalog that contains a
context-specific message — including when compared against itself. Because
pybabel update --check decides by is_identical, that flag reports every
such catalog as out of date on every run, no matter how current it is.

Any project that uses pgettext/npgettext — that is, any project that
disambiguates a homonym, which is what contexts are for — cannot use --check
as the CI staleness gate it was added for in #831.

Reproduction

>>> from babel.messages.catalog import Catalog
>>> c = Catalog(locale="ja")
>>> c.add("Guide", "ガイド", context="navigation")
<Message 'Guide' (flags: [])>
>>> c.is_identical(c)
False

End to end, on a catalog pybabel init produced seconds earlier:

$ cat src/app.py
from gettext import pgettext
pgettext("navigation", "Guide")
$ pybabel extract -F babel.cfg -o messages.pot src
$ pybabel init -i messages.pot -d locales -l ja
$ pybabel update -i messages.pot -d locales --check
...
BaseError: Some catalogs are out of date.

Tested on 2.18.0 and on main (95d2f60).

Cause

Catalog._key_for() stores a context-specific message under a
(msgid, msgctxt) tuple, and a pluralizable message under its singular id:

key = id
if isinstance(key, (list, tuple)):
    key = id[0]
if context is not None:
    key = (key, context)

Catalog.is_identical() iterates those internal keys but resolves each one
through Catalog.get():

for key in self._messages.keys() | other._messages.keys():
    message_1 = self.get(key)
    message_2 = other.get(key)

get() calls _key_for(key) again. Handed the (msgid, msgctxt) tuple, that
branch cannot tell it from a pluralizable message's (singular, plural) id, so
it takes key[0] and looks up the bare msgid — a key no message is stored
under. get() returns None, and is_identical returns False.

Context-free messages are unaffected, which is why the existing coverage
(test_enclosed_filenames_in_location_comment) does not catch it.

Fix

The keys being iterated are _messages' own, so they should index _messages
directly rather than being re-derived. PR to follow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions