8000 Add support of local numbering systems for number symbols · python-babel/babel@b0d8b60 · GitHub
[go: up one dir, main page]

Skip to content

Commit b0d8b60

Browse files
committed
Add support of local numbering systems for number symbols
- Load number symbols for multiple number systems from cldr data - Add numbering_systems and default_numbering_system properties for Locale - Use default numbering system of the locale for formatting number symbols Fixes partially issue #446
1 parent 65de3dc commit b0d8b60

File tree

5 files changed

+89
-16
lines changed

5 files changed

+89
-16
lines changed

babel/core.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class Locale:
149149
`Locale` objects provide access to a collection of locale data, such as
150150
territory and language names, number and date format patterns, and more:
151151
152-
>>> locale.number_symbols['decimal']
152+
>>> locale.number_symbols['latn']['decimal']
153153
u'.'
154154
155155
If a locale is requested for which no locale data is available, an
@@ -625,16 +625,39 @@ def currency_symbols(self) -> localedata.LocaleDataDict:
625625

626626
@property
627627
def number_symbols(self) -> localedata.LocaleDataDict:
628-
"""Symbols used in number formatting.
628+
"""Symbols used in number formatting by number system.
629629
630630
.. note:: The format of the value returned may change between
631631
Babel versions.
632632
633-
>>> Locale('fr', 'FR').number_symbols['decimal']
633+
>>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
634634
u','
635+
>>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
636+
u'٫'
637+
>>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
638+
u'.'
635639
"""
636640
return self._data['number_symbols']
637641

642+
@property
643+
def other_numbering_systems(self) -> localedata.LocaleDataDict:
644+
"""Mapping of othern numbering systems.
645+
>>> Locale('el', 'GR').other_numbering_systems['traditional']
646+
u'grek'
647+
648+
.. note:: The format of the value returned may change between
649+
Babel versions.
650+
"""
651+
return self._data['numbering_systems']
652+
653+
@property
654+
def default_numbering_system(self) -> str:
655+
"""The default numbering systems used by the locale.
656+
>>> Locale('el', 'GR').default_numbering_system
657+
u'latn'
658+
"""
659+
return self._data['default_numbering_system']
660+
638661
@property
639662
def decimal_formats(self) -> localedata.LocaleDataDict:
640663
"""Locale patterns for decimal number formatting.

babel/numbers.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,8 @@ def get_decimal_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
325325
326326
:param locale: the `Locale` object or locale identifier
327327
"""
328-
return Locale.parse(locale).number_symbols.get('decimal', '.')
328+
parsed_locale = Locale.parse(locale)
329+
return parsed_locale.number_symbols[parsed_locale.default_numbering_system].get('decimal', '.')
329330

330331

331332
def get_plus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
@@ -336,7 +337,8 @@ def get_plus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
336337
337338
:param locale: the `Locale` object or locale identifier
338339
"""
339-
return Locale.parse(locale).number_symbols.get('plusSign', '+& 67ED #39;)
340+
parsed_locale = Locale.parse(locale)
341+
return parsed_locale.number_symbols[parsed_locale.default_numbering_system].get('plusSign', '+')
340342

341343

342344
def get_minus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
@@ -347,7 +349,8 @@ def get_minus_sign_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
347349
348350
:param locale: the `Locale` object or locale identifier
349351
"""
350-
return Locale.parse(locale).number_symbols.get('minusSign', '-')
352+
parsed_locale = Locale.parse(locale)
353+
return parsed_locale.number_symbols[parsed_locale.default_numbering_system].get('minusSign', '-')
351354

352355

353356
def get_exponential_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
@@ -358,7 +361,8 @@ def get_exponential_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
358361
359362
:param locale: the `Locale` object or locale identifier
360363
"""
361-
return Locale.parse(locale).number_symbols.get('exponential', 'E')
364+
parsed_locale = Locale.parse(locale)
365+
return parsed_locale.number_symbols[parsed_locale.default_numbering_system].get('exponential', 'E')
362366

363367

364368
def get_group_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
@@ -369,7 +373,8 @@ def get_group_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
369373
370374
:param locale: the `Locale` object or locale identifier
371375
"""
372-
return Locale.parse(locale).number_symbols.get('group', ',')
376+
parsed_locale = Locale.parse(locale)
377+
return parsed_locale.number_symbols[parsed_locale.default_numbering_system].get('group', ',')
373378

374379

375380
def get_infinity_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
@@ -380,7 +385,8 @@ def get_infinity_symbol(locale: Locale | str | None = LC_NUMERIC) -> str:
380385
381386
:param locale: the `Locale` object or locale identifier
382387
"""
383-
return Locale.parse(locale).number_symbols.get('infinity', '∞')
388+
parsed_locale = Locale.parse(locale)
389+
return parsed_locale.number_symbols[parsed_locale.default_numbering_system].get('infinity', '∞')
384390

385391

386392
def format_number(number: float | decimal.Decimal | str, locale: Locale | str | None = LC_NUMERIC) -> str:

scripts/import_cldr.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False):
420420
parse_interval_formats(data, calendar)
421421

422422
parse_number_symbols(data, tree)
423+
parse_numbering_systems(data, tree)
423424
parse_decimal_formats(data, tree)
424425
parse_scientific_formats(data, tree)
425426
parse_percent_formats(data, tree)
@@ -739,14 +740,27 @@ def parse_calendar_datetime_skeletons(data, calendar):
739740

740741
def parse_number_symbols(data, tree):
741742
number_symbols = data.setdefault('number_symbols', {})
742-
for symbol_elem in tree.findall('.//numbers/symbols'):
743-
if _should_skip_number_elem(data, symbol_elem): # TODO: Support other number systems
743+
for symbol_system_elem in tree.findall('.//numbers/symbols'):
744+
number_system = symbol_system_elem.get('numberSystem')
745+
if not number_system:
744746
continue
745747

746-
for elem in symbol_elem.findall('./*'):
747-
if _should_skip_elem(elem):
748+
for symbol_element in symbol_system_elem.findall('./*'):
749+
if _should_skip_elem(symbol_element):
748750
continue
749-
number_symbols[elem.tag] = str(elem.text)
751+
752+
number_symbols.setdefault(number_system, {})[symbol_element.tag] = str(symbol_element.text)
753+
754+
755+
def parse_numbering_systems(data, tree):
756+
default_number_system_node = tree.find('.//numbers/defaultNumberingSystem')
757+
if default_number_system_node is not None:
758+
data['default_numbering_system'] = default_number_system_node.text
759+
760+
numbering_systems = data.setdefault('numbering_systems', {})
761+
other_numbering_systems_node = tree.find('.//numbers/otherNumberingSystems') or []
762+
for system in other_numbering_systems_node:
763+
numbering_systems[system.tag] = system.text
750764

751765

752766
def parse_decimal_formats(data, tree):

tests/test_core.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
def test_locale_provides_access_to_cldr_locale_data():
2020
locale = Locale('en', 'US')
2121
assert locale.display_name == 'English (United States)'
22-
assert locale.number_symbols['decimal'] == '.'
22+
assert locale.number_symbols["latn"]['decimal'] == '.'
2323

2424

2525
def test_locale_repr():
@@ -162,7 +162,25 @@ def test_currency_symbols_property(self):
162162
assert Locale('es', 'CO').currency_symbols['USD'] == 'US$'
163163

164164
def test_number_symbols_property(self):
165-
assert Locale('fr', 'FR').number_symbols['decimal'] == ','
165+
assert Locale('fr', 'FR').number_symbols["latn"]['decimal'] == ','
166+
assert Locale('ar', 'IL').number_symbols["arab"]['percentSign'] == \u061c'
167+
assert Locale('ar', 'IL').number_symbols["latn"]['percentSign'] == '\u200e%\u200e'
168+
169+
def test_other_numbering_systems_property(self):
170+
assert Locale('fr', 'FR').other_numbering_systems['native'] == 'latn'
171+
assert 'traditional' not in Locale('fr', 'FR').other_numbering_systems
172+
173+
assert Locale('el', 'GR').other_numbering_systems['native'] == 'latn'
174+
assert Locale('el', 'GR').other_numbering_systems['traditional'] == 'grek'
175+
176+
def test_default_numbering_systems_property(self):
177+
assert Locale('en', 'GB').default_numbering_system == 'latn'
178+
assert Locale('ar', 'EG').default_numbering_system == 'arab'
179+
180+
@pytest.mark.all_locales
181+
def test_all_locales_have_default_numbering_system(self, locale):
182+
locale = Locale.parse(locale)
183+
assert locale.default_numbering_system
166184

167185
def test_decimal_formats(self):
168186
assert Locale('en', 'US').decimal_formats[None].pattern == '#,##0.###'

tests/test_numbers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,23 +317,35 @@ def test_get_territory_currencies():
317317

318318
def test_get_decimal_symbol():
319319
assert numbers.get_decimal_symbol('en_US') == '.'
320+
assert numbers.get_decimal_symbol('sv_SE') == ','
321+
assert numbers.get_decimal_symbol('ar_EG') == '٫'
320322

321323

322324
def test_get_plus_sign_symbol():
323325
assert numbers.get_plus_sign_symbol('en_US') == '+'
326+
assert numbers.get_plus_sign_symbol('ar_EG') == '\u061c+'
324327

325328

326329
def test_get_minus_sign_symbol():
327330
assert numbers.get_minus_sign_symbol('en_US') == '-'
328331
assert numbers.get_minus_sign_symbol('nl_NL') == '-'
332+
assert numbers.get_minus_sign_symbol('ar_EG') == '\u061c-'
329333

330334

331335
def test_get_exponential_symbol():
332336
assert numbers.get_exponential_symbol('en_US') == 'E'
337+
assert numbers.get_exponential_symbol('ja_JP') == 'E'
338+
assert numbers.get_exponential_symbol('ar_EG') == 'اس'
333339

334340

335341
def test_get_group_symbol():
336342
assert numbers.get_group_symbol('en_US') == ','
343+
assert numbers.get_group_symbol('sv') == "\xa0"
344+
assert numbers.get_group_symbol('ar_EG') == '٬'
345+
346+
347+
def test_get_infinity_symbol():
348+
assert numbers.get_infinity_symbol('en_US') == '∞'
337349

338350

339351
def test_decimal_precision():

0 commit comments

Comments
 (0)
179B
0