Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

357 statements  

1""" 

2 babel.core 

3 ~~~~~~~~~~ 

4 

5 Core locale representation and locale data access. 

6 

7 :copyright: (c) 2013-2024 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 

17 

18from babel import localedata 

19from babel.plural import PluralRule 

20 

21__all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale', 

22 'parse_locale'] 

23 

24if TYPE_CHECKING: 

25 from typing_extensions import Literal, TypeAlias 

26 

27 _GLOBAL_KEY: TypeAlias = Literal[ 

28 "all_currencies", 

29 "currency_fractions", 

30 "language_aliases", 

31 "likely_subtags", 

32 "meta_zones", 

33 "parent_exceptions", 

34 "script_aliases", 

35 "territory_aliases", 

36 "territory_currencies", 

37 "territory_languages", 

38 "territory_zones", 

39 "variant_aliases", 

40 "windows_zone_mapping", 

41 "zone_aliases", 

42 "zone_territories", 

43 ] 

44 

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

46 

47_global_data = None 

48_default_plural_rule = PluralRule({}) 

49 

50 

51def _raise_no_data_error(): 

52 raise RuntimeError('The babel data files are not available. ' 

53 'This usually happens because you are using ' 

54 'a source checkout from Babel and you did ' 

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

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

57 'installing the library.') 

58 

59 

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

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

62 

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

64 information independent of individual locales. 

65 

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

67 u'Etc/UTC' 

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

69 u'DE' 

70 

71 The keys available are: 

72 

73 - ``all_currencies`` 

74 - ``currency_fractions`` 

75 - ``language_aliases`` 

76 - ``likely_subtags`` 

77 - ``parent_exceptions`` 

78 - ``script_aliases`` 

79 - ``territory_aliases`` 

80 - ``territory_currencies`` 

81 - ``territory_languages`` 

82 - ``territory_zones`` 

83 - ``variant_aliases`` 

84 - ``windows_zone_mapping`` 

85 - ``zone_aliases`` 

86 - ``zone_territories`` 

87 

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

89 

90 .. versionadded:: 0.9 

91 

92 :param key: the data key 

93 """ 

94 global _global_data 

95 if _global_data is None: 

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

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

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

99 _raise_no_data_error() 

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

101 _global_data = pickle.load(fileobj) 

102 assert _global_data is not None 

103 return _global_data.get(key, {}) 

104 

105 

106LOCALE_ALIASES = { 

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

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

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

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

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

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

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

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

115} 

116 

117 

118class UnknownLocaleError(Exception): 

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

120 is available. 

121 """ 

122 

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

124 """Create the exception. 

125 

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

127 """ 

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

129 

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

131 self.identifier = identifier 

132 

133 

134class Locale: 

135 """Representation of a specific locale. 

136 

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

138 >>> repr(locale) 

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

140 >>> locale.display_name 

141 u'English (United States)' 

142 

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

144 

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

146 >>> repr(locale) 

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

148 

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

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

151 

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

153 u'.' 

154 

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

156 `UnknownLocaleError` is raised: 

157 

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

159 Traceback (most recent call last): 

160 ... 

161 UnknownLocaleError: unknown locale 'en_XX' 

162 

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

164 """ 

165 

166 def __init__( 

167 self, 

168 language: str, 

169 territory: str | None = None, 

170 script: str | None = None, 

171 variant: str | None = None, 

172 modifier: str | None = None, 

173 ) -> None: 

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

175 

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

177 >>> locale.language 

178 'en' 

179 >>> locale.territory 

180 'US' 

181 

182 :param language: the language code 

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

184 :param script: the script code 

185 :param variant: the variant code 

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

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

188 requested locale 

189 """ 

190 #: the language code 

191 self.language = language 

192 #: the territory (country or region) code 

193 self.territory = territory 

194 #: the script code 

195 self.script = script 

196 #: the variant code 

197 self.variant = variant 

198 #: the modifier 

199 self.modifier = modifier 

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

201 

202 identifier = str(self) 

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

204 if not localedata.exists(identifier_without_modifier): 

205 raise UnknownLocaleError(identifier) 

206 

207 @classmethod 

208 def default(cls, category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> Locale: 

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

210 

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

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

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

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

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

216 

217 The following fallbacks to the variable are always considered: 

218 

219 - ``LANGUAGE`` 

220 - ``LC_ALL`` 

221 - ``LC_CTYPE`` 

222 - ``LANG`` 

223 

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

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

226 """ 

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

228 # aliases dictionary. 

229 locale_string = default_locale(category, aliases=aliases) 

230 return cls.parse(locale_string) 

231 

232 @classmethod 

233 def negotiate( 

234 cls, 

235 preferred: Iterable[str], 

236 available: Iterable[str], 

237 sep: str = '_', 

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

239 ) -> Locale | None: 

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

241 

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

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

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

245 Locale('de') 

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

247 

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

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

250 case is ignored in the comparison: 

251 

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

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

254 

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

256 :param available: the list of locale identifiers available 

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

258 """ 

259 identifier = negotiate_locale(preferred, available, sep=sep, 

260 aliases=aliases) 

261 if identifier: 

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

263 return None 

264 

265 @classmethod 

266 def parse( 

267 cls, 

268 identifier: str | Locale | None, 

269 sep: str = '_', 

270 resolve_likely_subtags: bool = True, 

271 ) -> Locale: 

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

273 

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

275 >>> l.display_name 

276 u'Deutsch (Deutschland)' 

277 

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

279 object, that object is returned: 

280 

281 >>> Locale.parse(l) 

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

283 

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

285 e.g. because a default locale identifier could not be determined, 

286 a `TypeError` is raised: 

287 

288 >>> Locale.parse(None) 

289 Traceback (most recent call last): 

290 ... 

291 TypeError: ... 

292 

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

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

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

296 language tag: 

297 

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

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

300 

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

302 

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

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

305 

306 :param identifier: the locale identifier string 

307 :param sep: optional component separator 

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

309 have its likely subtag resolved if the 

310 locale otherwise does not exist. For 

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

312 locale that exists but Babel can 

313 automatically expand it to the full 

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

315 expansion is only taking place if no 

316 locale exists otherwise. For instance 

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

318 by itself. 

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

320 identifier 

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

322 requested locale 

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

324 """ 

325 if isinstance(identifier, Locale): 

326 return identifier 

327 elif not isinstance(identifier, str): 

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

329 

330 parts = parse_locale(identifier, sep=sep) 

331 input_id = get_locale_identifier(parts) 

332 

333 def _try_load(parts): 

334 try: 

335 return cls(*parts) 

336 except UnknownLocaleError: 

337 return None 

338 

339 def _try_load_reducing(parts): 

340 # Success on first hit, return it. 

341 locale = _try_load(parts) 

342 if locale is not None: 

343 return locale 

344 

345 # Now try without script and variant 

346 locale = _try_load(parts[:2]) 

347 if locale is not None: 

348 return locale 

349 

350 locale = _try_load(parts) 

351 if locale is not None: 

352 return locale 

353 if not resolve_likely_subtags: 

354 raise UnknownLocaleError(input_id) 

355 

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

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

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

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

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

361 # maximize and minimize locale tags. 

362 

363 if len(parts) == 5: 

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

365 else: 

366 language, territory, script, variant = parts 

367 modifier = None 

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

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

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

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

372 

373 if territory == 'ZZ': 

374 territory = None 

375 if script == 'Zzzz': 

376 script = None 

377 

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

379 

380 # First match: try the whole identifier 

381 new_id = get_locale_identifier(parts) 

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

383 if likely_subtag is not None: 

384 locale = _try_load_reducing(parse_locale(likely_subtag)) 

385 if locale is not None: 

386 return locale 

387 

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

389 # simplified identifier that is just the language 

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

391 if likely_subtag is not None: 

392 parts2 = parse_locale(likely_subtag) 

393 if len(parts2) == 5: 

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

395 else: 

396 language2, _, script2, variant2 = parts2 

397 modifier2 = None 

398 locale = _try_load_reducing((language2, territory, script2, variant2, modifier2)) 

399 if locale is not None: 

400 return locale 

401 

402 raise UnknownLocaleError(input_id) 

403 

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

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

406 if not hasattr(other, key): 

407 return False 

408 return ( 

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

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

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

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

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

414 ) 

415 

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

417 return not self.__eq__(other) 

418 

419 def __hash__(self) -> int: 

420 return hash((self.language, self.territory, self.script, 

421 self.variant, self.modifier)) 

422 

423 def __repr__(self) -> str: 

424 parameters = [''] 

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

426 value = getattr(self, key) 

427 if value is not None: 

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

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

430 

431 def __str__(self) -> str: 

432 return get_locale_identifier((self.language, self.territory, 

433 self.script, self.variant, 

434 self.modifier)) 

435 

436 @property 

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

438 if self.__data is None: 

439 self.__data = localedata.LocaleDataDict(localedata.load(str(self))) 

440 return self.__data 

441 

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

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

444 

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

446 variant, if those are specified. 

447 

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

449 u'Chinese (Simplified, China)' 

450 

451 Modifiers are currently passed through verbatim: 

452 

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

454 u'Italian (Italy, euro)' 

455 

456 :param locale: the locale to use 

457 """ 

458 if locale is None: 

459 locale = self 

460 locale = Locale.parse(locale) 

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

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

463 details = [] 

464 if self.script: 

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

466 if self.territory: 

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

468 if self.variant: 

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

470 if self.modifier: 

471 details.append(self.modifier) 

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

473 if detail_string: 

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

475 return retval 

476 

477 display_name = property(get_display_name, doc="""\ 

478 The localized display name of the locale. 

479 

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

481 u'English' 

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

483 u'English (United States)' 

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

485 u'svenska' 

486 

487 :type: `unicode` 

488 """) 

489 

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

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

492 

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

494 u'Chinesisch' 

495 

496 .. versionadded:: 1.0 

497 

498 :param locale: the locale to use 

499 """ 

500 if locale is None: 

501 locale = self 

502 locale = Locale.parse(locale) 

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

504 

505 language_name = property(get_language_name, doc="""\ 

506 The localized language name of the locale. 

507 

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

509 u'English' 

510 """) 

511 

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

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

514 if locale is None: 

515 locale = self 

516 locale = Locale.parse(locale) 

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

518 

519 territory_name = property(get_territory_name, doc="""\ 

520 The localized territory name of the locale if available. 

521 

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

523 u'Deutschland' 

524 """) 

525 

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

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

528 if locale is None: 

529 locale = self 

530 locale = Locale.parse(locale) 

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

532 

533 script_name = property(get_script_name, doc="""\ 

534 The localized script name of the locale if available. 

535 

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

537 u'latinica' 

538 """) 

539 

540 @property 

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

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

543 

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

545 u'German' 

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

547 u'German (Germany)' 

548 

549 :type: `unicode`""" 

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

551 

552 # { General Locale Display Names 

553 

554 @property 

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

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

557 

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

559 u'Japanisch' 

560 

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

562 more information. 

563 """ 

564 return self._data['languages'] 

565 

566 @property 

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

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

569 

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

571 u'Hiragana' 

572 

573 See `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_ 

574 for more information. 

575 """ 

576 return self._data['scripts'] 

577 

578 @property 

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

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

581 

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

583 u'Alemania' 

584 

585 See `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_ 

586 for more information. 

587 """ 

588 return self._data['territories'] 

589 

590 @property 

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

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

593 

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

595 u'Alte deutsche Rechtschreibung' 

596 """ 

597 return self._data['variants'] 

598 

599 # { Number Formatting 

600 

601 @property 

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

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

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

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

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

607 

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

609 u'Colombian Peso' 

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

611 u'Kolumbianischer Peso' 

612 """ 

613 return self._data['currency_names'] 

614 

615 @property 

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

617 """Mapping of currency codes to symbols. 

618 

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

620 u'$' 

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

622 u'US$' 

623 """ 

624 return self._data['currency_symbols'] 

625 

626 @property 

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

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

629 

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

631 Babel versions. 

632 

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

634 u',' 

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

636 u'٫' 

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

638 u'.' 

639 """ 

640 return self._data['number_symbols'] 

641 

642 @property 

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

644 """ 

645 Mapping of other numbering systems available for the locale. 

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

647 

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

649 u'grek' 

650 

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

652 Babel versions. 

653 """ 

654 return self._data['numbering_systems'] 

655 

656 @property 

657 def default_numbering_system(self) -> str: 

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

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

660 u'latn' 

661 """ 

662 return self._data['default_numbering_system'] 

663 

664 @property 

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

666 """Locale patterns for decimal number formatting. 

667 

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

669 Babel versions. 

670 

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

672 <NumberPattern u'#,##0.###'> 

673 """ 

674 return self._data['decimal_formats'] 

675 

676 @property 

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

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

679 

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

681 Babel versions. 

682 

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

684 <NumberPattern u'0K'> 

685 """ 

686 return self._data['compact_decimal_formats'] 

687 

688 @property 

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

690 """Locale patterns for currency number formatting. 

691 

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

693 Babel versions. 

694 

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

696 <NumberPattern u'\\xa4#,##0.00'> 

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

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

699 """ 

700 return self._data['currency_formats'] 

701 

702 @property 

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

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

705 

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

707 Babel versions. 

708 

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

710 <NumberPattern u'¤0K'> 

711 """ 

712 return self._data['compact_currency_formats'] 

713 

714 @property 

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

716 """Locale patterns for percent number formatting. 

717 

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

719 Babel versions. 

720 

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

722 <NumberPattern u'#,##0%'> 

723 """ 

724 return self._data['percent_formats'] 

725 

726 @property 

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

728 """Locale patterns for scientific number formatting. 

729 

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

731 Babel versions. 

732 

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

734 <NumberPattern u'#E0'> 

735 """ 

736 return self._data['scientific_formats'] 

737 

738 # { Calendar Information and Date Formatting 

739 

740 @property 

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

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

743 

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

745 u'AM' 

746 """ 

747 try: 

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

749 except KeyError: 

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

751 

752 @property 

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

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

755 

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

757 """ 

758 return self._data['day_periods'] 

759 

760 @property 

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

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

763 """ 

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

765 

766 @property 

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

768 """Locale display names for weekdays. 

769 

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

771 u'Donnerstag' 

772 """ 

773 return self._data['days'] 

774 

775 @property 

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

777 """Locale display names for months. 

778 

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

780 u'Oktober' 

781 """ 

782 return self._data['months'] 

783 

784 @property 

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

786 """Locale display names for quarters. 

787 

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

789 u'1. Quartal' 

790 """ 

791 return self._data['quarters'] 

792 

793 @property 

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

795 """Locale display names for eras. 

796 

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

798 Babel versions. 

799 

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

801 u'Anno Domini' 

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

803 u'BC' 

804 """ 

805 return self._data['eras'] 

806 

807 @property 

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

809 """Locale display names for time zones. 

810 

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

812 Babel versions. 

813 

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

815 u'British Summer Time' 

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

817 u'St. John\u2019s' 

818 """ 

819 return self._data['time_zones'] 

820 

821 @property 

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

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

824 

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

826 have the same GMT offset and daylight savings time. 

827 

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

829 Babel versions. 

830 

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

832 u'Central European Summer Time' 

833 

834 .. versionadded:: 0.9 

835 """ 

836 return self._data['meta_zones'] 

837 

838 @property 

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

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

841 

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

843 Babel versions. 

844 

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

846 u'%(1)s (%(0)s)' 

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

848 u'Hor\\xe1rio %s' 

849 

850 .. versionadded:: 0.9 

851 """ 

852 return self._data['zone_formats'] 

853 

854 @property 

855 def first_week_day(self) -> int: 

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

857 

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

859 0 

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

861 6 

862 """ 

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

864 

865 @property 

866 def weekend_start(self) -> int: 

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

868 

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

870 5 

871 """ 

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

873 

874 @property 

875 def weekend_end(self) -> int: 

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

877 

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

879 6 

880 """ 

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

882 

883 @property 

884 def min_week_days(self) -> int: 

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

886 the first week of a year or month. 

887 

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

889 4 

890 """ 

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

892 

893 @property 

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

895 """Locale patterns for date formatting. 

896 

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

898 Babel versions. 

899 

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

901 <DateTimePattern u'M/d/yy'> 

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

903 <DateTimePattern u'd MMMM y'> 

904 """ 

905 return self._data['date_formats'] 

906 

907 @property 

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

909 """Locale patterns for time formatting. 

910 

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

912 Babel versions. 

913 

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

915 <DateTimePattern u'h:mm\u202fa'> 

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

917 <DateTimePattern u'HH:mm:ss z'> 

918 """ 

919 return self._data['time_formats'] 

920 

921 @property 

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

923 """Locale patterns for datetime formatting. 

924 

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

926 Babel versions. 

927 

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

929 u'{1}, {0}' 

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

931 u'{1} {0}' 

932 """ 

933 return self._data['datetime_formats'] 

934 

935 @property 

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

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

938 

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

940 <DateTimePattern u'E, M/d'> 

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

942 <DateTimePattern u'E dd/MM'> 

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

944 <DateTimePattern u"HH 'h'"> 

945 """ 

946 return self._data['datetime_skeletons'] 

947 

948 @property 

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

950 """Locale patterns for interval formatting. 

951 

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

953 Babel versions. 

954 

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

956 smallest changing component: 

957 

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

959 [u'E d.\u2009\u2013\u2009', u'E d.M.'] 

960 

961 .. seealso:: 

962 

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

964 

965 

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

967 """ 

968 return self._data['interval_formats'] 

969 

970 @property 

971 def plural_form(self) -> PluralRule: 

972 """Plural rules for the locale. 

973 

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

975 'one' 

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

977 'other' 

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

979 'one' 

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

981 'many' 

982 """ 

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

984 

985 @property 

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

987 """Patterns for generating lists 

988 

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

990 Babel versions. 

991 

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

993 u'{0}, {1}' 

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

995 u'{0}, and {1}' 

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

997 u'{0} and {1}' 

998 """ 

999 return self._data['list_patterns'] 

1000 

1001 @property 

1002 def ordinal_form(self) -> PluralRule: 

1003 """Plural rules for the locale. 

1004 

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

1006 'one' 

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

1008 'two' 

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

1010 'few' 

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

1012 'other' 

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

1014 'other' 

1015 """ 

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

1017 

1018 @property 

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

1020 """Localized names for various measurement systems. 

1021 

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

1023 u'am\\xe9ricain' 

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

1025 u'US' 

1026 

1027 """ 

1028 return self._data['measurement_systems'] 

1029 

1030 @property 

1031 def character_order(self) -> str: 

1032 """The text direction for the language. 

1033 

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

1035 'left-to-right' 

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

1037 'right-to-left' 

1038 """ 

1039 return self._data['character_order'] 

1040 

1041 @property 

1042 def text_direction(self) -> str: 

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

1044 

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

1046 'ltr' 

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

1048 'rtl' 

1049 """ 

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

1051 

1052 @property 

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

1054 """Display names for units of measurement. 

1055 

1056 .. seealso:: 

1057 

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

1059 

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

1061 Babel versions. 

1062 

1063 """ 

1064 return self._data['unit_display_names'] 

1065 

1066 

1067def default_locale(category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None: 

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

1069 environment variables. 

1070 

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

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

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

1074 >>> default_locale('LC_MESSAGES') 

1075 'fr_FR' 

1076 

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

1078 "en_US_POSIX" locale: 

1079 

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

1081 >>> default_locale('LC_MESSAGES') 

1082 'en_US_POSIX' 

1083 

1084 The following fallbacks to the variable are always considered: 

1085 

1086 - ``LANGUAGE`` 

1087 - ``LC_ALL`` 

1088 - ``LC_CTYPE`` 

1089 - ``LANG`` 

1090 

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

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

1093 """ 

1094 varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG') 

1095 for name in filter(None, varnames): 

1096 locale = os.getenv(name) 

1097 if locale: 

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

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

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

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

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

1103 locale = 'en_US_POSIX' 

1104 elif aliases and locale in aliases: 

1105 locale = aliases[locale] 

1106 try: 

1107 return get_locale_identifier(parse_locale(locale)) 

1108 except ValueError: 

1109 pass 

1110 return None 

1111 

1112 

1113def negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None: 

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

1115 

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

1117 'de_DE' 

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

1119 'de' 

1120 

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

1122 locale identifier: 

1123 

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

1125 'de_DE' 

1126 

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

1128 'de_DE' 

1129 

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

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

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

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

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

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

1136 identifiers to identifiers including the territory: 

1137 

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

1139 'ja_JP' 

1140 

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

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

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

1144 such cases, too: 

1145 

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

1147 'nb_NO' 

1148 

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

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

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

1152 

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

1154 :param available: the list of locale strings available 

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

1156 strings 

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

1158 """ 

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

1160 for locale in preferred: 

1161 ll = locale.lower() 

1162 if ll in available: 

1163 return locale 

1164 if aliases: 

1165 alias = aliases.get(ll) 

1166 if alias: 

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

1168 if alias.lower() in available: 

1169 return alias 

1170 parts = locale.split(sep) 

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

1172 return parts[0] 

1173 return None 

1174 

1175 

1176def parse_locale( 

1177 identifier: str, 

1178 sep: str = '_', 

1179) -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]: 

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

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

1182 

1183 >>> parse_locale('zh_CN') 

1184 ('zh', 'CN', None, None) 

1185 >>> parse_locale('zh_Hans_CN') 

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

1187 >>> parse_locale('ca_es_valencia') 

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

1189 >>> parse_locale('en_150') 

1190 ('en', '150', None, None) 

1191 >>> parse_locale('en_us_posix') 

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

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

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

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

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

1197 >>> parse_locale('it_IT@') 

1198 ('it', 'IT', None, None) 

1199 

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

1201 specified using the `sep` parameter. 

1202 

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

1204 

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

1206 ('zh', 'CN', None, None) 

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

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

1209 

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

1211 is raised: 

1212 

1213 >>> parse_locale('not_a_LOCALE_String') 

1214 Traceback (most recent call last): 

1215 ... 

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

1217 

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

1219 kept: 

1220 

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

1222 ('en', 'US', None, None) 

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

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

1225 

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

1227 

1228 :param identifier: the locale identifier string 

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

1230 identifier 

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

1232 identifier 

1233 """ 

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

1235 if '.' in identifier: 

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

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

1238 

1239 parts = identifier.split(sep) 

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

1241 if not lang.isalpha(): 

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

1243 

1244 script = territory = variant = None 

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

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

1247 

1248 if parts: 

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

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

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

1252 territory = parts.pop(0) 

1253 

1254 if parts and ( 

1255 len(parts[0]) == 4 and parts[0][0].isdigit() or 

1256 len(parts[0]) >= 5 and parts[0][0].isalpha() 

1257 ): 

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

1259 

1260 if parts: 

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

1262 

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

1264 if modifier: 

1265 return lang, territory, script, variant, modifier 

1266 else: 

1267 return lang, territory, script, variant 

1268 

1269 

1270def get_locale_identifier( 

1271 tup: tuple[str] 

1272 | tuple[str, str | None] 

1273 | tuple[str, str | None, str | None] 

1274 | tuple[str, str | None, str | None, str | None] 

1275 | tuple[str, str | None, str | None, str | None, str | None], 

1276 sep: str = "_", 

1277) -> str: 

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

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

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

1281 

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

1283 'de_DE_1999@custom' 

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

1285 'fi@custom' 

1286 

1287 

1288 .. versionadded:: 1.0 

1289 

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

1291 :param sep: the separator for the identifier. 

1292 """ 

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

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

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

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