feat(i18n): add browser-language localization with Jinja2 _() and locale middleware
Some checks failed
ci/woodpecker/pr/pipeline Pipeline failed

Add i18n support to the blog web UI with 4 languages (en/ru/fr/de),
80 translation keys, automatic Accept-Language detection, persistent
locale cookie, and a language switcher dropdown in the header.

- Infrastructure: TranslationService, translation dicts, convenience _()
- Presentation: locale middleware, /web/lang/{locale} switcher route
- Templates: all 9 templates use {{ _(key, current_locale) }}
- Tests: 26 tests across TranslationService, locale detection helpers
- Docs: TEST_MODEL.md and FEATURE_INFRASTRUCTURE.md updated with TC-UNIT-811-821
This commit is contained in:
2026-05-10 16:22:06 +03:00
parent 4e6505c598
commit d32ad29abc
18 changed files with 1077 additions and 100 deletions

View File

@@ -314,6 +314,113 @@ supports the domain and application layers.
- **Expected:** Calls `session.rollback` once
- **Last Verified:** 2026-05-07
### i18n Localization
#### Translation Service
##### TC-UNIT-811: TranslationService — Existing key returns translation
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestTranslationService::test_get_text_returns_translation_for_existing_key`
- **Expected:** Returns correct localized string when key exists in requested locale
- **Last Verified:** 2026-05-10
##### TC-UNIT-812: TranslationService — Fallback chain
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestTranslationService::{test_get_text_returns_english_fallback_for_missing_key, test_get_text_returns_key_when_neither_locale_nor_en_has_it, test_get_text_returns_english_fallback_for_unknown_locale}`
- **Preconditions:** Key missing in requested locale
- **Steps:** Call get_text with partially-available keys
- **Expected:**
- Falls back to English when key exists in `en` but not in requested locale
- Falls back to raw key when neither requested locale nor `en` has it
- Falls back to English when locale is completely unknown
- **Last Verified:** 2026-05-10
##### TC-UNIT-813: TranslationService — English locale
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestTranslationService::test_get_text_returns_en_when_requested_locale_is_en`
- **Expected:** Returns English string when locale is explicitly `en`
- **Last Verified:** 2026-05-10
##### TC-UNIT-814: TranslationService — Singleton pattern
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestTranslationService::{test_singleton_returns_same_instance, test_singleton_shares_translations}`
- **Expected:** Multiple calls to `get_instance()` return the same object with shared data
- **Last Verified:** 2026-05-10
#### Convenience Function
##### TC-UNIT-815: Convenience _() function
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestConvenienceFunction::{test_convenience_function_returns_translation, test_convenience_function_defaults_to_english, test_convenience_function_returns_key_on_missing}`
- **Expected:**
- Returns translation when key and locale are given
- Defaults to `DEFAULT_LOCALE` ("en") when locale omitted
- Returns raw key when no translation exists
- **Last Verified:** 2026-05-10
#### Locale Detection
##### TC-UNIT-816: Parse Accept-Language — Empty and single locale
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestParseAcceptLanguage::{test_empty_header_returns_empty_list, test_single_locale}`
- **Expected:**
- Empty header returns empty list
- Single locale returns single-element list
- **Last Verified:** 2026-05-10
##### TC-UNIT-817: Parse Accept-Language — Multi-locale with quality
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestParseAcceptLanguage::test_multiple_locales_with_quality`
- **Expected:** Locales sorted by descending q-value, quality values stripped
- **Last Verified:** 2026-05-10
##### TC-UNIT-818: Parse Accept-Language — Region codes and complex input
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestParseAcceptLanguage::{test_region_code_strips_to_base_language, test_complex_header, test_whitespace_around_locales}`
- **Expected:**
- `fr-FR` normalised to `fr`
- Realistic headers with region codes parsed correctly
- Whitespace around locale codes handled gracefully
- **Last Verified:** 2026-05-10
##### TC-UNIT-819: Get best locale — Cookie priority
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestGetBestLocale::{test_cookie_takes_priority, test_invalid_cookie_falls_back_to_header}`
- **Expected:**
- Cookie value used when it is a supported locale
- Unsupported locale in cookie falls back to Accept-Language header
- **Last Verified:** 2026-05-10
##### TC-UNIT-820: Get best locale — Header fallback
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestGetBestLocale::{test_no_cookie_uses_accept_language, test_no_cookie_no_header_returns_default, test_accept_language_unsupported_returns_default, test_missing_cookie_key_uses_header}`
- **Expected:**
- Accept-Language used when no cookie present
- Default locale returned when neither cookie nor header matches
- Unsupported language in header falls back to default
- Absent `locale` cookie key treated as no preference
- **Last Verified:** 2026-05-10
##### TC-UNIT-821: Setup locale manager — Middleware helper
- **Type:** Positive
- **Layer:** Unit
- **File:** `unit/infrastructure/test_i18n.py::TestSetupLocaleManager::{test_sets_locale_on_request_state, test_does_not_override_existing_locale, test_default_locale_when_no_match}`
- **Expected:**
- Sets `request.state.locale` from best-match locale
- Does not override an already-set locale
- Falls back to default when nothing matches
- **Last Verified:** 2026-05-10
## Coverage Summary
| Component | Cases | Status |
@@ -322,6 +429,7 @@ supports the domain and application layers.
| Settings & Config | 19 | ✅ Defaults, overrides, validation, env checks |
| Keycloak Auth Client | 16 | ✅ Token introspection, userinfo, caching, errors |
| Transaction Manager | 2 | ⚠️ Only commit/rollback; missing nested tx, error handling |
| i18n Localization | 11 | ✅ Translation service, locale detection, middleware helper |
## Gaps (Not Yet Covered)