6deab7dd8c
Мультиязычность:
- Миграция 0021: текстовые поля лендингов → JSON с ключами локалей
- Утилита resolve_locale_text с fallback-цепочкой (lang → ru → en → first)
- Админ API: dict[str, str] для title/subtitle/footer/meta/features
- Публичный API: ?lang= параметр, резолвит в плоские строки
- Обратная совместимость: plain strings → {"ru": value}
Гостевые платежи:
- Миграция 0022: user_id nullable во всех платёжных таблицах
- Поддержка всех провайдеров кроме Stars
- Общий хелпер try_fulfill_guest_purchase в common.py
- YooKassa переведена на общий хелпер
Исправления по ревью:
- CryptoBot: guest fulfillment после FOR UPDATE lock
- _patch_guest_metadata: commit вместо flush
- Freekassa/KassaAI: metadata как dict вместо JSON-строки
- purchase_token маскирован в логах
- None → "guest" в order_id
- Rate limit на GET /landing/{slug}
- Маскирование contact_value
44 lines
1.7 KiB
Python
44 lines
1.7 KiB
Python
"""Locale resolution utilities for multi-locale landing page text fields."""
|
|
|
|
SUPPORTED_LOCALES: tuple[str, ...] = ('ru', 'en', 'zh', 'fa')
|
|
DEFAULT_LOCALE: str = 'ru'
|
|
|
|
|
|
def resolve_locale_text(data: dict[str, str] | str | None, lang: str = DEFAULT_LOCALE) -> str:
|
|
"""Resolve a localized text dict to a single string for the given language.
|
|
|
|
Fallback chain: requested lang -> 'ru' -> 'en' -> first available value -> ''.
|
|
Accepts plain strings for backward compatibility with pre-migration data.
|
|
"""
|
|
if data is None:
|
|
return ''
|
|
if isinstance(data, str):
|
|
return data
|
|
return data.get(lang) or data.get('ru') or data.get('en') or next(iter(data.values()), '')
|
|
|
|
|
|
def ensure_locale_dict(value: dict[str, str] | str | None) -> dict[str, str]:
|
|
"""Coerce a value to a locale dict. Plain strings become ``{'ru': value}``."""
|
|
if value is None:
|
|
return {}
|
|
if isinstance(value, str):
|
|
return {'ru': value} if value else {}
|
|
return value
|
|
|
|
|
|
def validate_locale_dict(
|
|
value: dict[str, str],
|
|
*,
|
|
max_length: int | None = None,
|
|
field_name: str = 'field',
|
|
) -> dict[str, str]:
|
|
"""Validate that all keys are supported locales and values respect length limits."""
|
|
for locale, text in value.items():
|
|
if locale not in SUPPORTED_LOCALES:
|
|
raise ValueError(f'Unsupported locale "{locale}" in {field_name}. Allowed: {", ".join(SUPPORTED_LOCALES)}')
|
|
if not isinstance(text, str):
|
|
raise ValueError(f'{field_name}[{locale}] must be a string')
|
|
if max_length is not None and len(text) > max_length:
|
|
raise ValueError(f'{field_name}[{locale}] exceeds max length {max_length} (got {len(text)})')
|
|
return value
|