i18n: normalize Telegram region codes in locale detection

Resolve user locale by checking the Telegram language code as-is, then
its Babel-normalized form, then the base language. This fixes lowercase
regional codes such as pt-br failing to match available translations like
pt_BR. Add tests covering region-code variants and fallback behavior, and
update i18n documentation plus changelog notes.
This commit is contained in:
mahisafa82 2026-02-26 03:07:37 +03:30
parent 73710acb4c
commit 53ad537739
No known key found for this signature in database
4 changed files with 55 additions and 4 deletions

1
CHANGES/1755.bugfix.rst Normal file
View file

@ -0,0 +1 @@
Improved i18n locale detection in ``SimpleI18nMiddleware`` to support Telegram-style region codes such as ``pt-br`` by resolving first the raw code and then the normalized Babel-style code (for example ``pt_BR``).

View file

@ -129,14 +129,18 @@ class SimpleI18nMiddleware(I18nMiddleware):
event_from_user: User | None = data.get("event_from_user")
if event_from_user is None or event_from_user.language_code is None:
return self.i18n.default_locale
user_locale = event_from_user.language_code
try:
locale = Locale.parse(event_from_user.language_code, sep="-")
locale = Locale.parse(user_locale, sep="-")
except UnknownLocaleError:
return self.i18n.default_locale
if locale.language not in self.i18n.available_locales:
return self.i18n.default_locale
return locale.language
for candidate in (user_locale, str(locale), locale.language):
if candidate in self.i18n.available_locales:
return candidate
return self.i18n.default_locale
class ConstI18nMiddleware(I18nMiddleware):

View file

@ -113,6 +113,16 @@ On top of your application the instance of :class:`aiogram.utils.i18n.I18n` shou
After that you will need to choose one of builtin I18n middleware or write your own.
When using :class:`aiogram.utils.i18n.middleware.SimpleI18nMiddleware`, locale codes from
Telegram (for example ``pt-br``) are resolved against loaded locales in two steps:
1. Direct match as received from Telegram (``pt-br``)
2. Normalized Babel-style identifier (``pt_BR``)
So if your translations are stored under
``locales/pt_BR/LC_MESSAGES/messages.mo``, they will be selected for users with
``language_code="pt-br"``.
Builtin middlewares:

View file

@ -149,6 +149,42 @@ class TestSimpleI18nMiddleware:
)
assert locale == i18n.default_locale
@pytest.mark.parametrize(
("available_locales", "default_locale", "language_code", "expected_locale"),
[
({"pt-br": None}, "en", "pt-br", "pt-br"),
({"pt_BR": None}, "en", "pt-br", "pt_BR"),
({"pt-br": None, "pt_BR": None}, "en", "pt-br", "pt-br"),
({"en": None}, "uk", "en-US", "en"),
({"uk": None}, "en", "uk-UA", "uk"),
],
)
async def test_get_locale_region_code_variants(
self,
i18n: I18n,
available_locales: dict[str, None],
default_locale: str,
language_code: str,
expected_locale: str,
):
i18n.default_locale = default_locale
middleware = SimpleI18nMiddleware(i18n=i18n)
i18n.locales = available_locales
locale = await middleware.get_locale(
None,
{
"event_from_user": User(
id=42,
is_bot=False,
first_name="Test",
language_code=language_code,
)
},
)
assert locale == expected_locale
async def test_custom_keys(self, i18n: I18n):
async def handler(event, data):
return data