Coverage for app / backend / src / couchers / i18n / context.py: 92%
34 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 06:18 +0000
1from dataclasses import dataclass
2from datetime import date, datetime, time
3from zoneinfo import ZoneInfo
5from google.protobuf.timestamp_pb2 import Timestamp
7from couchers.i18n.i18next import I18Next
8from couchers.i18n.locales import DEFAULT_LOCALE
9from couchers.i18n.localize import (
10 get_main_i18next,
11 localize_date,
12 localize_date_from_iso,
13 localize_datetime,
14 localize_time,
15 localize_timezone,
16)
17from couchers.models.users import User
20@dataclass(frozen=True, slots=True, kw_only=True)
21class LocalizationContext:
22 """
23 Specifies regional settings used for localization of strings and date/times.
24 Future settings like 12/24h or format preferences would go here as well.
25 """
27 # The locale code (e.g. 'en', 'pt-BR'), used to lookup translations and format dates/numbers.
28 # Note that a locale doesn't necessarily specify a region.
29 locale: str
31 # The timezone to use when formatting date-times and instants.
32 timezone: ZoneInfo
34 @property
35 def localized_timezone(self) -> str:
36 return localize_timezone(self.timezone, self.locale)
38 def localize_string(
39 self, key: str, *, i18next: I18Next | None = None, substitutions: dict[str, str | int] | None = None
40 ) -> str:
41 i18next = i18next or get_main_i18next()
42 return i18next.localize(key, self.locale, substitutions=substitutions)
44 def localize_date(self, value: date | datetime) -> str:
45 if isinstance(value, datetime): 45 ↛ 46line 45 didn't jump to line 46 because the condition on line 45 was never true
46 value = value.astimezone(self.timezone).date()
47 return localize_date(value, self.locale)
49 def localize_date_from_iso(self, value: str) -> str:
50 return localize_date_from_iso(value, self.locale)
52 def localize_datetime(self, value: datetime | Timestamp) -> str:
53 return localize_datetime(value, self.timezone, self.locale)
55 def localize_time(self, value: datetime | time) -> str:
56 if isinstance(value, datetime): 56 ↛ 58line 56 didn't jump to line 58 because the condition on line 56 was always true
57 value = value.astimezone(self.timezone).time()
58 return localize_time(value, self.locale)
60 @staticmethod
61 def en_utc() -> LocalizationContext:
62 return LocalizationContext(locale="en", timezone=ZoneInfo("Etc/UTC"))
64 @staticmethod
65 def from_user(user: User) -> LocalizationContext:
66 return LocalizationContext(
67 locale=user.ui_language_preference or DEFAULT_LOCALE,
68 timezone=ZoneInfo(user.timezone or "Etc/UTC"),
69 )