)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"b990228712fde334e6609d1e312f8363e792749d","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"c6e7c914_00b05f2c","updated":"2026-04-08 10:27:48.000000000","message":"Looks good. I haven\u0027t tested it. Some small possible improvements.","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"ef62f83a3871eefb39bd0df4e4078dd332200fb9","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":5,"id":"805140ea_f87944ee","updated":"2026-04-13 09:44:13.000000000","message":"Hello @t.v.ovtchinnikova@gmail.com, I can see that your “extract” code is a simplified rewrite rather than a copy of the original enmerkar logic. And during a local testing I can see a different behavior, compared to the original enmerkar.\n\nLike, for:\n{% blocktrans %}Hello {{ name }}, welcome to {{ site }}{% endblocktrans %}\noriginal enmerkar return (with just __init__.py (version detection) fixed locally):\nmsgid \"Hello %(name)s, welcome to %(site)s\"\nThis patch return:\nmsgid \"Hello name, welcome to site\"\n\nFor:\n{{ _(\"Click here to continue\") }}\nOriginal enmerkar:\nmsgid \"Click here to continue\"\nThis patch:\nmsgid \"ick here to continu\"\n\nCould you please double check it? Maybe I am doing something wrong locally.\n\nBut if I am right then I would probably suggest copy enmerkar with as minimal changes as possible (it is BSD-licensed and has been well tested over the years).\nThe pkg_resources issue that caused the original failure was only enmerkar’s __init__.py. The extraction logic itself does not need rewriting (at least for me, locally it works well).\nIt should be enough just remove Django 1.9 try/except TypeError around Lexer().\nAnd keep the nice UnicodeDecodeError handling that you added.\n\nMaybe I am missing something as I am testing it locally but I would like to clarify it. I will be happy if I am wrong but it is definitely worth double checking it.\nThank you!","commit_id":"33f536ae32283a00d193838d0ec5df75d8810789"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"a023b408878af7d7005faa35845a4bf5717563e7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"2c34b397_f0e6e015","in_reply_to":"805140ea_f87944ee","updated":"2026-04-13 14:39:31.000000000","message":"Thanks Jan!","commit_id":"33f536ae32283a00d193838d0ec5df75d8810789"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"9d417c14f63ac4d7f68d92d583243ef0e908310b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":8,"id":"168cb67c_a0d7da98","updated":"2026-04-15 22:25:20.000000000","message":"Dropping decoding, since in Python 3 all strings are already Unicode and thus already \"decoded\".","commit_id":"f471ab7a1cec3858497adeac530e2b84956cf2b9"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"7efa723f423f3c609ed1b7c40eb8463761cad16b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"09296b46_9540bd54","updated":"2026-04-23 08:28:05.000000000","message":"Reviewed + tested locally. Looks good, works well. Thanks!","commit_id":"8d4ede745ce969c9aad6984c6bf6b078828f9829"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"85cfa02944d4086d8b45006afbcfc9263c955b13","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"dabc07fe_535e3b30","updated":"2026-04-22 21:16:24.000000000","message":"Thanks Jan!","commit_id":"8d4ede745ce969c9aad6984c6bf6b078828f9829"},{"author":{"_account_id":37598,"name":"Ivan Anfimov","display_name":"Ivan Anfimov","email":"lazekteam@gmail.com","username":"anfimovir"},"change_message_id":"d4f32e7e65355f6cc75e1453d2fb01fb3833056c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":9,"id":"c4bea18f_381cd8ec","updated":"2026-04-23 12:11:27.000000000","message":"recheck (post_failure)","commit_id":"8d4ede745ce969c9aad6984c6bf6b078828f9829"}],"horizon/test/unit/utils/test_babel_extract_django.py":[{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"35171a16df4127d6a3f1d2dd19814d7a9f668ec6","unresolved":true,"context_lines":[{"line_number":21,"context_line":"class ExtractDjangoTestCase(test.TestCase):"},{"line_number":22,"context_line":""},{"line_number":23,"context_line":"    def test_blocktrans(self):"},{"line_number":24,"context_line":"        buf \u003d io.StringIO(\u0027{% blocktrans %}Hello {{ name }}, \u0027"},{"line_number":25,"context_line":"                          \u0027welcome to {{ site }}{% endblocktrans %}\u0027)"},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"        messages \u003d list(extract_django(buf, [], [], {}))"}],"source_content_type":"text/x-python","patch_set":8,"id":"580c43c6_385fcaf1","line":24,"range":{"start_line":24,"start_character":17,"end_line":24,"end_character":25},"updated":"2026-04-22 08:33:24.000000000","message":"Hello @t.v.ovtchinnikova@gmail.com, I found that in babel extract.py there is ```rb``` so it returns bytes instead of str:\nhttps://github.com/python-babel/babel/blob/baf9431c27d06e9c59513f329c0bc5939f31773d/babel/messages/extract.py#L381\n\nSo if you use here StringIO I am not very sure if it is proper way to \"test\" it? Because the tests are passing although I think this still does not work properly.\n\nSee my other comment in babel_extract_django.py with more detailed description what issue I am facing.","commit_id":"f471ab7a1cec3858497adeac530e2b84956cf2b9"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"85cfa02944d4086d8b45006afbcfc9263c955b13","unresolved":false,"context_lines":[{"line_number":21,"context_line":"class ExtractDjangoTestCase(test.TestCase):"},{"line_number":22,"context_line":""},{"line_number":23,"context_line":"    def test_blocktrans(self):"},{"line_number":24,"context_line":"        buf \u003d io.StringIO(\u0027{% blocktrans %}Hello {{ name }}, \u0027"},{"line_number":25,"context_line":"                          \u0027welcome to {{ site }}{% endblocktrans %}\u0027)"},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"        messages \u003d list(extract_django(buf, [], [], {}))"}],"source_content_type":"text/x-python","patch_set":8,"id":"d787eade_11df250a","line":24,"range":{"start_line":24,"start_character":17,"end_line":24,"end_character":25},"in_reply_to":"580c43c6_385fcaf1","updated":"2026-04-22 21:16:24.000000000","message":"Done","commit_id":"f471ab7a1cec3858497adeac530e2b84956cf2b9"}],"horizon/utils/babel_extract_django.py":[{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"35610a8a52e2929e17fa2a1ca018f91b8c6634f1","unresolved":true,"context_lines":[{"line_number":1,"context_line":"# -*- encoding: utf-8 -*-"},{"line_number":2,"context_line":"#    Licensed under the Apache License, Version 2.0 (the \"License\"); you may"},{"line_number":3,"context_line":"#    not use this file except in compliance with the License. You may obtain"},{"line_number":4,"context_line":"#    a copy of the License at"}],"source_content_type":"text/x-python","patch_set":2,"id":"1eb9bfbe_60bd036b","line":1,"updated":"2026-04-07 09:27:10.000000000","message":"python 3 doesn\u0027t need encoding anymore, it defaults to utf8","commit_id":"b6f9cf9cdc974cfd13dad752fa44de09b027e6d4"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"f76df59dd736c6dbba2dd306ab2c2544bad7fb14","unresolved":false,"context_lines":[{"line_number":1,"context_line":"# -*- encoding: utf-8 -*-"},{"line_number":2,"context_line":"#    Licensed under the Apache License, Version 2.0 (the \"License\"); you may"},{"line_number":3,"context_line":"#    not use this file except in compliance with the License. You may obtain"},{"line_number":4,"context_line":"#    a copy of the License at"}],"source_content_type":"text/x-python","patch_set":2,"id":"99910cfb_07947fa9","line":1,"in_reply_to":"1eb9bfbe_60bd036b","updated":"2026-04-07 16:22:33.000000000","message":"Done","commit_id":"b6f9cf9cdc974cfd13dad752fa44de09b027e6d4"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"b990228712fde334e6609d1e312f8363e792749d","unresolved":true,"context_lines":[{"line_number":38,"context_line":"    try:"},{"line_number":39,"context_line":"        content \u003d fileobj.read().decode(encoding)"},{"line_number":40,"context_line":"    except UnicodeDecodeError:"},{"line_number":41,"context_line":"        return"},{"line_number":42,"context_line":""},{"line_number":43,"context_line":"    # Initialize Lexer (Handling Django version differences)"},{"line_number":44,"context_line":"    try:"}],"source_content_type":"text/x-python","patch_set":4,"id":"003d0309_f0a4c6f5","line":41,"updated":"2026-04-08 10:27:48.000000000","message":"Do we really want to quietly ignore this error? Maybe we should at least log a warning?","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"e2a029b8b2404e7ca35dd364dfd41e5057f20c69","unresolved":false,"context_lines":[{"line_number":38,"context_line":"    try:"},{"line_number":39,"context_line":"        content \u003d fileobj.read().decode(encoding)"},{"line_number":40,"context_line":"    except UnicodeDecodeError:"},{"line_number":41,"context_line":"        return"},{"line_number":42,"context_line":""},{"line_number":43,"context_line":"    # Initialize Lexer (Handling Django version differences)"},{"line_number":44,"context_line":"    try:"}],"source_content_type":"text/x-python","patch_set":4,"id":"e40eae38_a8852850","line":41,"in_reply_to":"003d0309_f0a4c6f5","updated":"2026-04-08 19:20:22.000000000","message":"Done","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":12015,"name":"François Magimel","email":"magimel.francois@gmail.com","username":"linkid"},"change_message_id":"7badb35cbfd3c5e74ec66febd5b1919690bfd789","unresolved":true,"context_lines":[{"line_number":44,"context_line":"    try:"},{"line_number":45,"context_line":"        # Django 1.9+"},{"line_number":46,"context_line":"        lexer \u003d Lexer(content)"},{"line_number":47,"context_line":"    except TypeError:"},{"line_number":48,"context_line":"        # Older Django versions"},{"line_number":49,"context_line":"        lexer \u003d Lexer(content, None)"},{"line_number":50,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"b2976dfd_90261baf","line":47,"updated":"2026-04-08 17:45:37.000000000","message":"No need to support older Django versions.","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"e2a029b8b2404e7ca35dd364dfd41e5057f20c69","unresolved":false,"context_lines":[{"line_number":44,"context_line":"    try:"},{"line_number":45,"context_line":"        # Django 1.9+"},{"line_number":46,"context_line":"        lexer \u003d Lexer(content)"},{"line_number":47,"context_line":"    except TypeError:"},{"line_number":48,"context_line":"        # Older Django versions"},{"line_number":49,"context_line":"        lexer \u003d Lexer(content, None)"},{"line_number":50,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"174dc8f3_70ad0a39","line":47,"in_reply_to":"b2976dfd_90261baf","updated":"2026-04-08 19:20:22.000000000","message":"Done","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":8648,"name":"Radomir Dopieralski","email":"openstack@dopieralski.pl","username":"thesheep"},"change_message_id":"b990228712fde334e6609d1e312f8363e792749d","unresolved":true,"context_lines":[{"line_number":46,"context_line":"        lexer \u003d Lexer(content)"},{"line_number":47,"context_line":"    except TypeError:"},{"line_number":48,"context_line":"        # Older Django versions"},{"line_number":49,"context_line":"        lexer \u003d Lexer(content, None)"},{"line_number":50,"context_line":""},{"line_number":51,"context_line":"    tokens \u003d lexer.tokenize()"},{"line_number":52,"context_line":"    lineno \u003d 1"}],"source_content_type":"text/x-python","patch_set":4,"id":"c5ff56d8_c704d752","line":49,"updated":"2026-04-08 10:27:48.000000000","message":"I\u0027m not sure we support such old django versions anyways, we could probably get rid of this.","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"e2a029b8b2404e7ca35dd364dfd41e5057f20c69","unresolved":false,"context_lines":[{"line_number":46,"context_line":"        lexer \u003d Lexer(content)"},{"line_number":47,"context_line":"    except TypeError:"},{"line_number":48,"context_line":"        # Older Django versions"},{"line_number":49,"context_line":"        lexer \u003d Lexer(content, None)"},{"line_number":50,"context_line":""},{"line_number":51,"context_line":"    tokens \u003d lexer.tokenize()"},{"line_number":52,"context_line":"    lineno \u003d 1"}],"source_content_type":"text/x-python","patch_set":4,"id":"f5657589_f139fdf3","line":49,"in_reply_to":"c5ff56d8_c704d752","updated":"2026-04-08 19:20:22.000000000","message":"Done","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":35133,"name":"Jan Jasek","email":"jjasek@redhat.com","username":"janjasek"},"change_message_id":"35171a16df4127d6a3f1d2dd19814d7a9f668ec6","unresolved":true,"context_lines":[{"line_number":45,"context_line":"    :rtype: ``iterator``"},{"line_number":46,"context_line":"    \"\"\""},{"line_number":47,"context_line":"    try:"},{"line_number":48,"context_line":"        content \u003d fileobj.read()"},{"line_number":49,"context_line":"    except (OSError, IOError) as e:"},{"line_number":50,"context_line":"        # Get the filename if available, otherwise use a placeholder"},{"line_number":51,"context_line":"        file \u003d getattr(fileobj, \u0027name\u0027, \u0027unknown file\u0027)"}],"source_content_type":"text/x-python","patch_set":8,"id":"11dceeeb_3cb30eb8","line":48,"range":{"start_line":48,"start_character":8,"end_line":48,"end_character":32},"updated":"2026-04-22 08:33:24.000000000","message":"Hello @t.v.ovtchinnikova@gmail.com,\nI think that the part of code in original enmerkar:\n```\nencoding \u003d options.get(\u0027encoding\u0027, \u0027utf8\u0027)\n    text \u003d fileobj.read().decode(encoding)\n```\nwas intentional and necessary, not Python2 artifact. When you open file in a general text mode ```open(filename, \u0027r\u0027)``` it is probably decoded to utf8 by default, but not in case ```open(filename, \u0027rb\u0027)```, then you need to decode by youself.\n\nI tried new version of this patch locally again and I am still not sure that it works properly. When I run tox -e translations it is successful, it pass but there is a lot of type errors:\n```TypeError: cannot use a string pattern on a bytes-like object```\n\nAnd this is what is generated (in django.pot files):\n0 horizon/locale/django.pot\n0 openstack_dashboard/locale/django.pot\n0 total\n\nWhen I add encoding on line 48:\n```\ncontent \u003d fileobj.read()\nif isinstance(content, bytes):\n content \u003d content.decode(options.get(\u0027encoding\u0027, \u0027utf-8\u0027))\n```\nThere is not TypeError at all and there is generated:\n   563 horizon/locale/django.pot\n 13366 openstack_dashboard/locale/django.pot\n 13929 total\n\nAlso your tests passed (I guess incorrectly) because they are using StringIO instead of BytesIO.\n\nAgain, maybe that\u0027s ignorance on my side, or something specific in my local environment but could you please double check it?","commit_id":"f471ab7a1cec3858497adeac530e2b84956cf2b9"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"85cfa02944d4086d8b45006afbcfc9263c955b13","unresolved":false,"context_lines":[{"line_number":45,"context_line":"    :rtype: ``iterator``"},{"line_number":46,"context_line":"    \"\"\""},{"line_number":47,"context_line":"    try:"},{"line_number":48,"context_line":"        content \u003d fileobj.read()"},{"line_number":49,"context_line":"    except (OSError, IOError) as e:"},{"line_number":50,"context_line":"        # Get the filename if available, otherwise use a placeholder"},{"line_number":51,"context_line":"        file \u003d getattr(fileobj, \u0027name\u0027, \u0027unknown file\u0027)"}],"source_content_type":"text/x-python","patch_set":8,"id":"c9e3ffa7_e6073ebb","line":48,"range":{"start_line":48,"start_character":8,"end_line":48,"end_character":32},"in_reply_to":"11dceeeb_3cb30eb8","updated":"2026-04-22 21:16:24.000000000","message":"Done","commit_id":"f471ab7a1cec3858497adeac530e2b84956cf2b9"}],"pyproject.toml":[{"author":{"_account_id":12015,"name":"François Magimel","email":"magimel.francois@gmail.com","username":"linkid"},"change_message_id":"7badb35cbfd3c5e74ec66febd5b1919690bfd789","unresolved":true,"context_lines":[{"line_number":38,"context_line":""},{"line_number":39,"context_line":"[project.entry-points.\"babel.extractors\"]"},{"line_number":40,"context_line":"# We use a custom extractor to find translatable strings in AngularJS templates."},{"line_number":41,"context_line":"# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for"},{"line_number":42,"context_line":"# details on how this works."},{"line_number":43,"context_line":"angular \u003d \"horizon.utils.babel_extract_angular:extract_angular\""},{"line_number":44,"context_line":"django \u003d \"horizon.utils.babel_extract_django:extract_django\""}],"source_content_type":"text/x-toml","patch_set":4,"id":"28fafb69_1d524648","line":41,"range":{"start_line":41,"start_character":0,"end_line":41,"end_character":2},"updated":"2026-04-08 17:45:37.000000000","message":"Dead link. You can use either https://babel.pocoo.org/en/latest/messages.html#referencing-extraction-methods or https://babel.pocoo.org/en/latest/messages.html#writing-extraction-methods instead.","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"},{"author":{"_account_id":6914,"name":"Tatiana Ovchinnikova","email":"t.v.ovtchinnikova@gmail.com","username":"tmazur"},"change_message_id":"e2a029b8b2404e7ca35dd364dfd41e5057f20c69","unresolved":false,"context_lines":[{"line_number":38,"context_line":""},{"line_number":39,"context_line":"[project.entry-points.\"babel.extractors\"]"},{"line_number":40,"context_line":"# We use a custom extractor to find translatable strings in AngularJS templates."},{"line_number":41,"context_line":"# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for"},{"line_number":42,"context_line":"# details on how this works."},{"line_number":43,"context_line":"angular \u003d \"horizon.utils.babel_extract_angular:extract_angular\""},{"line_number":44,"context_line":"django \u003d \"horizon.utils.babel_extract_django:extract_django\""}],"source_content_type":"text/x-toml","patch_set":4,"id":"c20aa2c7_2b9273a0","line":41,"range":{"start_line":41,"start_character":0,"end_line":41,"end_character":2},"in_reply_to":"28fafb69_1d524648","updated":"2026-04-08 19:20:22.000000000","message":"Done","commit_id":"8da8f3080d73bc28437872da392d6c200f047e3d"}]}
