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:05 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:05 +0000
1from __future__ import annotations
3import re
4from typing import TYPE_CHECKING, Literal
6from babel import lists
8from odoo.tools.misc import babel_locale_parse, get_lang
10if TYPE_CHECKING:
11 from collections.abc import Iterable
12 import odoo.api
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)
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.
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″"
57 See https://www.unicode.org/reports/tr35/tr35-49/tr35-general.html#ListPatterns for more details.
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)
75def py_to_js_locale(locale: str) -> str:
76 """
77 Converts a locale from Python to JavaScript format.
79 Most of the time the conversion is simply to replace _ with -.
80 Example: fr_BE → fr-BE
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
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
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)