Add internationalization support and enhance language handling
All checks were successful
CI / lint-and-test (push) Successful in 14s

- Introduced a new i18n module for managing translations and language normalization, supporting both Russian and English.
- Updated various handlers and services to utilize the new translation functions for user-facing messages, improving user experience based on language preferences.
- Enhanced error handling and response messages to be language-aware, ensuring appropriate feedback is provided to users in their preferred language.
- Added tests for the i18n module to validate language detection and translation functionality.
- Updated the example environment file to include a default language configuration.
This commit is contained in:
2026-02-18 13:56:49 +03:00
parent be57555d4f
commit 263c2fefbd
21 changed files with 594 additions and 92 deletions

View File

@@ -19,13 +19,15 @@ def client():
def test_duties_invalid_date_format(client):
r = client.get("/api/duties", params={"from": "2025-01-01", "to": "invalid"})
assert r.status_code == 400
assert "YYYY-MM-DD" in r.json()["detail"]
detail = r.json()["detail"]
assert "from" in detail.lower() and "to" in detail.lower()
def test_duties_from_after_to(client):
r = client.get("/api/duties", params={"from": "2025-02-01", "to": "2025-01-01"})
assert r.status_code == 400
assert "from" in r.json()["detail"].lower() or "позже" in r.json()["detail"]
detail = r.json()["detail"].lower()
assert "from" in detail or "to" in detail or "after" in detail or "позже" in detail
@patch("duty_teller.api.dependencies._is_private_client")
@@ -54,7 +56,7 @@ def test_duties_200_when_skip_auth(mock_fetch, client):
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
def test_duties_403_when_init_data_invalid(mock_validate, client):
mock_validate.return_value = (None, "hash_mismatch")
mock_validate.return_value = (None, "hash_mismatch", "en")
r = client.get(
"/api/duties",
params={"from": "2025-01-01", "to": "2025-01-31"},
@@ -62,13 +64,18 @@ def test_duties_403_when_init_data_invalid(mock_validate, client):
)
assert r.status_code == 403
detail = r.json()["detail"]
assert "авторизации" in detail or "Неверные" in detail or "Неверная" in detail
assert (
"signature" in detail.lower()
or "авторизации" in detail
or "Неверные" in detail
or "Неверная" in detail
)
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.can_access_miniapp")
def test_duties_403_when_username_not_allowed(mock_can_access, mock_validate, client):
mock_validate.return_value = ("someuser", "ok")
mock_validate.return_value = ("someuser", "ok", "en")
mock_can_access.return_value = False
with patch("duty_teller.api.app.fetch_duties_response") as mock_fetch:
r = client.get(
@@ -77,14 +84,16 @@ def test_duties_403_when_username_not_allowed(mock_can_access, mock_validate, cl
headers={"X-Telegram-Init-Data": "user=xxx&hash=yyy"},
)
assert r.status_code == 403
assert "Доступ запрещён" in r.json()["detail"]
assert (
"Access denied" in r.json()["detail"] or "Доступ запрещён" in r.json()["detail"]
)
mock_fetch.assert_not_called()
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.can_access_miniapp")
def test_duties_200_with_allowed_user(mock_can_access, mock_validate, client):
mock_validate.return_value = ("alloweduser", "ok")
mock_validate.return_value = ("alloweduser", "ok", "en")
mock_can_access.return_value = True
with patch("duty_teller.api.app.fetch_duties_response") as mock_fetch:
mock_fetch.return_value = [