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

386 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 parts = parse_locale(identifier, sep=sep) 

365 input_id = get_locale_identifier(parts) 

366 

367 def _try_load(parts): 

368 try: 

369 return cls(*parts) 

370 except UnknownLocaleError: 

371 return None 

372 

373 def _try_load_reducing(parts): 

374 # Success on first hit, return it. 

375 locale = _try_load(parts) 

376 if locale is not None: 

377 return locale 

378 

379 # Now try without script and variant 

380 locale = _try_load(parts[:2]) 

381 if locale is not None: 

382 return locale 

383 

384 locale = _try_load(parts) 

385 if locale is not None: 

386 return locale 

387 if not resolve_likely_subtags: 

388 raise UnknownLocaleError(input_id) 

389 

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

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

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

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

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

395 # maximize and minimize locale tags. 

396 

397 if len(parts) == 5: 

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

399 else: 

400 language, territory, script, variant = parts 

401 modifier = None 

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

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

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

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

406 

407 if territory == 'ZZ': 

408 territory = None 

409 if script == 'Zzzz': 

410 script = None 

411 

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

413 

414 # First match: try the whole identifier 

415 new_id = get_locale_identifier(parts) 

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

417 if likely_subtag is not None: 

418 locale = _try_load_reducing(parse_locale(likely_subtag)) 

419 if locale is not None: 

420 return locale 

421 

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

423 # simplified identifier that is just the language 

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

425 if likely_subtag is not None: 

426 parts2 = parse_locale(likely_subtag) 

427 if len(parts2) == 5: 

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

429 else: 

430 language2, _, script2, variant2 = parts2 

431 modifier2 = None 

432 locale = _try_load_reducing( 

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

434 ) 

435 if locale is not None: 

436 return locale 

437 

438 raise UnknownLocaleError(input_id) 

439 

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

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

442 if not hasattr(other, key): 

443 return False 

444 return ( 

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

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

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

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

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

450 ) 

451 

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

453 return not self.__eq__(other) 

454 

455 def __hash__(self) -> int: 

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

457 

458 def __repr__(self) -> str: 

459 parameters = [''] 

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

461 value = getattr(self, key) 

462 if value is not None: 

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

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

465 

466 def __str__(self) -> str: 

467 return get_locale_identifier( 

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

469 ) 

470 

471 @property 

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

473 if self.__data is None: 

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

475 return self.__data 

476 

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

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

479 

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

481 variant, if those are specified. 

482 

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

484 'Chinese (Simplified, China)' 

485 

486 Modifiers are currently passed through verbatim: 

487 

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

489 'Italian (Italy, euro)' 

490 

491 :param locale: the locale to use 

492 """ 

493 if locale is None: 

494 locale = self 

495 locale = Locale.parse(locale) 

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

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

498 details = [] 

499 if self.script: 

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

501 if self.territory: 

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

503 if self.variant: 

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

505 if self.modifier: 

506 details.append(self.modifier) 

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

508 if detail_string: 

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

510 return retval 

511 

512 @property 

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

514 """ 

515 The localized display name of the locale. 

516 

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

518 'English' 

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

520 'English (United States)' 

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

522 'svenska' 

523 """ 

524 return self.get_display_name() 

525 

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

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

528 

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

530 'Chinesisch' 

531 

532 .. versionadded:: 1.0 

533 

534 :param locale: the locale to use 

535 """ 

536 if locale is None: 

537 locale = self 

538 locale = Locale.parse(locale) 

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

540 

541 @property 

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

543 """ 

544 The localized language name of the locale. 

545 

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

547 'English' 

548 """ 

549 return self.get_language_name() 

550 

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

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

553 if locale is None: 

554 locale = self 

555 locale = Locale.parse(locale) 

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

557 

558 @property 

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

560 """ 

561 The localized territory name of the locale if available. 

562 

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

564 'Deutschland' 

565 """ 

566 return self.get_territory_name() 

567 

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

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

570 if locale is None: 

571 locale = self 

572 locale = Locale.parse(locale) 

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

574 

575 @property 

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

577 """ 

578 The localized script name of the locale if available. 

579 

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

581 'latinica' 

582 """ 

583 return self.get_script_name() 

584 

585 @property 

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

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

588 

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

590 'German' 

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

592 'German (Germany)' 

593 """ 

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

595 

596 # { General Locale Display Names 

597 

598 @property 

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

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

601 

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

603 'Japanisch' 

604 

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

606 more information. 

607 """ 

608 return self._data['languages'] 

609 

610 @property 

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

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

613 

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

615 'Hiragana' 

616 

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

618 for more information. 

619 """ 

620 return self._data['scripts'] 

621 

622 @property 

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

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

625 

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

627 'Alemania' 

628 

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

630 for more information. 

631 """ 

632 return self._data['territories'] 

633 

634 @property 

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

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

637 

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

639 'Alte deutsche Rechtschreibung' 

640 """ 

641 return self._data['variants'] 

642 

643 # { Number Formatting 

644 

645 @property 

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

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

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

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

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

651 

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

653 'Colombian Peso' 

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

655 'Kolumbianischer Peso' 

656 """ 

657 return self._data['currency_names'] 

658 

659 @property 

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

661 """Mapping of currency codes to symbols. 

662 

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

664 '$' 

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

666 'US$' 

667 """ 

668 return self._data['currency_symbols'] 

669 

670 @property 

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

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

673 

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

675 Babel versions. 

676 

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

678 ',' 

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

680 '٫' 

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

682 '.' 

683 """ 

684 return self._data['number_symbols'] 

685 

686 @property 

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

688 """ 

689 Mapping of other numbering systems available for the locale. 

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

691 

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

693 'grek' 

694 

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

696 Babel versions. 

697 """ 

698 return self._data['numbering_systems'] 

699 

700 @property 

701 def default_numbering_system(self) -> str: 

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

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

704 'latn' 

705 """ 

706 return self._data['default_numbering_system'] 

707 

708 @property 

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

710 """Locale patterns for decimal number formatting. 

711 

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

713 Babel versions. 

714 

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

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

717 """ 

718 return self._data['decimal_formats'] 

719 

720 @property 

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

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

723 

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

725 Babel versions. 

726 

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

728 <NumberPattern '0K'> 

729 """ 

730 return self._data['compact_decimal_formats'] 

731 

732 @property 

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

734 """Locale patterns for currency number formatting. 

735 

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

737 Babel versions. 

738 

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

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

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

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

743 """ 

744 return self._data['currency_formats'] 

745 

746 @property 

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

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

749 

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

751 Babel versions. 

752 

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

754 <NumberPattern '¤0K'> 

755 """ 

756 return self._data['compact_currency_formats'] 

757 

758 @property 

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

760 """Locale patterns for percent number formatting. 

761 

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

763 Babel versions. 

764 

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

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

767 """ 

768 return self._data['percent_formats'] 

769 

770 @property 

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

772 """Locale patterns for scientific number formatting. 

773 

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

775 Babel versions. 

776 

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

778 <NumberPattern '#E0'> 

779 """ 

780 return self._data['scientific_formats'] 

781 

782 # { Calendar Information and Date Formatting 

783 

784 @property 

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

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

787 

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

789 'AM' 

790 """ 

791 try: 

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

793 except KeyError: 

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

795 

796 @property 

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

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

799 

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

801 """ 

802 return self._data['day_periods'] 

803 

804 @property 

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

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

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

808 

809 @property 

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

811 """Locale display names for weekdays. 

812 

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

814 'Donnerstag' 

815 """ 

816 return self._data['days'] 

817 

818 @property 

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

820 """Locale display names for months. 

821 

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

823 'Oktober' 

824 """ 

825 return self._data['months'] 

826 

827 @property 

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

829 """Locale display names for quarters. 

830 

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

832 '1. Quartal' 

833 """ 

834 return self._data['quarters'] 

835 

836 @property 

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

838 """Locale display names for eras. 

839 

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

841 Babel versions. 

842 

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

844 'Anno Domini' 

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

846 'BC' 

847 """ 

848 return self._data['eras'] 

849 

850 @property 

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

852 """Locale display names for time zones. 

853 

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

855 Babel versions. 

856 

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

858 'British Summer Time' 

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

860 'St. John’s' 

861 """ 

862 return self._data['time_zones'] 

863 

864 @property 

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

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

867 

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

869 have the same GMT offset and daylight savings time. 

870 

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

872 Babel versions. 

873 

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

875 'Central European Summer Time' 

876 

877 .. versionadded:: 0.9 

878 """ 

879 return self._data['meta_zones'] 

880 

881 @property 

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

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

884 

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

886 Babel versions. 

887 

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

889 '%(1)s (%(0)s)' 

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

891 'Horário %s' 

892 

893 .. versionadded:: 0.9 

894 """ 

895 return self._data['zone_formats'] 

896 

897 @property 

898 def first_week_day(self) -> int: 

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

900 

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

902 0 

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

904 6 

905 """ 

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

907 

908 @property 

909 def weekend_start(self) -> int: 

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

911 

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

913 5 

914 """ 

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

916 

917 @property 

918 def weekend_end(self) -> int: 

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

920 

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

922 6 

923 """ 

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

925 

926 @property 

927 def min_week_days(self) -> int: 

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

929 the first week of a year or month. 

930 

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

932 4 

933 """ 

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

935 

936 @property 

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

938 """Locale patterns for date formatting. 

939 

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

941 Babel versions. 

942 

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

944 <DateTimePattern 'M/d/yy'> 

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

946 <DateTimePattern 'd MMMM y'> 

947 """ 

948 return self._data['date_formats'] 

949 

950 @property 

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

952 """Locale patterns for time formatting. 

953 

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

955 Babel versions. 

956 

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

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

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

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

961 """ 

962 return self._data['time_formats'] 

963 

964 @property 

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

966 """Locale patterns for datetime formatting. 

967 

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

969 Babel versions. 

970 

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

972 '{1}, {0}' 

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

974 '{1} {0}' 

975 """ 

976 return self._data['datetime_formats'] 

977 

978 @property 

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

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

981 

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

983 <DateTimePattern 'E, M/d'> 

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

985 <DateTimePattern 'E dd/MM'> 

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

987 <DateTimePattern "HH 'h'"> 

988 """ 

989 return self._data['datetime_skeletons'] 

990 

991 @property 

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

993 """Locale patterns for interval formatting. 

994 

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

996 Babel versions. 

997 

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

999 smallest changing component: 

1000 

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

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

1003 

1004 .. seealso:: 

1005 

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

1007 

1008 

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

1010 """ 

1011 return self._data['interval_formats'] 

1012 

1013 @property 

1014 def plural_form(self) -> PluralRule: 

1015 """Plural rules for the locale. 

1016 

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

1018 'one' 

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

1020 'other' 

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

1022 'one' 

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

1024 'many' 

1025 """ 

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

1027 

1028 @property 

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

1030 """Patterns for generating lists 

1031 

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

1033 Babel versions. 

1034 

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

1036 '{0}, {1}' 

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

1038 '{0}, and {1}' 

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

1040 '{0} and {1}' 

1041 """ 

1042 return self._data['list_patterns'] 

1043 

1044 @property 

1045 def ordinal_form(self) -> PluralRule: 

1046 """Plural rules for the locale. 

1047 

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

1049 'one' 

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

1051 'two' 

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

1053 'few' 

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

1055 'other' 

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

1057 'other' 

1058 """ 

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

1060 

1061 @property 

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

1063 """Localized names for various measurement systems. 

1064 

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

1066 'américain' 

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

1068 'US' 

1069 

1070 """ 

1071 return self._data['measurement_systems'] 

1072 

1073 @property 

1074 def character_order(self) -> str: 

1075 """The text direction for the language. 

1076 

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

1078 'left-to-right' 

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

1080 'right-to-left' 

1081 """ 

1082 return self._data['character_order'] 

1083 

1084 @property 

1085 def text_direction(self) -> str: 

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

1087 

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

1089 'ltr' 

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

1091 'rtl' 

1092 """ 

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

1094 

1095 @property 

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

1097 """Display names for units of measurement. 

1098 

1099 .. seealso:: 

1100 

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

1102 

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

1104 Babel versions. 

1105 

1106 """ 

1107 return self._data['unit_display_names'] 

1108 

1109 

1110def default_locale( 

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

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

1113) -> str | None: 

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

1115 environment variables. 

1116 

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

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

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

1120 >>> default_locale('LC_MESSAGES') 

1121 'fr_FR' 

1122 

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

1124 "en_US_POSIX" locale: 

1125 

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

1127 >>> default_locale('LC_MESSAGES') 

1128 'en_US_POSIX' 

1129 

1130 The following fallbacks to the variable are always considered: 

1131 

1132 - ``LANGUAGE`` 

1133 - ``LC_ALL`` 

1134 - ``LC_CTYPE`` 

1135 - ``LANG`` 

1136 

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

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

1139 """ 

1140 

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

1142 if category: 

1143 if isinstance(category, str): 

1144 varnames = (category, *varnames) 

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

1146 varnames = (*category, *varnames) 

1147 else: 

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

1149 

1150 for name in varnames: 

1151 if not name: 

1152 continue 

1153 locale = os.getenv(name) 

1154 if locale: 

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

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

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

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

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

1160 locale = 'en_US_POSIX' 

1161 elif aliases and locale in aliases: 

1162 locale = aliases[locale] 

1163 try: 

1164 return get_locale_identifier(parse_locale(locale)) 

1165 except ValueError: 

1166 pass 

1167 return None 

1168 

1169 

1170def negotiate_locale( 

1171 preferred: Iterable[str], 

1172 available: Iterable[str], 

1173 sep: str = '_', 

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

1175) -> str | None: 

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

1177 

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

1179 'de_DE' 

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

1181 'de' 

1182 

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

1184 locale identifier: 

1185 

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

1187 'de_DE' 

1188 

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

1190 'de_DE' 

1191 

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

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

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

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

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

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

1198 identifiers to identifiers including the territory: 

1199 

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

1201 'ja_JP' 

1202 

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

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

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

1206 such cases, too: 

1207 

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

1209 'nb_NO' 

1210 

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

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

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

1214 

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

1216 :param available: the list of locale strings available 

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

1218 strings 

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

1220 """ 

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

1222 for locale in preferred: 

1223 ll = locale.lower() 

1224 if ll in available: 

1225 return locale 

1226 if aliases: 

1227 alias = aliases.get(ll) 

1228 if alias: 

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

1230 if alias.lower() in available: 

1231 return alias 

1232 parts = locale.split(sep) 

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

1234 return parts[0] 

1235 return None 

1236 

1237 

1238def parse_locale( 

1239 identifier: str, 

1240 sep: str = '_', 

1241) -> ( 

1242 tuple[str, str | None, str | None, str | None] 

1243 | tuple[str, str | None, str | None, str | None, str | None] 

1244): 

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

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

1247 

1248 >>> parse_locale('zh_CN') 

1249 ('zh', 'CN', None, None) 

1250 >>> parse_locale('zh_Hans_CN') 

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

1252 >>> parse_locale('ca_es_valencia') 

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

1254 >>> parse_locale('en_150') 

1255 ('en', '150', None, None) 

1256 >>> parse_locale('en_us_posix') 

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

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

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

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

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

1262 >>> parse_locale('it_IT@') 

1263 ('it', 'IT', None, None) 

1264 

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

1266 specified using the `sep` parameter. 

1267 

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

1269 

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

1271 ('zh', 'CN', None, None) 

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

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

1274 

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

1276 is raised: 

1277 

1278 >>> parse_locale('not_a_LOCALE_String') 

1279 Traceback (most recent call last): 

1280 ... 

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

1282 

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

1284 kept: 

1285 

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

1287 ('en', 'US', None, None) 

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

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

1290 

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

1292 

1293 :param identifier: the locale identifier string 

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

1295 identifier 

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

1297 identifier 

1298 """ 

1299 if not identifier: 

1300 raise ValueError("empty locale identifier") 

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

1302 if '.' in identifier: 

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

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

1305 

1306 parts = identifier.split(sep) 

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

1308 if not lang.isalpha(): 

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

1310 

1311 script = territory = variant = None 

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

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

1314 

1315 if parts: 

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

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

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

1319 territory = parts.pop(0) 

1320 

1321 if parts and ( 

1322 len(parts[0]) == 4 

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

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

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

1326 ): 

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

1328 

1329 if parts: 

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

1331 

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

1333 if modifier: 

1334 return lang, territory, script, variant, modifier 

1335 else: 

1336 return lang, territory, script, variant 

1337 

1338 

1339def get_locale_identifier( 

1340 tup: tuple[str] 

1341 | tuple[str, str | None] 

1342 | tuple[str, str | None, str | None] 

1343 | tuple[str, str | None, str | None, str | None] 

1344 | tuple[str, str | None, str | None, str | None, str | None], 

1345 sep: str = "_", 

1346) -> str: 

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

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

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

1350 

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

1352 'de_DE_1999@custom' 

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

1354 'fi@custom' 

1355 

1356 

1357 .. versionadded:: 1.0 

1358 

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

1360 :param sep: the separator for the identifier. 

1361 """ 

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

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

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

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

1366 

1367 

1368def get_cldr_version() -> str: 

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

1370 

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

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

1373 

1374 >>> get_cldr_version() 

1375 '48' 

1376 

1377 .. versionadded:: 2.18 

1378 

1379 :rtype: str 

1380 """ 

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