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
« 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"""
6from collections.abc import Callable
7from enum import Enum
10class PluralCategory(Enum):
11 """
12 Unicode CLDR plural categories, as defined on
13 https://cldr.unicode.org/index/cldr-spec/plural-rules
14 """
16 ZERO = "zero"
17 ONE = "one"
18 TWO = "two"
19 FEW = "few"
20 MANY = "many"
21 OTHER = "other"
24type PluralRule = Callable[[int], PluralCategory]
25"""Selects the plural category for a given count."""
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 """
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]
39 # Resolve one of the methods below
40 return getattr(PluralRules, lang_family, default=None)
42 @staticmethod
43 def de(count: int) -> PluralCategory:
44 return PluralRules.en(count) # Same as EN
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
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
61 return PluralCategory.OTHER # 2 manzanas
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
71 return PluralCategory.OTHER # 2 pommes
73 @staticmethod
74 def pt(count: int) -> PluralCategory:
75 return PluralRules.fr(count) # Same as FR
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