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

1from dataclasses import dataclass 

2from datetime import date, datetime, time 

3from zoneinfo import ZoneInfo 

4 

5from google.protobuf.timestamp_pb2 import Timestamp 

6 

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 

18 

19 

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 """ 

26 

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 

30 

31 # The timezone to use when formatting date-times and instants. 

32 timezone: ZoneInfo 

33 

34 @property 

35 def localized_timezone(self) -> str: 

36 return localize_timezone(self.timezone, self.locale) 

37 

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) 

43 

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) 

48 

49 def localize_date_from_iso(self, value: str) -> str: 

50 return localize_date_from_iso(value, self.locale) 

51 

52 def localize_datetime(self, value: datetime | Timestamp) -> str: 

53 return localize_datetime(value, self.timezone, self.locale) 

54 

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) 

59 

60 @staticmethod 

61 def en_utc() -> LocalizationContext: 

62 return LocalizationContext(locale="en", timezone=ZoneInfo("Etc/UTC")) 

63 

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 )