Coverage for src/couchers/i18n/plurals.py: 55%

55 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-12-25 10:58 +0000

1""" 

2Implements Unicode CLDR pluralization rules, used by i18next. 

3See https://cldr.unicode.org/index/cldr-spec/plural-rules 

4""" 

5 

6from collections.abc import Callable 

7from enum import Enum 

8 

9 

10class PluralCategory(Enum): 

11 """ 

12 Unicode CLDR plural categories, as defined on 

13 https://cldr.unicode.org/index/cldr-spec/plural-rules 

14 """ 

15 

16 ZERO = "zero" 

17 ONE = "one" 

18 TWO = "two" 

19 FEW = "few" 

20 MANY = "many" 

21 OTHER = "other" 

22 

23 

24type PluralRule = Callable[[int], PluralCategory] 

25"""Selects the plural category for a given count.""" 

26 

27 

28class PluralRules: 

29 """ 

30 Implements Unicode CLDR language rules for known languages. 

31 See https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html 

32 """ 

33 

34 @staticmethod 

35 def for_language(lang: str) -> PluralRule | None: 

36 separator_index = lang.find("-") 

37 lang_family = lang if separator_index == -1 else lang[0:separator_index] 

38 

39 # Resolve one of the methods below 

40 return getattr(PluralRules, lang_family, default=None) 

41 

42 @staticmethod 

43 def de(count: int) -> PluralCategory: 

44 return PluralRules.en(count) # Same as EN 

45 

46 @staticmethod 

47 def en(count: int) -> PluralCategory: 

48 count = abs(count) 

49 if count == 1: 

50 return PluralCategory.ONE # 1 apple 

51 return PluralCategory.OTHER # 2 apples 

52 

53 @staticmethod 

54 def es(count: int) -> PluralCategory: 

55 count = abs(count) 

56 if count == 1: 

57 return PluralCategory.ONE # 1 manzana 

58 if count > 0 and count % 1_000_000 == 0: 

59 return PluralCategory.MANY # 1000000 de manzanas 

60 

61 return PluralCategory.OTHER # 2 manzanas 

62 

63 @staticmethod 

64 def fr(count: int) -> PluralCategory: 

65 count = abs(count) 

66 if count == 0 or count == 1: 

67 return PluralCategory.ONE # 0 pomme, 1 pomme 

68 if count % 1_000_000 == 0: 

69 return PluralCategory.MANY # 1000000 de pommes 

70 

71 return PluralCategory.OTHER # 2 pommes 

72 

73 @staticmethod 

74 def pt(count: int) -> PluralCategory: 

75 return PluralRules.fr(count) # Same as FR 

76 

77 @staticmethod 

78 def ru(count: int) -> PluralCategory: 

79 count = abs(count) 

80 if count % 10 == 1 and count % 100 != 11: 

81 return PluralCategory.ONE 

82 elif count % 10 in range(2, 5) and count % 100 not in range(12, 15): 

83 return PluralCategory.FEW 

84 elif count % 10 == 0 or count % 10 >= 5 or count % 100 in range(11, 15): 

85 return PluralCategory.MANY 

86 return PluralCategory.OTHER