Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i18n babel can not extract lazy text strings correct #1395

Closed
2 tasks done
Robotvasya opened this issue Jan 12, 2024 · 3 comments · Fixed by #1409
Closed
2 tasks done

i18n babel can not extract lazy text strings correct #1395

Robotvasya opened this issue Jan 12, 2024 · 3 comments · Fixed by #1409
Labels
3.x Issue or PR for stable 3.x version docs Something is missing in docs good first issue A good place to start contributing to this project without going too deep. help wanted Extra attention is needed

Comments

@Robotvasya
Copy link
Contributor

Robotvasya commented Jan 12, 2024

Checklist

  • I am sure the error is coming from aiogram code
  • I have searched in the issue tracker for similar bug reports, including closed ones

Operating system

Any

Python version

3.10

aiogram version

3.3.0

Expected behavior

I make en example according this document: https://docs.aiogram.dev/en/dev-3.x/utils/i18n.html

...
from aiogram.utils.i18n import gettext as _
from aiogram.utils.i18n import lazy_gettext as __
from aiogram.utils.i18n import I18n, ConstI18nMiddleware
...
@dp.message(F.text == __("Start"))  # lazy text
async def handler_1(message: Message) -> None:
    await message.answer(_("Welcome, {name}!").format(name=html.quote(message.from_user.full_name)))
    await message.answer(_("How many messages do you have? Input number, please:"))

@dp.message(F.text)
async def handler_2(message: Message) -> None:
    # this is plural and wrapped by double underscore
    await message.answer(__("You have {} message!", "You have {} messages!", 2).format(message.text)) 
...
def main() -> None:
...
    i18n = I18n(path="locales", default_locale="en", domain="my-super-bot")
    dp.message.outer_middleware(ConstI18nMiddleware(locale='en', i18n=i18n))
...

I create lines lazy text and gettext wrapped strings.
Also I add plural forms.
Extracting strings using Babel should return this strings in .pot template file:

"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.13.1\n"

#: lesson1.py:12
msgid "Start"
msgstr ""

#: lesson1.py:14
msgid "Welcome, {name}!"
msgstr ""

#: lesson1.py:15
msgid "How many messages do you have? Input number, please:"
msgstr ""


#: lesson1.py:19
msgid "You have {} message!"
msgid_plural "You have {} messages!"
msgstr[0] ""
msgstr[1] ""

Current behavior

After extraction we loose some strings.
Babel command

pybabel extract -o locales/messages.pot --keywords="__:1,2" --input-dirs=.

extracts either this:

"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.13.1\n"

<---- So here we loose lazy text string "Start", see expected behavior.

#: lesson1.py:14 
msgid "Welcome, {name}!"   # regular gettext
msgstr ""

#: lesson1.py:15
msgid "How many messages do you have? Input number, please:"  # regular gettext
msgstr ""

#: lesson1.py:19
msgid "You have {} message!"  # alias ngettext as gettext in \aiogram\utils\i18n\context.py
msgid_plural "You have {} messages!"
msgstr[0] ""
msgstr[1] ""

or

pybabel extract -o locales/messages.pot --keywords="__"  --input-dirs=.

extracts that:

"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.13.1\n"

#: lesson1.py:12
msgid "Start"
msgstr ""

#: lesson1.py:14
msgid "Welcome, {name}!"
msgstr ""

#: lesson1.py:15
msgid "How many messages do you have? Input number, please:"
msgstr ""

#: lesson1.py:19
msgid "You have {} message!"
msgstr ""
<---- Here we loose plural forms, see expected behavior.

Here we loose plural forms.

Ether this or that but not all together.
The use of these keys __:1,2 оr _:1,2 _ __ and their various combinations leads to an even more disastrous result.

Steps to reproduce

pybabel extract -o locales/messages.pot --input-dirs=.
pybabel extract -o locales/messages.pot --keywords="__" --input-dirs=.
pybabel extract -o locales/messages.pot --keywords="__:1,2" --input-dirs=.
pybabel extract -o locales/messages.pot --keywords="__ __:1,2"--input-dirs=.

Code example

from aiogram import Bot, Dispatcher, F, html
from aiogram.types import Message
from aiogram.utils.i18n import gettext as _

from aiogram.utils.i18n import lazy_gettext as __
from aiogram.utils.i18n import I18n, ConstI18nMiddleware

TOKEN = "token"
dp = Dispatcher()


@dp.message(F.text == __("Start"))
async def handler_1(message: Message) -> None:
    await message.answer(_("Welcome, {name}!").format(name=html.quote(message.from_user.full_name)))
    await message.answer(_("How many messages do you have? Input number, please:"))

@dp.message(F.text)
async def handler_2(message: Message) -> None:
    await message.answer(__("You have {} message!", "You have {} messages!", 2).format(message.text))


def main() -> None:
    bot = Bot(TOKEN, parse_mode="HTML")
    i18n = I18n(path="locales", default_locale="en", domain="my-super-bot")
    dp.message.outer_middleware(ConstI18nMiddleware(locale='en', i18n=i18n))
    dp.run_polling(bot)


if __name__ == "__main__":
    main()

Logs

No response

Additional information

No response

@Robotvasya Robotvasya added the bug Something is wrong with the framework label Jan 12, 2024
@Olegt0rr
Copy link
Contributor

Olegt0rr commented Jan 16, 2024

Is there any issue with the same behaviour without aiogram?

Looks like you can remove aiogram from this example and create an issue for babel

@Olegt0rr Olegt0rr added the needs triage This issue is not yet confirmed label Jan 16, 2024
@Robotvasya
Copy link
Contributor Author

Robotvasya commented Jan 16, 2024

Thank you, Oleg.

Really. I did some research on how functions work in i18n/utils.
Since the aiogram library combines together gettext and ngettext as _(), this effect occurs when retrieving strings using Babel. Although xgettext works a little better, I still had to suffer. This happens because gettext and ngettext functions have different numbers of arguments.

I propose to add the following clarification to the documentation https://docs.aiogram.dev/en/dev-3.x/utils/i18n.html in the Deal with Babel Step 1 Note:
To retrieve simultaneously the strings wrapped by gettext as _(), ngettext as _() and lazy_gettext as __() the following retrieve command
pybabel extract -o locales/messages.pot -k _:1,1t -k _:1,2 -k __
or
xgettext -L Python --keyword=_:1,2 --keyword=__ your_file.py

UPD
To success extraction plural forms should be wrapped by _() and not __().

@Olegt0rr Olegt0rr added help wanted Extra attention is needed good first issue A good place to start contributing to this project without going too deep. docs Something is missing in docs 3.x Issue or PR for stable 3.x version and removed bug Something is wrong with the framework needs triage This issue is not yet confirmed labels Jan 22, 2024
@Olegt0rr
Copy link
Contributor

Olegt0rr commented Jan 22, 2024

Thanx for ur investigation!

Feel free to make a contribution via PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.x Issue or PR for stable 3.x version docs Something is missing in docs good first issue A good place to start contributing to this project without going too deep. help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants