feat(i18n): add browser-language localization with Jinja2 _() and locale middleware
Some checks failed
ci/woodpecker/pr/pipeline Pipeline failed
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:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user