Coverage for adhoc-cicd-odoo-odoo / odoo / tools / i18n.py: 22%

27 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-09 18:22 +0000

1from __future__ import annotations 

2 

3import re 

4from typing import TYPE_CHECKING, Literal 

5 

6from babel import lists 

7 

8from odoo.tools.misc import babel_locale_parse, get_lang 

9 

10if TYPE_CHECKING: 

11 from collections.abc import Iterable 

12 import odoo.api 

13 

14XPG_LOCALE_RE = re.compile( 

15 r"""^ 

16 ([a-z]+) # language 

17 (_[A-Z\d]+)? # maybe _territory 

18 # no support for .codeset (we don't use that in Odoo) 

19 (@.+)? # maybe @modifier 

20 $""", 

21 re.VERBOSE, 

22) 

23 

24 

25def format_list( 

26 env: odoo.api.Environment, 

27 lst: Iterable, 

28 style: Literal["standard", "standard-short", "or", "or-short", "unit", "unit-short", "unit-narrow"] = "standard", 

29 lang_code: str | None = None, 

30) -> str: 

31 """ 

32 Format the items in `lst` as a list in a locale-dependent manner with the chosen style. 

33 

34 The available styles are defined by babel according to the Unicode TR35-49 spec: 

35 * standard: 

36 A typical 'and' list for arbitrary placeholders. 

37 e.g. "January, February, and March" 

38 * standard-short: 

39 A short version of an 'and' list, suitable for use with short or abbreviated placeholder values. 

40 e.g. "Jan., Feb., and Mar." 

41 * or: 

42 A typical 'or' list for arbitrary placeholders. 

43 e.g. "January, February, or March" 

44 * or-short: 

45 A short version of an 'or' list. 

46 e.g. "Jan., Feb., or Mar." 

47 * unit: 

48 A list suitable for wide units. 

49 e.g. "3 feet, 7 inches" 

50 * unit-short: 

51 A list suitable for short units 

52 e.g. "3 ft, 7 in" 

53 * unit-narrow: 

54 A list suitable for narrow units, where space on the screen is very limited. 

55 e.g. "3′ 7″" 

56 

57 See https://www.unicode.org/reports/tr35/tr35-49/tr35-general.html#ListPatterns for more details. 

58 

59 :param env: the current environment. 

60 :param lst: the iterable of items to format into a list. 

61 :param style: the style to format the list with. 

62 :param lang_code: the locale (i.e. en_US). 

63 :return: the formatted list. 

64 """ 

65 locale = babel_locale_parse(lang_code or get_lang(env).code) 

66 # Some styles could be unavailable for the chosen locale 

67 if style not in locale.list_patterns: 

68 style = "standard" 

69 try: 

70 return lists.format_list([str(el) for el in lst], style, locale) 

71 except KeyError: 

72 return lists.format_list([str(el) for el in lst], 'standard', locale) 

73 

74 

75def py_to_js_locale(locale: str) -> str: 

76 """ 

77 Converts a locale from Python to JavaScript format. 

78 

79 Most of the time the conversion is simply to replace _ with -. 

80 Example: fr_BE → fr-BE 

81 

82 Exception: Serbian can be written in both Latin and Cyrillic scripts 

83 interchangeably, therefore its locale includes a special modifier 

84 to indicate which script to use. 

85 Example: sr@latin → sr-Latn 

86 

87 BCP 47 (JS): 

88 language[-extlang][-script][-region][-variant][-extension][-privateuse] 

89 https://www.ietf.org/rfc/rfc5646.txt 

90 XPG syntax (Python): 

91 language[_territory][.codeset][@modifier] 

92 https://www.gnu.org/software/libc/manual/html_node/Locale-Names.html 

93 

94 :param locale: The locale formatted for use on the Python-side. 

95 :return: The locale formatted for use on the JavaScript-side. 

96 """ 

97 match_ = XPG_LOCALE_RE.match(locale) 

98 if not match_: 

99 return locale 

100 language, territory, modifier = match_.groups() 

101 subtags = [language] 

102 if modifier == "@Cyrl": 

103 subtags.append("Cyrl") 

104 elif modifier == "@latin": 

105 subtags.append("Latn") 

106 if territory: 

107 subtags.append(territory.removeprefix("_")) 

108 return "-".join(subtags)