Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/babel/core.py: 37%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

389 statements  

1""" 

2babel.core 

3~~~~~~~~~~ 

4 

5Core locale representation and locale data access. 

6 

7:copyright: (c) 2013-2026 by the Babel Team. 

8:license: BSD, see LICENSE for more details. 

9""" 

10 

11from __future__ import annotations 

12 

13import os 

14import pickle 

15from collections.abc import Iterable, Mapping 

16from typing import TYPE_CHECKING, Any, Literal 

17 

18from babel import localedata 

19from babel.plural import PluralRule 

20 

21__all__ = [ 

22 'Locale', 

23 'UnknownLocaleError', 

24 'default_locale', 

25 'get_cldr_version', 

26 'get_global', 

27 'get_locale_identifier', 

28 'negotiate_locale', 

29 'parse_locale', 

30] 

31 

32if TYPE_CHECKING: 

33 from typing_extensions import TypeAlias 

34 

35 _GLOBAL_KEY: TypeAlias = Literal[ 

36 "all_currencies", 

37 "cldr", 

38 "currency_fractions", 

39 "language_aliases", 

40 "likely_subtags", 

41 "meta_zones", 

42 "parent_exceptions", 

43 "script_aliases", 

44 "territory_aliases", 

45 "territory_currencies", 

46 "territory_languages", 

47 "territory_zones", 

48 "variant_aliases", 

49 "windows_zone_mapping", 

50 "zone_aliases", 

51 "zone_territories", 

52 ] 

53 

54 _global_data: Mapping[_GLOBAL_KEY, Mapping[str, Any]] | None 

55 

56_global_data = None 

57_default_plural_rule = PluralRule({}) 

58 

59 

60def _raise_no_data_error(): 

61 raise RuntimeError( 

62 'The babel data files are not available. ' 

63 'This usually happens because you are using ' 

64 'a source checkout from Babel and you did ' 

65 'not build the data files. Just make sure ' 

66 'to run "python setup.py import_cldr" before ' 

67 'installing the library.', 

68 ) 

69 

70 

71def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]: 

72 """Return the dictionary for the given key in the global data. 

73 

74 The global data is stored in the ``babel/global.dat`` file and contains 

75 information independent of individual locales. 

76 

77 >>> get_global('zone_aliases')['UTC'] 

78 'Etc/UTC' 

79 >>> get_global('zone_territories')['Europe/Berlin'] 

80 'DE' 

81 

82 The keys available are: 

83 

84 - ``all_currencies`` 

85 - ``cldr`` (metadata) 

86 - ``currency_fractions`` 

87 - ``language_aliases`` 

88 - ``likely_subtags`` 

89 - ``parent_exceptions`` 

90 - ``script_aliases`` 

91 - ``territory_aliases`` 

92 - ``territory_currencies`` 

93 - ``territory_languages`` 

94 - ``territory_zones`` 

95 - ``variant_aliases`` 

96 - ``windows_zone_mapping`` 

97 - ``zone_aliases`` 

98 - ``zone_territories`` 

99 

100 .. note:: The internal structure of the data may change between versions. 

101 

102 .. versionadded:: 0.9 

103 

104 :param key: the data key 

105 """ 

106 global _global_data 

107 if _global_data is None: 

108 dirname = os.path.join(os.path.dirname(__file__)) 

109 filename = os.path.join(dirname, 'global.dat') 

110 if not os.path.isfile(filename): 

111 _raise_no_data_error() 

112 with open(filename, 'rb') as fileobj: 

113 _global_data = pickle.load(fileobj) 

114 assert _global_data is not None 

115 return _global_data.get(key, {}) 

116 

117 

118LOCALE_ALIASES = { 

119 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', 

120 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', 

121 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', 

122 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', 

123 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', 

124 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', 

125 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', 

126 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA', 

127} # fmt: skip 

128 

129 

130class UnknownLocaleError(Exception): 

131 """Exception thrown when a locale is requested for which no locale data 

132 is available. 

133 """ 

134 

135 def __init__(self, identifier: str) -> None: 

136 """Create the exception. 

137 

138 :param identifier: the identifier string of the unsupported locale 

139 """ 

140 Exception.__init__(self, f"unknown locale {identifier!r}") 

141 

142 #: The identifier of the locale that could not be found. 

143 self.identifier = identifier 

144 

145 

146class Locale: 

147 """Representation of a specific locale. 

148 

149 >>> locale = Locale('en', 'US') 

150 >>> repr(locale) 

151 "Locale('en', territory='US')" 

152 >>> locale.display_name 

153 'English (United States)' 

154 

155 A `Locale` object can also be instantiated from a raw locale string: 

156 

157 >>> locale = Locale.parse('en-US', sep='-') 

158 >>> repr(locale) 

159 "Locale('en', territory='US')" 

160 

161 `Locale` objects provide access to a collection of locale data, such as 

162 territory and language names, number and date format patterns, and more: 

163 

164 >>> locale.number_symbols['latn']['decimal'] 

165 '.' 

166 

167 If a locale is requested for which no locale data is available, an 

168 `UnknownLocaleError` is raised: 

169 

170 >>> Locale.parse('en_XX') 

171 Traceback (most recent call last): 

172 ... 

173 UnknownLocaleError: unknown locale 'en_XX' 

174 

175 For more information see :rfc:`3066`. 

176 """ 

177 

178 def __init__( 

179 self, 

180 language: str, 

181 territory: str | None = None, 

182 script: str | None = None, 

183 variant: str | None = None, 

184 modifier: str | None = None, 

185 ) -> None: 

186 """Initialize the locale object from the given identifier components. 

187 

188 >>> locale = Locale('en', 'US') 

189 >>> locale.language 

190 'en' 

191 >>> locale.territory 

192 'US' 

193 

194 :param language: the language code 

195 :param territory: the territory (country or region) code 

196 :param script: the script code 

197 :param variant: the variant code 

198 :param modifier: a modifier (following the '@' symbol, sometimes called '@variant') 

199 :raise `UnknownLocaleError`: if no locale data is available for the 

200 requested locale 

201 """ 

202 #: the language code 

203 self.language = language 

204 #: the territory (country or region) code 

205 self.territory = territory 

206 #: the script code 

207 self.script = script 

208 #: the variant code 

209 self.variant = variant 

210 #: the modifier 

211 self.modifier = modifier 

212 self.__data: localedata.LocaleDataDict | None = None 

213 

214 identifier = str(self) 

215 identifier_without_modifier = identifier.partition('@')[0] 

216 if localedata.exists(identifier): 

217 self.__data_identifier = identifier 

218 elif localedata.exists(identifier_without_modifier): 

219 self.__data_identifier = identifier_without_modifier 

220 else: 

221 raise UnknownLocaleError(identifier) 

222 

223 @classmethod 

224 def default( 

225 cls, 

226 category: str | None = None, 

227 aliases: Mapping[str, str] = LOCALE_ALIASES, 

228 ) -> Locale: 

229 """Return the system default locale for the specified category. 

230 

231 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']: 

232 ... os.environ[name] = '' 

233 >>> os.environ['LANG'] = 'fr_FR.UTF-8' 

234 >>> Locale.default('LC_MESSAGES') 

235 Locale('fr', territory='FR') 

236 

237 The following fallbacks to the variable are always considered: 

238 

239 - ``LANGUAGE`` 

240 - ``LC_ALL`` 

241 - ``LC_CTYPE`` 

242 - ``LANG`` 

243 

244 :param category: one of the ``LC_XXX`` environment variable names 

245 :param aliases: a dictionary of aliases for locale identifiers 

246 """ 

247 # XXX: use likely subtag expansion here instead of the 

248 # aliases dictionary. 

249 locale_string = default_locale(category, aliases=aliases) 

250 return cls.parse(locale_string) 

251 

252 @classmethod 

253 def negotiate( 

254 cls, 

255 preferred: Iterable[str], 

256 available: Iterable[str], 

257 sep: str = '_', 

258 aliases: Mapping[str, str] = LOCALE_ALIASES, 

259 ) -> Locale | None: 

260 """Find the best match between available and requested locale strings. 

261 

262 >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT']) 

263 Locale('de', territory='DE') 

264 >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de']) 

265 Locale('de') 

266 >>> Locale.negotiate(['de_DE', 'de'], ['en_US']) 

267 

268 You can specify the character used in the locale identifiers to separate 

269 the different components. This separator is applied to both lists. Also, 

270 case is ignored in the comparison: 

271 

272 >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-') 

273 Locale('de', territory='DE') 

274 

275 :param preferred: the list of locale identifiers preferred by the user 

276 :param available: the list of locale identifiers available 

277 :param aliases: a dictionary of aliases for locale identifiers 

278 :param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'. 

279 """ 

280 identifier = negotiate_locale(preferred, available, sep=sep, aliases=aliases) 

281 if identifier: 

282 return Locale.parse(identifier, sep=sep) 

283 return None 

284 

285 @classmethod 

286 def parse( 

287 cls, 

288 identifier: Locale | str | None, 

289 sep: str = '_', 

290 resolve_likely_subtags: bool = True, 

291 ) -> Locale: 

292 """Create a `Locale` instance for the given locale identifier. 

293 

294 >>> l = Locale.parse('de-DE', sep='-') 

295 >>> l.display_name 

296 'Deutsch (Deutschland)' 

297 

298 If the `identifier` parameter is not a string, but actually a `Locale` 

299 object, that object is returned: 

300 

301 >>> Locale.parse(l) 

302 Locale('de', territory='DE') 

303 

304 If the `identifier` parameter is neither of these, such as `None` 

305 or an empty string, e.g. because a default locale identifier 

306 could not be determined, a `TypeError` is raised: 

307 

308 >>> Locale.parse(None) 

309 Traceback (most recent call last): 

310 ... 

311 TypeError: ... 

312 

313 This also can perform resolving of likely subtags which it does 

314 by default. This is for instance useful to figure out the most 

315 likely locale for a territory you can use ``'und'`` as the 

316 language tag: 

317 

318 >>> Locale.parse('und_AT') 

319 Locale('de', territory='AT') 

320 

321 Modifiers are optional, and always at the end, separated by "@": 

322 

323 >>> Locale.parse('de_AT@euro') 

324 Locale('de', territory='AT', modifier='euro') 

325 

326 :param identifier: the locale identifier string 

327 :param sep: optional component separator 

328 :param resolve_likely_subtags: if this is specified then a locale will 

329 have its likely subtag resolved if the 

330 locale otherwise does not exist. For 

331 instance ``zh_TW`` by itself is not a 

332 locale that exists but Babel can 

333 automatically expand it to the full 

334 form of ``zh_hant_TW``. Note that this 

335 expansion is only taking place if no 

336 locale exists otherwise. For instance 

337 there is a locale ``en`` that can exist 

338 by itself. 

339 :raise `ValueError`: if the string does not appear to be a valid locale 

340 identifier 

341 :raise `UnknownLocaleError`: if no locale data is available for the 

342 requested locale 

343 :raise `TypeError`: if the identifier is not a string or a `Locale` 

344 :raise `ValueError`: if the identifier is not a valid string 

345 """ 

346 if isinstance(identifier, Locale): 

347 return identifier 

348 

349 if not identifier: 

350 msg = ( 

351 f"Empty locale identifier value: {identifier!r}\n\n" 

352 f"If you didn't explicitly pass an empty value to a Babel function, " 

353 f"this could be caused by there being no suitable locale environment " 

354 f"variables for the API you tried to use." 

355 ) 

356 if isinstance(identifier, str): 

357 # `parse_locale` would raise a ValueError, so let's do that here 

358 raise ValueError(msg) 

359 raise TypeError(msg) 

360 

361 if not isinstance(identifier, str): 

362 raise TypeError(f"Unexpected value for identifier: {identifier!r}") 

363 

364 # C/POSIX is not a CLDR language. Same mapping default_locale uses. 

365 posix_stem = identifier.partition(".")[0] 

366 if posix_stem.upper() in {"C", "POSIX"}: 

367 identifier = "en_US_POSIX" 

368 

369 parts = parse_locale(identifier, sep=sep) 

370 input_id = get_locale_identifier(parts) 

371 

372 def _try_load(parts): 

373 try: 

374 return cls(*parts) 

375 except UnknownLocaleError: 

376 return None 

377 

378 def _try_load_reducing(parts): 

379 # Success on first hit, return it. 

380 locale = _try_load(parts) 

381 if locale is not None: 

382 return locale 

383 

384 # Now try without script and variant 

385 locale = _try_load(parts[:2]) 

386 if locale is not None: 

387 return locale 

388 

389 locale = _try_load(parts) 

390 if locale is not None: 

391 return locale 

392 if not resolve_likely_subtags: 

393 raise UnknownLocaleError(input_id) 

394 

395 # From here onwards is some very bad likely subtag resolving. This 

396 # whole logic is not entirely correct but good enough (tm) for the 

397 # time being. This has been added so that zh_TW does not cause 

398 # errors for people when they upgrade. Later we should properly 

399 # implement ICU like fuzzy locale objects and provide a way to 

400 # maximize and minimize locale tags. 

401 

402 if len(parts) == 5: 

403 language, territory, script, variant, modifier = parts 

404 else: 

405 language, territory, script, variant = parts 

406 modifier = None 

407 language = get_global('language_aliases').get(language, language) 

408 territory = get_global('territory_aliases').get(territory or '', (territory,))[0] 

409 script = get_global('script_aliases').get(script or '', script) 

410 variant = get_global('variant_aliases').get(variant or '', variant) 

411 

412 if territory == 'ZZ': 

413 territory = None 

414 if script == 'Zzzz': 

415 script = None 

416 

417 parts = language, territory, script, variant, modifier 

418 

419 # First match: try the whole identifier 

420 new_id = get_locale_identifier(parts) 

421 likely_subtag = get_global('likely_subtags').get(new_id) 

422 if likely_subtag is not None: 

423 locale = _try_load_reducing(parse_locale(likely_subtag)) 

424 if locale is not None: 

425 return locale 

426 

427 # If we did not find anything so far, try again with a 

428 # simplified identifier that is just the language 

429 likely_subtag = get_global('likely_subtags').get(language) 

430 if likely_subtag is not None: 

431 parts2 = parse_locale(likely_subtag) 

432 if len(parts2) == 5: 

433 language2, _, script2, variant2, modifier2 = parts2 

434 else: 

435 language2, _, script2, variant2 = parts2 

436 modifier2 = None 

437 locale = _try_load_reducing( 

438 (language2, territory, script2, variant2, modifier2), 

439 ) 

440 if locale is not None: 

441 return locale 

442 

443 raise UnknownLocaleError(input_id) 

444 

445 def __eq__(self, other: object) -> bool: 

446 for key in ('language', 'territory', 'script', 'variant', 'modifier'): 

447 if not hasattr(other, key): 

448 return False 

449 return ( 

450 self.language == getattr(other, 'language') # noqa: B009 

451 and self.territory == getattr(other, 'territory') # noqa: B009 

452 and self.script == getattr(other, 'script') # noqa: B009 

453 and self.variant == getattr(other, 'variant') # noqa: B009 

454 and self.modifier == getattr(other, 'modifier') # noqa: B009 

455 ) 

456 

457 def __ne__(self, other: object) -> bool: 

458 return not self.__eq__(other) 

459 

460 def __hash__(self) -> int: 

461 return hash((self.language, self.territory, self.script, self.variant, self.modifier)) 

462 

463 def __repr__(self) -> str: 

464 parameters = [''] 

465 for key in ('territory', 'script', 'variant', 'modifier'): 

466 value = getattr(self, key) 

467 if value is not None: 

468 parameters.append(f"{key}={value!r}") 

469 return f"Locale({self.language!r}{', '.join(parameters)})" 

470 

471 def __str__(self) -> str: 

472 return get_locale_identifier( 

473 (self.language, self.territory, self.script, self.variant, self.modifier), 

474 ) 

475 

476 @property 

477 def _data(self) -> localedata.LocaleDataDict: 

478 if self.__data is None: 

479 self.__data = localedata.get_locale_data(self.__data_identifier) 

480 return self.__data 

481 

482 def get_display_name(self, locale: Locale | str | None = None) -> str | None: 

483 """Return the display name of the locale using the given locale. 

484 

485 The display name will include the language, territory, script, and 

486 variant, if those are specified. 

487 

488 >>> Locale('zh', 'CN', script='Hans').get_display_name('en') 

489 'Chinese (Simplified, China)' 

490 

491 Modifiers are currently passed through verbatim: 

492 

493 >>> Locale('it', 'IT', modifier='euro').get_display_name('en') 

494 'Italian (Italy, euro)' 

495 

496 :param locale: the locale to use 

497 """ 

498 if locale is None: 

499 locale = self 

500 locale = Locale.parse(locale) 

501 retval = locale.languages.get(self.language) 

502 if retval and (self.territory or self.script or self.variant): 

503 details = [] 

504 if self.script: 

505 details.append(locale.scripts.get(self.script)) 

506 if self.territory: 

507 details.append(locale.territories.get(self.territory)) 

508 if self.variant: 

509 details.append(locale.variants.get(self.variant)) 

510 if self.modifier: 

511 details.append(self.modifier) 

512 detail_string = ', '.join(atom for atom in details if atom) 

513 if detail_string: 

514 retval += f" ({detail_string})" 

515 return retval 

516 

517 @property 

518 def display_name(self) -> str | None: 

519 """ 

520 The localized display name of the locale. 

521 

522 >>> Locale('en').display_name 

523 'English' 

524 >>> Locale('en', 'US').display_name 

525 'English (United States)' 

526 >>> Locale('sv').display_name 

527 'svenska' 

528 """ 

529 return self.get_display_name() 

530 

531 def get_language_name(self, locale: Locale | str | None = None) -> str | None: 

532 """Return the language of this locale in the given locale. 

533 

534 >>> Locale('zh', 'CN', script='Hans').get_language_name('de') 

535 'Chinesisch' 

536 

537 .. versionadded:: 1.0 

538 

539 :param locale: the locale to use 

540 """ 

541 if locale is None: 

542 locale = self 

543 locale = Locale.parse(locale) 

544 return locale.languages.get(self.language) 

545 

546 @property 

547 def language_name(self) -> str | None: 

548 """ 

549 The localized language name of the locale. 

550 

551 >>> Locale('en', 'US').language_name 

552 'English' 

553 """ 

554 return self.get_language_name() 

555 

556 def get_territory_name(self, locale: Locale | str | None = None) -> str | None: 

557 """Return the territory name in the given locale.""" 

558 if locale is None: 

559 locale = self 

560 locale = Locale.parse(locale) 

561 return locale.territories.get(self.territory or '') 

562 

563 @property 

564 def territory_name(self) -> str | None: 

565 """ 

566 The localized territory name of the locale if available. 

567 

568 >>> Locale('de', 'DE').territory_name 

569 'Deutschland' 

570 """ 

571 return self.get_territory_name() 

572 

573 def get_script_name(self, locale: Locale | str | None = None) -> str | None: 

574 """Return the script name in the given locale.""" 

575 if locale is None: 

576 locale = self 

577 locale = Locale.parse(locale) 

578 return locale.scripts.get(self.script or '') 

579 

580 @property 

581 def script_name(self) -> str | None: 

582 """ 

583 The localized script name of the locale if available. 

584 

585 >>> Locale('sr', 'ME', script='Latn').script_name 

586 'latinica' 

587 """ 

588 return self.get_script_name() 

589 

590 @property 

591 def english_name(self) -> str | None: 

592 """The english display name of the locale. 

593 

594 >>> Locale('de').english_name 

595 'German' 

596 >>> Locale('de', 'DE').english_name 

597 'German (Germany)' 

598 """ 

599 return self.get_display_name(Locale('en')) 

600 

601 # { General Locale Display Names 

602 

603 @property 

604 def languages(self) -> localedata.LocaleDataDict: 

605 """Mapping of language codes to translated language names. 

606 

607 >>> Locale('de', 'DE').languages['ja'] 

608 'Japanisch' 

609 

610 See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for 

611 more information. 

612 """ 

613 return self._data['languages'] 

614 

615 @property 

616 def scripts(self) -> localedata.LocaleDataDict: 

617 """Mapping of script codes to translated script names. 

618 

619 >>> Locale('en', 'US').scripts['Hira'] 

620 'Hiragana' 

621 

622 See `ISO 15924 <https://www.unicode.org/iso15924/>`_ 

623 for more information. 

624 """ 

625 return self._data['scripts'] 

626 

627 @property 

628 def territories(self) -> localedata.LocaleDataDict: 

629 """Mapping of script codes to translated script names. 

630 

631 >>> Locale('es', 'CO').territories['DE'] 

632 'Alemania' 

633 

634 See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_ 

635 for more information. 

636 """ 

637 return self._data['territories'] 

638 

639 @property 

640 def variants(self) -> localedata.LocaleDataDict: 

641 """Mapping of script codes to translated script names. 

642 

643 >>> Locale('de', 'DE').variants['1901'] 

644 'Alte deutsche Rechtschreibung' 

645 """ 

646 return self._data['variants'] 

647 

648 # { Number Formatting 

649 

650 @property 

651 def currencies(self) -> localedata.LocaleDataDict: 

652 """Mapping of currency codes to translated currency names. This 

653 only returns the generic form of the currency name, not the count 

654 specific one. If an actual number is requested use the 

655 :func:`babel.numbers.get_currency_name` function. 

656 

657 >>> Locale('en').currencies['COP'] 

658 'Colombian Peso' 

659 >>> Locale('de', 'DE').currencies['COP'] 

660 'Kolumbianischer Peso' 

661 """ 

662 return self._data['currency_names'] 

663 

664 @property 

665 def currency_symbols(self) -> localedata.LocaleDataDict: 

666 """Mapping of currency codes to symbols. 

667 

668 >>> Locale('en', 'US').currency_symbols['USD'] 

669 '$' 

670 >>> Locale('es', 'CO').currency_symbols['USD'] 

671 'US$' 

672 """ 

673 return self._data['currency_symbols'] 

674 

675 @property 

676 def number_symbols(self) -> localedata.LocaleDataDict: 

677 """Symbols used in number formatting by number system. 

678 

679 .. note:: The format of the value returned may change between 

680 Babel versions. 

681 

682 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal'] 

683 ',' 

684 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal'] 

685 '٫' 

686 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal'] 

687 '.' 

688 """ 

689 return self._data['number_symbols'] 

690 

691 @property 

692 def other_numbering_systems(self) -> localedata.LocaleDataDict: 

693 """ 

694 Mapping of other numbering systems available for the locale. 

695 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems 

696 

697 >>> Locale('el', 'GR').other_numbering_systems['traditional'] 

698 'grek' 

699 

700 .. note:: The format of the value returned may change between 

701 Babel versions. 

702 """ 

703 return self._data['numbering_systems'] 

704 

705 @property 

706 def default_numbering_system(self) -> str: 

707 """The default numbering system used by the locale. 

708 >>> Locale('el', 'GR').default_numbering_system 

709 'latn' 

710 """ 

711 return self._data['default_numbering_system'] 

712 

713 @property 

714 def decimal_formats(self) -> localedata.LocaleDataDict: 

715 """Locale patterns for decimal number formatting. 

716 

717 .. note:: The format of the value returned may change between 

718 Babel versions. 

719 

720 >>> Locale('en', 'US').decimal_formats[None] 

721 <NumberPattern '#,##0.###'> 

722 """ 

723 return self._data['decimal_formats'] 

724 

725 @property 

726 def compact_decimal_formats(self) -> localedata.LocaleDataDict: 

727 """Locale patterns for compact decimal number formatting. 

728 

729 .. note:: The format of the value returned may change between 

730 Babel versions. 

731 

732 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"] 

733 <NumberPattern '0K'> 

734 """ 

735 return self._data['compact_decimal_formats'] 

736 

737 @property 

738 def currency_formats(self) -> localedata.LocaleDataDict: 

739 """Locale patterns for currency number formatting. 

740 

741 .. note:: The format of the value returned may change between 

742 Babel versions. 

743 

744 >>> Locale('en', 'US').currency_formats['standard'] 

745 <NumberPattern '\\xa4#,##0.00'> 

746 >>> Locale('en', 'US').currency_formats['accounting'] 

747 <NumberPattern '\\xa4#,##0.00;(\\xa4#,##0.00)'> 

748 """ 

749 return self._data['currency_formats'] 

750 

751 @property 

752 def compact_currency_formats(self) -> localedata.LocaleDataDict: 

753 """Locale patterns for compact currency number formatting. 

754 

755 .. note:: The format of the value returned may change between 

756 Babel versions. 

757 

758 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"] 

759 <NumberPattern '¤0K'> 

760 """ 

761 return self._data['compact_currency_formats'] 

762 

763 @property 

764 def percent_formats(self) -> localedata.LocaleDataDict: 

765 """Locale patterns for percent number formatting. 

766 

767 .. note:: The format of the value returned may change between 

768 Babel versions. 

769 

770 >>> Locale('en', 'US').percent_formats[None] 

771 <NumberPattern '#,##0%'> 

772 """ 

773 return self._data['percent_formats'] 

774 

775 @property 

776 def scientific_formats(self) -> localedata.LocaleDataDict: 

777 """Locale patterns for scientific number formatting. 

778 

779 .. note:: The format of the value returned may change between 

780 Babel versions. 

781 

782 >>> Locale('en', 'US').scientific_formats[None] 

783 <NumberPattern '#E0'> 

784 """ 

785 return self._data['scientific_formats'] 

786 

787 # { Calendar Information and Date Formatting 

788 

789 @property 

790 def periods(self) -> localedata.LocaleDataDict: 

791 """Locale display names for day periods (AM/PM). 

792 

793 >>> Locale('en', 'US').periods['am'] 

794 'AM' 

795 """ 

796 try: 

797 return self._data['day_periods']['stand-alone']['wide'] 

798 except KeyError: 

799 return localedata.LocaleDataDict({}) # pragma: no cover 

800 

801 @property 

802 def day_periods(self) -> localedata.LocaleDataDict: 

803 """Locale display names for various day periods (not necessarily only AM/PM). 

804 

805 These are not meant to be used without the relevant `day_period_rules`. 

806 """ 

807 return self._data['day_periods'] 

808 

809 @property 

810 def day_period_rules(self) -> localedata.LocaleDataDict: 

811 """Day period rules for the locale. Used by `get_period_id`.""" 

812 return self._data.get('day_period_rules', localedata.LocaleDataDict({})) 

813 

814 @property 

815 def days(self) -> localedata.LocaleDataDict: 

816 """Locale display names for weekdays. 

817 

818 >>> Locale('de', 'DE').days['format']['wide'][3] 

819 'Donnerstag' 

820 """ 

821 return self._data['days'] 

822 

823 @property 

824 def months(self) -> localedata.LocaleDataDict: 

825 """Locale display names for months. 

826 

827 >>> Locale('de', 'DE').months['format']['wide'][10] 

828 'Oktober' 

829 """ 

830 return self._data['months'] 

831 

832 @property 

833 def quarters(self) -> localedata.LocaleDataDict: 

834 """Locale display names for quarters. 

835 

836 >>> Locale('de', 'DE').quarters['format']['wide'][1] 

837 '1. Quartal' 

838 """ 

839 return self._data['quarters'] 

840 

841 @property 

842 def eras(self) -> localedata.LocaleDataDict: 

843 """Locale display names for eras. 

844 

845 .. note:: The format of the value returned may change between 

846 Babel versions. 

847 

848 >>> Locale('en', 'US').eras['wide'][1] 

849 'Anno Domini' 

850 >>> Locale('en', 'US').eras['abbreviated'][0] 

851 'BC' 

852 """ 

853 return self._data['eras'] 

854 

855 @property 

856 def time_zones(self) -> localedata.LocaleDataDict: 

857 """Locale display names for time zones. 

858 

859 .. note:: The format of the value returned may change between 

860 Babel versions. 

861 

862 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight'] 

863 'British Summer Time' 

864 >>> Locale('en', 'US').time_zones['America/St_Johns']['city'] 

865 'St. John’s' 

866 """ 

867 return self._data['time_zones'] 

868 

869 @property 

870 def meta_zones(self) -> localedata.LocaleDataDict: 

871 """Locale display names for meta time zones. 

872 

873 Meta time zones are basically groups of different Olson time zones that 

874 have the same GMT offset and daylight savings time. 

875 

876 .. note:: The format of the value returned may change between 

877 Babel versions. 

878 

879 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight'] 

880 'Central European Summer Time' 

881 

882 .. versionadded:: 0.9 

883 """ 

884 return self._data['meta_zones'] 

885 

886 @property 

887 def zone_formats(self) -> localedata.LocaleDataDict: 

888 """Patterns related to the formatting of time zones. 

889 

890 .. note:: The format of the value returned may change between 

891 Babel versions. 

892 

893 >>> Locale('en', 'US').zone_formats['fallback'] 

894 '%(1)s (%(0)s)' 

895 >>> Locale('pt', 'BR').zone_formats['region'] 

896 'Horário %s' 

897 

898 .. versionadded:: 0.9 

899 """ 

900 return self._data['zone_formats'] 

901 

902 @property 

903 def first_week_day(self) -> int: 

904 """The first day of a week, with 0 being Monday. 

905 

906 >>> Locale('de', 'DE').first_week_day 

907 0 

908 >>> Locale('en', 'US').first_week_day 

909 6 

910 """ 

911 return self._data['week_data']['first_day'] 

912 

913 @property 

914 def weekend_start(self) -> int: 

915 """The day the weekend starts, with 0 being Monday. 

916 

917 >>> Locale('de', 'DE').weekend_start 

918 5 

919 """ 

920 return self._data['week_data']['weekend_start'] 

921 

922 @property 

923 def weekend_end(self) -> int: 

924 """The day the weekend ends, with 0 being Monday. 

925 

926 >>> Locale('de', 'DE').weekend_end 

927 6 

928 """ 

929 return self._data['week_data']['weekend_end'] 

930 

931 @property 

932 def min_week_days(self) -> int: 

933 """The minimum number of days in a week so that the week is counted as 

934 the first week of a year or month. 

935 

936 >>> Locale('de', 'DE').min_week_days 

937 4 

938 """ 

939 return self._data['week_data']['min_days'] 

940 

941 @property 

942 def date_formats(self) -> localedata.LocaleDataDict: 

943 """Locale patterns for date formatting. 

944 

945 .. note:: The format of the value returned may change between 

946 Babel versions. 

947 

948 >>> Locale('en', 'US').date_formats['short'] 

949 <DateTimePattern 'M/d/yy'> 

950 >>> Locale('fr', 'FR').date_formats['long'] 

951 <DateTimePattern 'd MMMM y'> 

952 """ 

953 return self._data['date_formats'] 

954 

955 @property 

956 def time_formats(self) -> localedata.LocaleDataDict: 

957 """Locale patterns for time formatting. 

958 

959 .. note:: The format of the value returned may change between 

960 Babel versions. 

961 

962 >>> Locale('en', 'US').time_formats['short'] 

963 <DateTimePattern 'h:mm\\u202fa'> 

964 >>> Locale('fr', 'FR').time_formats['long'] 

965 <DateTimePattern 'HH:mm:ss z'> 

966 """ 

967 return self._data['time_formats'] 

968 

969 @property 

970 def datetime_formats(self) -> localedata.LocaleDataDict: 

971 """Locale patterns for datetime formatting. 

972 

973 .. note:: The format of the value returned may change between 

974 Babel versions. 

975 

976 >>> Locale('en').datetime_formats['full'] 

977 '{1}, {0}' 

978 >>> Locale('th').datetime_formats['medium'] 

979 '{1} {0}' 

980 """ 

981 return self._data['datetime_formats'] 

982 

983 @property 

984 def datetime_skeletons(self) -> localedata.LocaleDataDict: 

985 """Locale patterns for formatting parts of a datetime. 

986 

987 >>> Locale('en').datetime_skeletons['MEd'] 

988 <DateTimePattern 'E, M/d'> 

989 >>> Locale('fr').datetime_skeletons['MEd'] 

990 <DateTimePattern 'E dd/MM'> 

991 >>> Locale('fr').datetime_skeletons['H'] 

992 <DateTimePattern "HH 'h'"> 

993 """ 

994 return self._data['datetime_skeletons'] 

995 

996 @property 

997 def interval_formats(self) -> localedata.LocaleDataDict: 

998 """Locale patterns for interval formatting. 

999 

1000 .. note:: The format of the value returned may change between 

1001 Babel versions. 

1002 

1003 How to format date intervals in Finnish when the day is the 

1004 smallest changing component: 

1005 

1006 >>> Locale('fi_FI').interval_formats['MEd']['d'] 

1007 ['E d.\\u2009–\\u2009', 'E d.M.'] 

1008 

1009 .. seealso:: 

1010 

1011 The primary API to use this data is :py:func:`babel.dates.format_interval`. 

1012 

1013 

1014 :rtype: dict[str, dict[str, list[str]]] 

1015 """ 

1016 return self._data['interval_formats'] 

1017 

1018 @property 

1019 def plural_form(self) -> PluralRule: 

1020 """Plural rules for the locale. 

1021 

1022 >>> Locale('en').plural_form(1) 

1023 'one' 

1024 >>> Locale('en').plural_form(0) 

1025 'other' 

1026 >>> Locale('fr').plural_form(0) 

1027 'one' 

1028 >>> Locale('ru').plural_form(100) 

1029 'many' 

1030 """ 

1031 return self._data.get('plural_form', _default_plural_rule) 

1032 

1033 @property 

1034 def list_patterns(self) -> localedata.LocaleDataDict: 

1035 """Patterns for generating lists 

1036 

1037 .. note:: The format of the value returned may change between 

1038 Babel versions. 

1039 

1040 >>> Locale('en').list_patterns['standard']['start'] 

1041 '{0}, {1}' 

1042 >>> Locale('en').list_patterns['standard']['end'] 

1043 '{0}, and {1}' 

1044 >>> Locale('en_GB').list_patterns['standard']['end'] 

1045 '{0} and {1}' 

1046 """ 

1047 return self._data['list_patterns'] 

1048 

1049 @property 

1050 def ordinal_form(self) -> PluralRule: 

1051 """Plural rules for the locale. 

1052 

1053 >>> Locale('en').ordinal_form(1) 

1054 'one' 

1055 >>> Locale('en').ordinal_form(2) 

1056 'two' 

1057 >>> Locale('en').ordinal_form(3) 

1058 'few' 

1059 >>> Locale('fr').ordinal_form(2) 

1060 'other' 

1061 >>> Locale('ru').ordinal_form(100) 

1062 'other' 

1063 """ 

1064 return self._data.get('ordinal_form', _default_plural_rule) 

1065 

1066 @property 

1067 def measurement_systems(self) -> localedata.LocaleDataDict: 

1068 """Localized names for various measurement systems. 

1069 

1070 >>> Locale('fr', 'FR').measurement_systems['US'] 

1071 'américain' 

1072 >>> Locale('en', 'US').measurement_systems['US'] 

1073 'US' 

1074 

1075 """ 

1076 return self._data['measurement_systems'] 

1077 

1078 @property 

1079 def character_order(self) -> str: 

1080 """The text direction for the language. 

1081 

1082 >>> Locale('de', 'DE').character_order 

1083 'left-to-right' 

1084 >>> Locale('ar', 'SA').character_order 

1085 'right-to-left' 

1086 """ 

1087 return self._data['character_order'] 

1088 

1089 @property 

1090 def text_direction(self) -> str: 

1091 """The text direction for the language in CSS short-hand form. 

1092 

1093 >>> Locale('de', 'DE').text_direction 

1094 'ltr' 

1095 >>> Locale('ar', 'SA').text_direction 

1096 'rtl' 

1097 """ 

1098 return ''.join(word[0] for word in self.character_order.split('-')) 

1099 

1100 @property 

1101 def unit_display_names(self) -> localedata.LocaleDataDict: 

1102 """Display names for units of measurement. 

1103 

1104 .. seealso:: 

1105 

1106 You may want to use :py:func:`babel.units.get_unit_name` instead. 

1107 

1108 .. note:: The format of the value returned may change between 

1109 Babel versions. 

1110 

1111 """ 

1112 return self._data['unit_display_names'] 

1113 

1114 

1115def default_locale( 

1116 category: str | tuple[str, ...] | list[str] | None = None, 

1117 aliases: Mapping[str, str] = LOCALE_ALIASES, 

1118) -> str | None: 

1119 """Returns the system default locale for a given category, based on 

1120 environment variables. 

1121 

1122 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']: 

1123 ... os.environ[name] = '' 

1124 >>> os.environ['LANG'] = 'fr_FR.UTF-8' 

1125 >>> default_locale('LC_MESSAGES') 

1126 'fr_FR' 

1127 

1128 The "C" or "POSIX" pseudo-locales are treated as aliases for the 

1129 "en_US_POSIX" locale: 

1130 

1131 >>> os.environ['LC_MESSAGES'] = 'POSIX' 

1132 >>> default_locale('LC_MESSAGES') 

1133 'en_US_POSIX' 

1134 

1135 The following fallbacks to the variable are always considered: 

1136 

1137 - ``LANGUAGE`` 

1138 - ``LC_ALL`` 

1139 - ``LC_CTYPE`` 

1140 - ``LANG`` 

1141 

1142 :param category: one or more of the ``LC_XXX`` environment variable names 

1143 :param aliases: a dictionary of aliases for locale identifiers 

1144 """ 

1145 

1146 varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG') 

1147 if category: 

1148 if isinstance(category, str): 

1149 varnames = (category, *varnames) 

1150 elif isinstance(category, (list, tuple)): 

1151 varnames = (*category, *varnames) 

1152 else: 

1153 raise TypeError(f"Invalid type for category: {category!r}") 

1154 

1155 for name in varnames: 

1156 if not name: 

1157 continue 

1158 locale = os.getenv(name) 

1159 if locale: 

1160 if name == 'LANGUAGE' and ':' in locale: 

1161 # the LANGUAGE variable may contain a colon-separated list of 

1162 # language codes; we just pick the language on the list 

1163 locale = locale.split(':')[0] 

1164 if locale.split('.')[0] in ('C', 'POSIX'): 

1165 locale = 'en_US_POSIX' 

1166 elif aliases and locale in aliases: 

1167 locale = aliases[locale] 

1168 try: 

1169 return get_locale_identifier(parse_locale(locale)) 

1170 except ValueError: 

1171 pass 

1172 return None 

1173 

1174 

1175def negotiate_locale( 

1176 preferred: Iterable[str], 

1177 available: Iterable[str], 

1178 sep: str = '_', 

1179 aliases: Mapping[str, str] = LOCALE_ALIASES, 

1180) -> str | None: 

1181 """Find the best match between available and requested locale strings. 

1182 

1183 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT']) 

1184 'de_DE' 

1185 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de']) 

1186 'de' 

1187 

1188 Case is ignored by the algorithm, the result uses the case of the preferred 

1189 locale identifier: 

1190 

1191 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) 

1192 'de_DE' 

1193 

1194 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) 

1195 'de_DE' 

1196 

1197 By default, some web browsers unfortunately do not include the territory 

1198 in the locale identifier for many locales, and some don't even allow the 

1199 user to easily add the territory. So while you may prefer using qualified 

1200 locale identifiers in your web-application, they would not normally match 

1201 the language-only locale sent by such browsers. To workaround that, this 

1202 function uses a default mapping of commonly used language-only locale 

1203 identifiers to identifiers including the territory: 

1204 

1205 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US']) 

1206 'ja_JP' 

1207 

1208 Some browsers even use an incorrect or outdated language code, such as "no" 

1209 for Norwegian, where the correct locale identifier would actually be "nb_NO" 

1210 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of 

1211 such cases, too: 

1212 

1213 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE']) 

1214 'nb_NO' 

1215 

1216 You can override this default mapping by passing a different `aliases` 

1217 dictionary to this function, or you can bypass the behavior althogher by 

1218 setting the `aliases` parameter to `None`. 

1219 

1220 :param preferred: the list of locale strings preferred by the user 

1221 :param available: the list of locale strings available 

1222 :param sep: character that separates the different parts of the locale 

1223 strings 

1224 :param aliases: a dictionary of aliases for locale identifiers 

1225 """ 

1226 available = [a.lower() for a in available if a] 

1227 for locale in preferred: 

1228 ll = locale.lower() 

1229 if ll in available: 

1230 return locale 

1231 if aliases: 

1232 alias = aliases.get(ll) 

1233 if alias: 

1234 alias = alias.replace('_', sep) 

1235 if alias.lower() in available: 

1236 return alias 

1237 parts = locale.split(sep) 

1238 if len(parts) > 1 and parts[0].lower() in available: 

1239 return parts[0] 

1240 return None 

1241 

1242 

1243def parse_locale( 

1244 identifier: str, 

1245 sep: str = '_', 

1246) -> ( 

1247 tuple[str, str | None, str | None, str | None] 

1248 | tuple[str, str | None, str | None, str | None, str | None] 

1249): 

1250 """Parse a locale identifier into a tuple of the form ``(language, 

1251 territory, script, variant, modifier)``. 

1252 

1253 >>> parse_locale('zh_CN') 

1254 ('zh', 'CN', None, None) 

1255 >>> parse_locale('zh_Hans_CN') 

1256 ('zh', 'CN', 'Hans', None) 

1257 >>> parse_locale('ca_es_valencia') 

1258 ('ca', 'ES', None, 'VALENCIA') 

1259 >>> parse_locale('en_150') 

1260 ('en', '150', None, None) 

1261 >>> parse_locale('en_us_posix') 

1262 ('en', 'US', None, 'POSIX') 

1263 >>> parse_locale('it_IT@euro') 

1264 ('it', 'IT', None, None, 'euro') 

1265 >>> parse_locale('it_IT@custom') 

1266 ('it', 'IT', None, None, 'custom') 

1267 >>> parse_locale('it_IT@') 

1268 ('it', 'IT', None, None) 

1269 

1270 The default component separator is "_", but a different separator can be 

1271 specified using the `sep` parameter. 

1272 

1273 The optional modifier is always separated with "@" and at the end: 

1274 

1275 >>> parse_locale('zh-CN', sep='-') 

1276 ('zh', 'CN', None, None) 

1277 >>> parse_locale('zh-CN@custom', sep='-') 

1278 ('zh', 'CN', None, None, 'custom') 

1279 

1280 If the identifier cannot be parsed into a locale, a `ValueError` exception 

1281 is raised: 

1282 

1283 >>> parse_locale('not_a_LOCALE_String') 

1284 Traceback (most recent call last): 

1285 ... 

1286 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier 

1287 

1288 Encoding information is removed from the identifier, while modifiers are 

1289 kept: 

1290 

1291 >>> parse_locale('en_US.UTF-8') 

1292 ('en', 'US', None, None) 

1293 >>> parse_locale('de_DE.iso885915@euro') 

1294 ('de', 'DE', None, None, 'euro') 

1295 

1296 See :rfc:`4646` for more information. 

1297 

1298 :param identifier: the locale identifier string 

1299 :param sep: character that separates the different components of the locale 

1300 identifier 

1301 :raise `ValueError`: if the string does not appear to be a valid locale 

1302 identifier 

1303 """ 

1304 if not identifier: 

1305 raise ValueError("empty locale identifier") 

1306 identifier, _, modifier = identifier.partition('@') 

1307 if '.' in identifier: 

1308 # this is probably the charset/encoding, which we don't care about 

1309 identifier = identifier.split('.', 1)[0] 

1310 

1311 parts = identifier.split(sep) 

1312 lang = parts.pop(0).lower() 

1313 if not lang.isalpha(): 

1314 raise ValueError(f"expected only letters, got {lang!r}") 

1315 

1316 script = territory = variant = None 

1317 if parts and len(parts[0]) == 4 and parts[0].isalpha(): 

1318 script = parts.pop(0).title() 

1319 

1320 if parts: 

1321 if len(parts[0]) == 2 and parts[0].isalpha(): 

1322 territory = parts.pop(0).upper() 

1323 elif len(parts[0]) == 3 and parts[0].isdigit(): 

1324 territory = parts.pop(0) 

1325 

1326 if parts and ( 

1327 len(parts[0]) == 4 

1328 and parts[0][0].isdigit() 

1329 or len(parts[0]) >= 5 

1330 and parts[0][0].isalpha() 

1331 ): 

1332 variant = parts.pop().upper() 

1333 

1334 if parts: 

1335 raise ValueError(f"{identifier!r} is not a valid locale identifier") 

1336 

1337 # TODO(3.0): always return a 5-tuple 

1338 if modifier: 

1339 return lang, territory, script, variant, modifier 

1340 else: 

1341 return lang, territory, script, variant 

1342 

1343 

1344def get_locale_identifier( 

1345 tup: tuple[str] 

1346 | tuple[str, str | None] 

1347 | tuple[str, str | None, str | None] 

1348 | tuple[str, str | None, str | None, str | None] 

1349 | tuple[str, str | None, str | None, str | None, str | None], 

1350 sep: str = "_", 

1351) -> str: 

1352 """The reverse of :func:`parse_locale`. It creates a locale identifier out 

1353 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to 

1354 ``None`` and trailing ``None``\\s can also be left out of the tuple. 

1355 

1356 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom')) 

1357 'de_DE_1999@custom' 

1358 >>> get_locale_identifier(('fi', None, None, None, 'custom')) 

1359 'fi@custom' 

1360 

1361 

1362 .. versionadded:: 1.0 

1363 

1364 :param tup: the tuple as returned by :func:`parse_locale`. 

1365 :param sep: the separator for the identifier. 

1366 """ 

1367 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5 

1368 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup)) 

1369 ret = sep.join(filter(None, (lang, script, territory, variant))) 

1370 return f'{ret}@{modifier}' if modifier else ret 

1371 

1372 

1373def get_cldr_version() -> str: 

1374 """Return the Unicode CLDR version used by this Babel installation. 

1375 

1376 Generally, you should be able to assume that the return value of this 

1377 function is a string representing a version number, e.g. '47'. 

1378 

1379 >>> get_cldr_version() 

1380 '48' 

1381 

1382 .. versionadded:: 2.18 

1383 

1384 :rtype: str 

1385 """ 

1386 return str(get_global("cldr")["version"])