diff --git a/babel/core.py b/babel/core.py index eb07a8d24..3ba8b85ea 100644 --- a/babel/core.py +++ b/babel/core.py @@ -1372,7 +1372,7 @@ def get_cldr_version() -> str: function is a string representing a version number, e.g. '47'. >>> get_cldr_version() - '47' + '48' .. versionadded:: 2.18 diff --git a/babel/numbers.py b/babel/numbers.py index 767bac95a..b782521e5 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -741,7 +741,7 @@ def format_currency( >>> format_currency(1099.98, 'JPY', locale='en_US') '\\xa51,100' >>> format_currency(1099.98, 'COP', '#,##0.00', locale='es_ES') - '1.099,98' + '1.100' However, the number of decimal digits can be overridden from the currency information, by setting the last parameter to ``False``: diff --git a/babel/units.py b/babel/units.py index 2c3462422..fa41260af 100644 --- a/babel/units.py +++ b/babel/units.py @@ -1,11 +1,22 @@ from __future__ import annotations import decimal +import warnings from typing import Literal from babel.core import Locale from babel.numbers import LC_NUMERIC, format_decimal +_DEPRECATED_UNIT_IDS: dict[str, str] = { + # Unit IDs deprecated in CLDR 48 + "concentr-permillion": "concentr-part-per-1e6", + "concentr-portion": "concentr-part", + "concentr-portion-per-1e9": "concentr-part-per-1e9", + "permillion": "part-per-1e6", + "portion": "part", + "portion-per-1e9": "part-per-1e9", +} + class UnknownUnitError(ValueError): def __init__(self, unit: str, locale: Locale) -> None: @@ -66,6 +77,19 @@ def _find_unit_pattern(unit_id: str, locale: Locale | str | None = None) -> str unit_patterns: dict[str, str] = locale._data["unit_patterns"] if unit_id in unit_patterns: return unit_id + + # Only if a direct hit didn't work, try deprecated units... + new_id = _DEPRECATED_UNIT_IDS.get(unit_id) + if new_id is not None: + warnings.warn( + f"Unit identifier {unit_id!r} is deprecated; use {new_id!r} instead.", + DeprecationWarning, + stacklevel=4, + ) + if new_id in unit_patterns: + return new_id + + # ... and finally fuzzy matching. for unit_pattern in sorted(unit_patterns, key=len): if unit_pattern.endswith(unit_id): return unit_pattern diff --git a/scripts/download_import_cldr.py b/scripts/download_import_cldr.py index c197fd307..082b2efec 100755 --- a/scripts/download_import_cldr.py +++ b/scripts/download_import_cldr.py @@ -9,10 +9,10 @@ import zipfile from urllib.request import Request, urlopen -URL = 'https://unicode.org/Public/cldr/47/cldr-common-47.zip' -FILENAME = 'cldr-common-47.0.zip' -# Via https://unicode.org/Public/cldr/45/hashes/SHASUM512.txt -FILESUM = '3b1eb2a046dae23cf16f611f452833e2a95affb1aa2ae3fa599753d229d152577114c2ff44ca98a7f369fa41dc6f45b0d7a6647653ca79694aacfd3f3be59801' +URL = 'https://unicode.org/Public/cldr/48.2/cldr-common-48.2.zip' +FILENAME = 'cldr-common-48.2.zip' +# Via https://unicode.org/Public/cldr/48.2/hashes/SHASUM512.txt (core.zip; see https://unicode-org.atlassian.net/browse/CLDR-19388) +FILESUM = 'de8660f5371e0fcfd03a42e3b4fc4c686ec6cd602b402f1e3d227844005a54eb7952873894443523837d5828c42874a1a267a19f91ded207a2d166144791fa62' def reporthook(bytes_transmitted, total_size): diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 400150b19..2bbdad173 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -17,6 +17,7 @@ import pickle import re import sys +import warnings from optparse import OptionParser from xml.etree import ElementTree @@ -61,23 +62,6 @@ def _text(elem): log = logging.getLogger("import_cldr") -def need_conversion(dst_filename, data_dict, source_filename): - with open(source_filename, 'rb') as f: - blob = f.read(4096) - version_match = re.search(b'version number="\\$Revision: (\\d+)', blob) - if not version_match: # CLDR 36.0 was shipped without proper revision numbers - return True - version = int(version_match.group(1)) - - data_dict['_version'] = version - if not os.path.isfile(dst_filename): - return True - - with open(dst_filename, 'rb') as f: - data = pickle.load(f) - return data.get('_version') != version - - def _translate_alias(ctxt, path): parts = path.split('/') keys = ctxt[:] @@ -164,7 +148,7 @@ def main(): parser = OptionParser(usage='%prog path/to/cldr') parser.add_option( '-f', '--force', dest='force', action='store_true', default=False, - help='force import even if destination file seems up to date', + help='legacy option; conversion is always done', ) parser.add_option( '-j', '--json', dest='dump_json', action='store_true', default=False, @@ -186,22 +170,21 @@ def main(): return process_data( srcdir=args[0], destdir=BABEL_PACKAGE_ROOT, - force=bool(options.force), dump_json=bool(options.dump_json), ) -def process_data(srcdir, destdir, force=False, dump_json=False): +def process_data(srcdir, destdir, force=True, dump_json=False): + if not force: + warnings.warn("Setting `force=False` has no effect.", DeprecationWarning, stacklevel=2) sup_filename = os.path.join(srcdir, 'supplemental', 'supplementalData.xml') sup = parse(sup_filename) # Import global data from the supplemental files global_path = os.path.join(destdir, 'global.dat') - global_data = {} - if force or need_conversion(global_path, global_data, sup_filename): - global_data.update(parse_global(srcdir, sup)) - write_datafile(global_path, global_data, dump_json=dump_json) - _process_local_datas(sup, srcdir, destdir, force=force, dump_json=dump_json) + global_data = parse_global(srcdir, sup) + write_datafile(global_path, global_data, dump_json=dump_json) + _process_local_datas(sup, srcdir, destdir, dump_json=dump_json) def parse_global(srcdir, sup): @@ -366,7 +349,9 @@ def parse_global(srcdir, sup): return global_data -def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): +def _process_local_datas(sup, srcdir, destdir, force=True, dump_json=False): + if not force: + warnings.warn("Setting `force=False` has no effect.", DeprecationWarning, stacklevel=2) day_period_rules = parse_day_period_rules(parse(os.path.join(srcdir, 'supplemental', 'dayPeriods.xml'))) # build a territory containment mapping for inheritance regions = {} @@ -401,8 +386,6 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): data_filename = os.path.join(destdir, "locale-data", f"{stem}.dat") data = {} - if not (force or need_conversion(data_filename, data, full_filename)): - continue tree = parse(full_filename) diff --git a/tests/test_core.py b/tests/test_core.py index 461d70782..7cb9cd90b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -413,4 +413,4 @@ def test_locale_parse_empty(): def test_get_cldr_version(): - assert core.get_cldr_version() == "47" + assert core.get_cldr_version() == "48" diff --git a/tests/test_dates.py b/tests/test_dates.py index 12bb23433..f80bb3288 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -613,10 +613,10 @@ def test_format_time(timezone_getter): def test_format_skeleton(timezone_getter): dt = datetime(2007, 4, 1, 15, 30) assert (dates.format_skeleton('yMEd', dt, locale='en_US') == 'Sun, 4/1/2007') - assert (dates.format_skeleton('yMEd', dt, locale='th') == 'อา. 1/4/2007') + assert (dates.format_skeleton('yMEd', dt, locale='th') == 'อาทิตย์ 1/4/2007') assert (dates.format_skeleton('EHm', dt, locale='en') == 'Sun 15:30') - assert (dates.format_skeleton('EHm', dt, tzinfo=timezone_getter('Asia/Bangkok'), locale='th') == 'อา. 22:30 น.') + assert (dates.format_skeleton('EHm', dt, tzinfo=timezone_getter('Asia/Bangkok'), locale='th') == 'อาทิตย์ 22:30 น.') @pytest.mark.parametrize(('skeleton', 'expected'), [ @@ -1194,8 +1194,8 @@ def test_issue_1192(): # The actual returned value here is not actually strictly specified ("get_timezone_name" # is not an operation specified as such). Issue #1192 concerned this invocation returning # the invalid "no inheritance marker" value; _that_ should never be returned here. - # IOW, if the below "Hawaii-Aleutian Time" changes with e.g. CLDR updates, that's fine. - assert dates.get_timezone_name('Pacific/Honolulu', 'short', locale='en_GB') == "Hawaii-Aleutian Time" + # IOW, if the below "United States (Honolulu) Time" changes with e.g. CLDR updates, that's fine. + assert dates.get_timezone_name('Pacific/Honolulu', 'short', locale='en_GB') == "United States (Honolulu) Time" @pytest.mark.xfail diff --git a/tests/test_lists.py b/tests/test_lists.py index ef522f223..da96a124c 100644 --- a/tests/test_lists.py +++ b/tests/test_lists.py @@ -9,7 +9,7 @@ (['string1', 'string2'], 'en', 'string1 and string2'), (['string1', 'string2', 'string3'], 'en', 'string1, string2, and string3'), (['string1', 'string2', 'string3'], 'zh', 'string1、string2和string3'), - (['string1', 'string2', 'string3', 'string4'], 'ne', 'string1,string2, string3 र string4'), + (['string1', 'string2', 'string3', 'string4'], 'ne', 'string1, string2, string3 र string4'), ]) def test_format_list(list, locale, expected): assert lists.format_list(list, locale=locale) == expected diff --git a/tests/test_numbers.py b/tests/test_numbers.py index 4f24f5b88..cf888b7e6 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -40,7 +40,7 @@ def test_list_currencies(): assert list_currencies(locale='pa_Arab') == {'PKR', 'INR', 'EUR'} - assert len(list_currencies()) == 307 + assert len(list_currencies()) == 308 def test_validate_currency(): @@ -297,7 +297,7 @@ def test_format_currency_format_type(): assert (numbers.format_currency(1099.98, 'JPY', locale='en_US') == '\xa51,100') assert (numbers.format_currency(1099.98, 'COP', '#,##0.00', locale='es_ES') - == '1.099,98') + == '1.100') assert (numbers.format_currency(1099.98, 'JPY', locale='en_US', currency_digits=False) == '\xa51,099.98') @@ -320,7 +320,7 @@ def test_format_compact_currency(): assert numbers.format_compact_currency(123, 'EUR', locale='yav', format_type="short") == '€\xa0123' assert numbers.format_compact_currency(12345, 'EUR', locale='yav', format_type="short") == '€\xa012K' assert numbers.format_compact_currency(123456789, 'EUR', locale='de_DE', fraction_digits=1) == '123,5\xa0Mio.\xa0€' - assert numbers.format_compact_currency(123456789, 'USD', locale='ar_EG', fraction_digits=2, format_type="short", numbering_system="default") == '123٫46\xa0مليون\xa0US$' + assert numbers.format_compact_currency(123456789, 'USD', locale='ar_EG', fraction_digits=2, format_type="short", numbering_system="default") == '\u200f123٫46\xa0مليون\xa0US$' def test_format_compact_currency_invalid_format_type(): diff --git a/tests/test_support_format.py b/tests/test_support_format.py index fdfac844b..cd9765b1e 100644 --- a/tests/test_support_format.py +++ b/tests/test_support_format.py @@ -53,7 +53,7 @@ def test_format_currency(ar_eg_format, en_us_format): def test_format_compact_currency(ar_eg_format, en_us_format): assert en_us_format.compact_currency(1099.98, 'USD') == '$1K' assert en_us_format.compact_currency(Decimal("1099.98"), 'USD') == '$1K' - assert ar_eg_format.compact_currency(1099.98, 'EGP') == '1\xa0ألف\xa0ج.م.\u200f' + assert ar_eg_format.compact_currency(1099.98, 'EGP') == '\u200f1\xa0ألف\xa0ج.م.\u200f' def test_format_percent(ar_eg_format, en_us_format): diff --git a/tests/test_units.py b/tests/test_units.py index 27b7535fe..cab9c0a1b 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -8,9 +8,9 @@ ('speed-light-speed', 1, '1 světlo'), ('speed-light-speed', 2, '2 světla'), ('speed-light-speed', 5, '5 světel'), - ('concentr-portion-per-1e9', 1, '1 částice na miliardu'), - ('concentr-portion-per-1e9', 2, '2 částice na miliardu'), - ('concentr-portion-per-1e9', 5, '5 částic na miliardu'), + ('concentr-part-per-1e9', 1, '1 částice na miliardu'), + ('concentr-part-per-1e9', 2, '2 částice na miliardu'), + ('concentr-part-per-1e9', 5, '5 částic na miliardu'), ('duration-night', 1, '1 noc'), ('duration-night', 2, '2 noci'), ('duration-night', 5, '5 nocí'), @@ -29,3 +29,9 @@ def test_new_cldr46_units(unit, count, expected): ]) def test_issue_1217(count, unit, locale, length, expected): assert format_unit(count, unit, length, locale=locale) == expected + + +def test_deprecated_unit_ids(): + for id in ("concentr-permillion", "concentr-portion", "concentr-portion-per-1e9"): + with pytest.warns(DeprecationWarning, match=id): + format_unit(1, id, locale='en')