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

378 statements  

1""" 

2babel.core 

3~~~~~~~~~~ 

4 

5Core locale representation and locale data access. 

6 

7:copyright: (c) 2013-2025 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.LocaleDataDict(localedata.load(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 display_name = property( 

513 get_display_name, 

514 doc="""\ 

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 :type: `unicode` 

525 """, 

526 ) 

527 

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

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

530 

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

532 'Chinesisch' 

533 

534 .. versionadded:: 1.0 

535 

536 :param locale: the locale to use 

537 """ 

538 if locale is None: 

539 locale = self 

540 locale = Locale.parse(locale) 

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

542 

543 language_name = property( 

544 get_language_name, 

545 doc="""\ 

546 The localized language name of the locale. 

547 

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

549 'English' 

550 """, 

551 ) 

552 

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

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

555 if locale is None: 

556 locale = self 

557 locale = Locale.parse(locale) 

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

559 

560 territory_name = property( 

561 get_territory_name, 

562 doc="""\ 

563 The localized territory name of the locale if available. 

564 

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

566 'Deutschland' 

567 """, 

568 ) 

569 

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

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

572 if locale is None: 

573 locale = self 

574 locale = Locale.parse(locale) 

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

576 

577 script_name = property( 

578 get_script_name, 

579 doc="""\ 

580 The localized script name of the locale if available. 

581 

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

583 'latinica' 

584 """, 

585 ) 

586 

587 @property 

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

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

590 

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

592 'German' 

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

594 'German (Germany)' 

595 

596 :type: `unicode`""" 

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

598 

599 # { General Locale Display Names 

600 

601 @property 

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

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

604 

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

606 'Japanisch' 

607 

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

609 more information. 

610 """ 

611 return self._data['languages'] 

612 

613 @property 

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

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

616 

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

618 'Hiragana' 

619 

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

621 for more information. 

622 """ 

623 return self._data['scripts'] 

624 

625 @property 

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

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

628 

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

630 'Alemania' 

631 

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

633 for more information. 

634 """ 

635 return self._data['territories'] 

636 

637 @property 

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

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

640 

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

642 'Alte deutsche Rechtschreibung' 

643 """ 

644 return self._data['variants'] 

645 

646 # { Number Formatting 

647 

648 @property 

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

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

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

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

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

654 

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

656 'Colombian Peso' 

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

658 'Kolumbianischer Peso' 

659 """ 

660 return self._data['currency_names'] 

661 

662 @property 

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

664 """Mapping of currency codes to symbols. 

665 

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

667 '$' 

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

669 'US$' 

670 """ 

671 return self._data['currency_symbols'] 

672 

673 @property 

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

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

676 

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

678 Babel versions. 

679 

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

681 ',' 

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

683 '٫' 

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

685 '.' 

686 """ 

687 return self._data['number_symbols'] 

688 

689 @property 

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

691 """ 

692 Mapping of other numbering systems available for the locale. 

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

694 

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

696 'grek' 

697 

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

699 Babel versions. 

700 """ 

701 return self._data['numbering_systems'] 

702 

703 @property 

704 def default_numbering_system(self) -> str: 

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

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

707 'latn' 

708 """ 

709 return self._data['default_numbering_system'] 

710 

711 @property 

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

713 """Locale patterns for decimal number formatting. 

714 

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

716 Babel versions. 

717 

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

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

720 """ 

721 return self._data['decimal_formats'] 

722 

723 @property 

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

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

726 

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

728 Babel versions. 

729 

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

731 <NumberPattern '0K'> 

732 """ 

733 return self._data['compact_decimal_formats'] 

734 

735 @property 

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

737 """Locale patterns for currency number formatting. 

738 

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

740 Babel versions. 

741 

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

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

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

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

746 """ 

747 return self._data['currency_formats'] 

748 

749 @property 

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

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

752 

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

754 Babel versions. 

755 

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

757 <NumberPattern '¤0K'> 

758 """ 

759 return self._data['compact_currency_formats'] 

760 

761 @property 

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

763 """Locale patterns for percent number formatting. 

764 

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

766 Babel versions. 

767 

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

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

770 """ 

771 return self._data['percent_formats'] 

772 

773 @property 

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

775 """Locale patterns for scientific number formatting. 

776 

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

778 Babel versions. 

779 

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

781 <NumberPattern '#E0'> 

782 """ 

783 return self._data['scientific_formats'] 

784 

785 # { Calendar Information and Date Formatting 

786 

787 @property 

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

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

790 

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

792 'AM' 

793 """ 

794 try: 

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

796 except KeyError: 

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

798 

799 @property 

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

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

802 

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

804 """ 

805 return self._data['day_periods'] 

806 

807 @property 

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

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

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

811 

812 @property 

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

814 """Locale display names for weekdays. 

815 

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

817 'Donnerstag' 

818 """ 

819 return self._data['days'] 

820 

821 @property 

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

823 """Locale display names for months. 

824 

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

826 'Oktober' 

827 """ 

828 return self._data['months'] 

829 

830 @property 

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

832 """Locale display names for quarters. 

833 

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

835 '1. Quartal' 

836 """ 

837 return self._data['quarters'] 

838 

839 @property 

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

841 """Locale display names for eras. 

842 

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

844 Babel versions. 

845 

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

847 'Anno Domini' 

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

849 'BC' 

850 """ 

851 return self._data['eras'] 

852 

853 @property 

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

855 """Locale display names for time zones. 

856 

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

858 Babel versions. 

859 

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

861 'British Summer Time' 

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

863 'St. John’s' 

864 """ 

865 return self._data['time_zones'] 

866 

867 @property 

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

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

870 

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

872 have the same GMT offset and daylight savings time. 

873 

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

875 Babel versions. 

876 

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

878 'Central European Summer Time' 

879 

880 .. versionadded:: 0.9 

881 """ 

882 return self._data['meta_zones'] 

883 

884 @property 

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

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

887 

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

889 Babel versions. 

890 

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

892 '%(1)s (%(0)s)' 

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

894 'Horário %s' 

895 

896 .. versionadded:: 0.9 

897 """ 

898 return self._data['zone_formats'] 

899 

900 @property 

901 def first_week_day(self) -> int: 

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

903 

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

905 0 

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

907 6 

908 """ 

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

910 

911 @property 

912 def weekend_start(self) -> int: 

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

914 

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

916 5 

917 """ 

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

919 

920 @property 

921 def weekend_end(self) -> int: 

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

923 

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

925 6 

926 """ 

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

928 

929 @property 

930 def min_week_days(self) -> int: 

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

932 the first week of a year or month. 

933 

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

935 4 

936 """ 

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

938 

939 @property 

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

941 """Locale patterns for date formatting. 

942 

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

944 Babel versions. 

945 

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

947 <DateTimePattern 'M/d/yy'> 

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

949 <DateTimePattern 'd MMMM y'> 

950 """ 

951 return self._data['date_formats'] 

952 

953 @property 

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

955 """Locale patterns for time formatting. 

956 

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

958 Babel versions. 

959 

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

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

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

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

964 """ 

965 return self._data['time_formats'] 

966 

967 @property 

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

969 """Locale patterns for datetime formatting. 

970 

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

972 Babel versions. 

973 

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

975 '{1}, {0}' 

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

977 '{1} {0}' 

978 """ 

979 return self._data['datetime_formats'] 

980 

981 @property 

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

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

984 

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

986 <DateTimePattern 'E, M/d'> 

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

988 <DateTimePattern 'E dd/MM'> 

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

990 <DateTimePattern "HH 'h'"> 

991 """ 

992 return self._data['datetime_skeletons'] 

993 

994 @property 

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

996 """Locale patterns for interval formatting. 

997 

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

999 Babel versions. 

1000 

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

1002 smallest changing component: 

1003 

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

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

1006 

1007 .. seealso:: 

1008 

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

1010 

1011 

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

1013 """ 

1014 return self._data['interval_formats'] 

1015 

1016 @property 

1017 def plural_form(self) -> PluralRule: 

1018 """Plural rules for the locale. 

1019 

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

1021 'one' 

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

1023 'other' 

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

1025 'one' 

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

1027 'many' 

1028 """ 

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

1030 

1031 @property 

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

1033 """Patterns for generating lists 

1034 

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

1036 Babel versions. 

1037 

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

1039 '{0}, {1}' 

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

1041 '{0}, and {1}' 

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

1043 '{0} and {1}' 

1044 """ 

1045 return self._data['list_patterns'] 

1046 

1047 @property 

1048 def ordinal_form(self) -> PluralRule: 

1049 """Plural rules for the locale. 

1050 

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

1052 'one' 

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

1054 'two' 

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

1056 'few' 

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

1058 'other' 

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

1060 'other' 

1061 """ 

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

1063 

1064 @property 

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

1066 """Localized names for various measurement systems. 

1067 

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

1069 'américain' 

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

1071 'US' 

1072 

1073 """ 

1074 return self._data['measurement_systems'] 

1075 

1076 @property 

1077 def character_order(self) -> str: 

1078 """The text direction for the language. 

1079 

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

1081 'left-to-right' 

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

1083 'right-to-left' 

1084 """ 

1085 return self._data['character_order'] 

1086 

1087 @property 

1088 def text_direction(self) -> str: 

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

1090 

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

1092 'ltr' 

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

1094 'rtl' 

1095 """ 

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

1097 

1098 @property 

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

1100 """Display names for units of measurement. 

1101 

1102 .. seealso:: 

1103 

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

1105 

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

1107 Babel versions. 

1108 

1109 """ 

1110 return self._data['unit_display_names'] 

1111 

1112 

1113def default_locale( 

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

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

1116) -> str | None: 

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

1118 environment variables. 

1119 

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

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

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

1123 >>> default_locale('LC_MESSAGES') 

1124 'fr_FR' 

1125 

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

1127 "en_US_POSIX" locale: 

1128 

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

1130 >>> default_locale('LC_MESSAGES') 

1131 'en_US_POSIX' 

1132 

1133 The following fallbacks to the variable are always considered: 

1134 

1135 - ``LANGUAGE`` 

1136 - ``LC_ALL`` 

1137 - ``LC_CTYPE`` 

1138 - ``LANG`` 

1139 

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

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

1142 """ 

1143 

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

1145 if category: 

1146 if isinstance(category, str): 

1147 varnames = (category, *varnames) 

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

1149 varnames = (*category, *varnames) 

1150 else: 

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

1152 

1153 for name in varnames: 

1154 if not name: 

1155 continue 

1156 locale = os.getenv(name) 

1157 if locale: 

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

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

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

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

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

1163 locale = 'en_US_POSIX' 

1164 elif aliases and locale in aliases: 

1165 locale = aliases[locale] 

1166 try: 

1167 return get_locale_identifier(parse_locale(locale)) 

1168 except ValueError: 

1169 pass 

1170 return None 

1171 

1172 

1173def negotiate_locale( 

1174 preferred: Iterable[str], 

1175 available: Iterable[str], 

1176 sep: str = '_', 

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

1178) -> str | None: 

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

1180 

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

1182 'de_DE' 

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

1184 'de' 

1185 

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

1187 locale identifier: 

1188 

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

1190 'de_DE' 

1191 

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

1193 'de_DE' 

1194 

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

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

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

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

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

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

1201 identifiers to identifiers including the territory: 

1202 

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

1204 'ja_JP' 

1205 

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

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

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

1209 such cases, too: 

1210 

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

1212 'nb_NO' 

1213 

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

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

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

1217 

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

1219 :param available: the list of locale strings available 

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

1221 strings 

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

1223 """ 

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

1225 for locale in preferred: 

1226 ll = locale.lower() 

1227 if ll in available: 

1228 return locale 

1229 if aliases: 

1230 alias = aliases.get(ll) 

1231 if alias: 

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

1233 if alias.lower() in available: 

1234 return alias 

1235 parts = locale.split(sep) 

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

1237 return parts[0] 

1238 return None 

1239 

1240 

1241def parse_locale( 

1242 identifier: str, 

1243 sep: str = '_', 

1244) -> ( 

1245 tuple[str, str | None, str | None, str | None] 

1246 | tuple[str, str | None, str | None, str | None, str | None] 

1247): 

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

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

1250 

1251 >>> parse_locale('zh_CN') 

1252 ('zh', 'CN', None, None) 

1253 >>> parse_locale('zh_Hans_CN') 

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

1255 >>> parse_locale('ca_es_valencia') 

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

1257 >>> parse_locale('en_150') 

1258 ('en', '150', None, None) 

1259 >>> parse_locale('en_us_posix') 

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

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

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

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

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

1265 >>> parse_locale('it_IT@') 

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

1267 

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

1269 specified using the `sep` parameter. 

1270 

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

1272 

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

1274 ('zh', 'CN', None, None) 

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

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

1277 

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

1279 is raised: 

1280 

1281 >>> parse_locale('not_a_LOCALE_String') 

1282 Traceback (most recent call last): 

1283 ... 

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

1285 

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

1287 kept: 

1288 

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

1290 ('en', 'US', None, None) 

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

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

1293 

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

1295 

1296 :param identifier: the locale identifier string 

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

1298 identifier 

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

1300 identifier 

1301 """ 

1302 if not identifier: 

1303 raise ValueError("empty locale identifier") 

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

1305 if '.' in identifier: 

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

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

1308 

1309 parts = identifier.split(sep) 

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

1311 if not lang.isalpha(): 

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

1313 

1314 script = territory = variant = None 

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

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

1317 

1318 if parts: 

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

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

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

1322 territory = parts.pop(0) 

1323 

1324 if parts and ( 

1325 len(parts[0]) == 4 

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

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

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

1329 ): 

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

1331 

1332 if parts: 

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

1334 

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

1336 if modifier: 

1337 return lang, territory, script, variant, modifier 

1338 else: 

1339 return lang, territory, script, variant 

1340 

1341 

1342def get_locale_identifier( 

1343 tup: tuple[str] 

1344 | tuple[str, str | None] 

1345 | tuple[str, str | None, str | None] 

1346 | tuple[str, str | None, str | None, str | None] 

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

1348 sep: str = "_", 

1349) -> str: 

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

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

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

1353 

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

1355 'de_DE_1999@custom' 

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

1357 'fi@custom' 

1358 

1359 

1360 .. versionadded:: 1.0 

1361 

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

1363 :param sep: the separator for the identifier. 

1364 """ 

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

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

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

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

1369 

1370 

1371def get_cldr_version() -> str: 

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

1373 

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

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

1376 

1377 >>> get_cldr_version() 

1378 '47' 

1379 

1380 .. versionadded:: 2.18 

1381 

1382 :rtype: str 

1383 """ 

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