From 2a1709a7768f6f07c3d2dbfdb03d3c8a6bd80aef Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 13 Dec 2023 13:28:43 +0200 Subject: [PATCH 1/8] Drop support for Python 3.7 (EOL since June 2023) (#1048) --- .github/workflows/ci.yml | 5 ++--- babel/messages/extract.py | 4 ++-- docs/dev.rst | 4 ++-- pyproject.toml | 2 +- setup.py | 3 +-- tox.ini | 5 ++--- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15f1712d1..c2096ea5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,11 @@ jobs: - "windows-2022" - "macos-11" python-version: - - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" - - "pypy-3.7" + - "pypy3.8" - "3.12" env: BABEL_CLDR_NO_DOWNLOAD_PROGRESS: "1" @@ -71,7 +70,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.12" cache: "pip" cache-dependency-path: "**/setup.py" - run: pip install build -e . diff --git a/babel/messages/extract.py b/babel/messages/extract.py index b13f1a9a6..8e3779871 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -38,10 +38,10 @@ from babel.util import parse_encoding, parse_future_flags, pathmatch if TYPE_CHECKING: - from typing import IO, Protocol + from typing import IO, Final, Protocol from _typeshed import SupportsItems, SupportsRead, SupportsReadline - from typing_extensions import Final, TypeAlias, TypedDict + from typing_extensions import TypeAlias, TypedDict class _PyOptions(TypedDict, total=False): encoding: str diff --git a/docs/dev.rst b/docs/dev.rst index 97a105b98..95f20d58c 100644 --- a/docs/dev.rst +++ b/docs/dev.rst @@ -30,8 +30,8 @@ Python Versions At the moment the following Python versions should be supported: -* Python 3.7 and up -* PyPy 3.7 and up +* Python 3.8 and up +* PyPy 3.8 and up Unicode ------- diff --git a/pyproject.toml b/pyproject.toml index c90b6e45e..5621c2eb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.ruff] -target-version = "py37" +target-version = "py38" select = [ "B", "C", diff --git a/setup.py b/setup.py index 4da20aabf..b43e5e52b 100755 --- a/setup.py +++ b/setup.py @@ -47,7 +47,6 @@ def run(self): 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', @@ -57,7 +56,7 @@ def run(self): 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', ], - python_requires='>=3.7', + python_requires='>=3.8', packages=['babel', 'babel.messages', 'babel.localtime'], package_data={"babel": ["py.typed"]}, include_package_data=True, diff --git a/tox.ini b/tox.ini index f91ba5ba7..cdb2514e2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] isolated_build = true envlist = - py{37,38,39,310,311,312} + py{38,39,310,311,312} pypy3 - py{37,38}-pytz + py{38}-pytz py{311,312}-setuptools [testenv] @@ -30,7 +30,6 @@ passenv = [gh-actions] python = pypy3: pypy3 - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 From 40e60a1f6cf178d9f57fcc14f157ea1b2ab77361 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 26 Dec 2023 20:50:18 +0100 Subject: [PATCH 2/8] Upgrade GitHub Actions (#1054) --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2096ea5b..25071a68f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,8 @@ jobs: - "3.9" - "3.10" - "3.11" - - "pypy3.8" - "3.12" + - "pypy3.10" env: BABEL_CLDR_NO_DOWNLOAD_PROGRESS: "1" BABEL_CLDR_QUIET: "1" @@ -45,10 +45,10 @@ jobs: path: cldr key: cldr-${{ hashFiles('scripts/*cldr*') }} - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - allow-prereleases: true # For Python 3.12 + allow-prereleases: true cache: "pip" cache-dependency-path: "**/setup.py" - name: Install dependencies @@ -68,7 +68,7 @@ jobs: needs: lint steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.12" cache: "pip" From e0d10183635b9ae1d37c31811e23c8974a1bc31e Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 27 Mar 2024 17:46:53 +0200 Subject: [PATCH 3/8] Improve .po IO (#1068) * read_po: note interface also supports iterable-of-strings, not a filelike * write_po: refactor into generate_po --- babel/messages/pofile.py | 121 ++++++++++++++++++++-------------- tests/messages/test_pofile.py | 9 +++ 2 files changed, 81 insertions(+), 49 deletions(-) diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index b64a5085e..6e9dbdf03 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -291,7 +291,7 @@ def _process_comment(self, line) -> None: # These are called user comments self.user_comments.append(line[1:].strip()) - def parse(self, fileobj: IO[AnyStr]) -> None: + def parse(self, fileobj: IO[AnyStr] | Iterable[AnyStr]) -> None: """ Reads from the file-like object `fileobj` and adds any po file units found in it to the `Catalog` supplied to the constructor. @@ -329,7 +329,7 @@ def _invalid_pofile(self, line, lineno, msg) -> None: def read_po( - fileobj: IO[AnyStr], + fileobj: IO[AnyStr] | Iterable[AnyStr], locale: str | Locale | None = None, domain: str | None = None, ignore_obsolete: bool = False, @@ -337,7 +337,7 @@ def read_po( abort_invalid: bool = False, ) -> Catalog: """Read messages from a ``gettext`` PO (portable object) file from the given - file-like object and return a `Catalog`. + file-like object (or an iterable of lines) and return a `Catalog`. >>> from datetime import datetime >>> from io import StringIO @@ -373,7 +373,7 @@ def read_po( .. versionadded:: 1.0 Added support for explicit charset argument. - :param fileobj: the file-like object to read the PO file from + :param fileobj: the file-like object (or iterable of lines) to read the PO file from :param locale: the locale identifier or `Locale` object, or `None` if the catalog is not bound to a locale (which basically means it's a template) @@ -529,45 +529,69 @@ def write_po( updating the catalog :param include_lineno: include line number in the location comment """ - def _normalize(key, prefix=''): - return normalize(key, prefix=prefix, width=width) - - def _write(text): - if isinstance(text, str): - text = text.encode(catalog.charset, 'backslashreplace') - fileobj.write(text) - - def _write_comment(comment, prefix=''): - # xgettext always wraps comments even if --no-wrap is passed; - # provide the same behaviour - _width = width if width and width > 0 else 76 - for line in wraptext(comment, _width): - _write(f"#{prefix} {line.strip()}\n") - - def _write_message(message, prefix=''): + + sort_by = None + if sort_output: + sort_by = "message" + elif sort_by_file: + sort_by = "location" + + for line in generate_po( + catalog, + ignore_obsolete=ignore_obsolete, + include_lineno=include_lineno, + include_previous=include_previous, + no_location=no_location, + omit_header=omit_header, + sort_by=sort_by, + width=width, + ): + if isinstance(line, str): + line = line.encode(catalog.charset, 'backslashreplace') + fileobj.write(line) + + +def generate_po( + catalog: Catalog, + *, + ignore_obsolete: bool = False, + include_lineno: bool = True, + include_previous: bool = False, + no_location: bool = False, + omit_header: bool = False, + sort_by: Literal["message", "location"] | None = None, + width: int = 76, +) -> Iterable[str]: + r"""Yield text strings representing a ``gettext`` PO (portable object) file. + + See `write_po()` for a more detailed description. + """ + # xgettext always wraps comments even if --no-wrap is passed; + # provide the same behaviour + comment_width = width if width and width > 0 else 76 + + def _format_comment(comment, prefix=''): + for line in wraptext(comment, comment_width): + yield f"#{prefix} {line.strip()}\n" + + def _format_message(message, prefix=''): if isinstance(message.id, (list, tuple)): if message.context: - _write(f"{prefix}msgctxt {_normalize(message.context, prefix)}\n") - _write(f"{prefix}msgid {_normalize(message.id[0], prefix)}\n") - _write(f"{prefix}msgid_plural {_normalize(message.id[1], prefix)}\n") + yield f"{prefix}msgctxt {normalize(message.context, prefix=prefix, width=width)}\n" + yield f"{prefix}msgid {normalize(message.id[0], prefix=prefix, width=width)}\n" + yield f"{prefix}msgid_plural {normalize(message.id[1], prefix=prefix, width=width)}\n" for idx in range(catalog.num_plurals): try: string = message.string[idx] except IndexError: string = '' - _write(f"{prefix}msgstr[{idx:d}] {_normalize(string, prefix)}\n") + yield f"{prefix}msgstr[{idx:d}] {normalize(string, prefix=prefix, width=width)}\n" else: if message.context: - _write(f"{prefix}msgctxt {_normalize(message.context, prefix)}\n") - _write(f"{prefix}msgid {_normalize(message.id, prefix)}\n") - _write(f"{prefix}msgstr {_normalize(message.string or '', prefix)}\n") - - sort_by = None - if sort_output: - sort_by = "message" - elif sort_by_file: - sort_by = "location" + yield f"{prefix}msgctxt {normalize(message.context, prefix=prefix, width=width)}\n" + yield f"{prefix}msgid {normalize(message.id, prefix=prefix, width=width)}\n" + yield f"{prefix}msgstr {normalize(message.string or '', prefix=prefix, width=width)}\n" for message in _sort_messages(catalog, sort_by=sort_by): if not message.id: # This is the header "message" @@ -580,12 +604,12 @@ def _write_message(message, prefix=''): lines += wraptext(line, width=width, subsequent_indent='# ') comment_header = '\n'.join(lines) - _write(f"{comment_header}\n") + yield f"{comment_header}\n" for comment in message.user_comments: - _write_comment(comment) + yield from _format_comment(comment) for comment in message.auto_comments: - _write_comment(comment, prefix='.') + yield from _format_comment(comment, prefix='.') if not no_location: locs = [] @@ -606,22 +630,21 @@ def _write_message(message, prefix=''): location = f"{location}:{lineno:d}" if location not in locs: locs.append(location) - _write_comment(' '.join(locs), prefix=':') + yield from _format_comment(' '.join(locs), prefix=':') if message.flags: - _write(f"#{', '.join(['', *sorted(message.flags)])}\n") + yield f"#{', '.join(['', *sorted(message.flags)])}\n" if message.previous_id and include_previous: - _write_comment( - f'msgid {_normalize(message.previous_id[0])}', + yield from _format_comment( + f'msgid {normalize(message.previous_id[0], width=width)}', prefix='|', ) if len(message.previous_id) > 1: - _write_comment('msgid_plural %s' % _normalize( - message.previous_id[1], - ), prefix='|') + norm_previous_id = normalize(message.previous_id[1], width=width) + yield from _format_comment(f'msgid_plural {norm_previous_id}', prefix='|') - _write_message(message) - _write('\n') + yield from _format_message(message) + yield '\n' if not ignore_obsolete: for message in _sort_messages( @@ -629,12 +652,12 @@ def _write_message(message, prefix=''): sort_by=sort_by, ): for comment in message.user_comments: - _write_comment(comment) - _write_message(message, prefix='#~ ') - _write('\n') + yield from _format_comment(comment) + yield from _format_message(message, prefix='#~ ') + yield '\n' -def _sort_messages(messages: Iterable[Message], sort_by: Literal["message", "location"]) -> list[Message]: +def _sort_messages(messages: Iterable[Message], sort_by: Literal["message", "location"] | None) -> list[Message]: """ Sort the given message iterable by the given criteria. diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index 043f9c8bd..d322857d5 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -884,3 +884,12 @@ def test_unknown_language_write(): buf = BytesIO() pofile.write_po(buf, catalog) assert 'sr_SP' in buf.getvalue().decode() + + +def test_iterable_of_strings(): + """ + Test we can parse from an iterable of strings. + """ + catalog = pofile.read_po(['msgid "foo"', b'msgstr "Voh"'], locale="en_US") + assert catalog.locale == Locale("en", "US") + assert catalog.get("foo").string == "Voh" From fe82fbc90d8044d17bfc4ae1c7a0cb24e85153ef Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 16 Apr 2024 09:23:55 +0300 Subject: [PATCH 4/8] Use CLDR 44 and adjust tests to match new data (#1071) --- babel/dates.py | 2 -- babel/numbers.py | 4 ++-- babel/units.py | 4 ++-- scripts/download_import_cldr.py | 8 ++++---- tests/test_numbers.py | 12 +++++++----- tests/test_support.py | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/babel/dates.py b/babel/dates.py index 40d950983..040820a6d 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1511,8 +1511,6 @@ def format_period(self, char: str, num: int) -> str: >>> format = DateTimeFormat(datetime(2022, 4, 28, 6, 27), 'zh_Hant') >>> format.format_period('a', 1) u'上午' - >>> format.format_period('b', 1) - u'清晨' >>> format.format_period('B', 1) u'清晨' diff --git a/babel/numbers.py b/babel/numbers.py index 2240c65d5..2d46e0271 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -418,7 +418,7 @@ def get_exponential_symbol( >>> get_exponential_symbol('en_US') u'E' >>> get_exponential_symbol('ar_EG', numbering_system='default') - u'اس' + u'أس' >>> get_exponential_symbol('ar_EG', numbering_system='latn') u'E' @@ -956,7 +956,7 @@ def format_scientific( >>> format_scientific(10000, locale='en_US') u'1E4' >>> format_scientific(10000, locale='ar_EG', numbering_system='default') - u'1اس4' + u'1أس4' The format pattern can also be specified explicitly: diff --git a/babel/units.py b/babel/units.py index 36206d0c8..8dae6947e 100644 --- a/babel/units.py +++ b/babel/units.py @@ -248,8 +248,8 @@ def format_compound_unit( >>> format_compound_unit(4, "meter", "ratakisko", length="short", locale="fi") '4 m/ratakisko' - >>> format_compound_unit(35, "minute", denominator_unit="fathom", locale="sv") - '35 minuter per famn' + >>> format_compound_unit(35, "minute", denominator_unit="nautical-mile", locale="sv") + '35 minuter per nautisk mil' >>> from babel.numbers import format_currency >>> format_compound_unit(format_currency(35, "JPY", locale="de"), denominator_unit="liter", locale="de") diff --git a/scripts/download_import_cldr.py b/scripts/download_import_cldr.py index bcee0ea7c..10a2deaea 100755 --- a/scripts/download_import_cldr.py +++ b/scripts/download_import_cldr.py @@ -9,10 +9,10 @@ import zipfile from urllib.request import urlretrieve -URL = 'http://unicode.org/Public/cldr/43/cldr-common-43.0.zip' -FILENAME = 'cldr-common-43.0.zip' -# Via https://unicode.org/Public/cldr/43/hashes/SHASUM512 -FILESUM = '930c64208d6f680d115bfa74a69445fb614910bb54233227b0b9ae85ddbce4db19e4ec863bf04ae9d4a11b2306aa7394e553384d7537487de8011f0e34877cef' +URL = 'https://unicode.org/Public/cldr/44/cldr-common-44.0.zip' +FILENAME = 'cldr-common-44.0.zip' +# Via https://unicode.org/Public/cldr/44/hashes/SHASUM512 +FILESUM = 'f2cd8733948caf308d6e39eae21724da7f29f528f8969d456514e1e84ecd5f1e6936d0460414a968888bb1b597bc1ee723950ea47df5cba21a02bb14f96d18b6' BLKSIZE = 131072 diff --git a/tests/test_numbers.py b/tests/test_numbers.py index d89592a0e..e58f0735f 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -164,7 +164,7 @@ def test_compact(self): assert numbers.format_compact_decimal(1000, locale='ja_JP', format_type="short") == '1000' assert numbers.format_compact_decimal(9123, locale='ja_JP', format_type="short") == '9123' assert numbers.format_compact_decimal(10000, locale='ja_JP', format_type="short") == '1万' - assert numbers.format_compact_decimal(1234567, locale='ja_JP', format_type="long") == '123万' + assert numbers.format_compact_decimal(1234567, locale='ja_JP', format_type="short") == '123万' assert numbers.format_compact_decimal(-1, locale='en_US', format_type="short") == '-1' assert numbers.format_compact_decimal(-1234, locale='en_US', format_type="short", fraction_digits=2) == '-1.23K' assert numbers.format_compact_decimal(-123456789, format_type='short', locale='en_US') == '-123M' @@ -373,8 +373,8 @@ def test_get_exponential_symbol(): assert numbers.get_exponential_symbol('en_US', numbering_system="default") == 'E' assert numbers.get_exponential_symbol('ja_JP') == 'E' assert numbers.get_exponential_symbol('ar_EG') == 'E' - assert numbers.get_exponential_symbol('ar_EG', numbering_system="default") == 'اس' - assert numbers.get_exponential_symbol('ar_EG', numbering_system="arab") == 'اس' + assert numbers.get_exponential_symbol('ar_EG', numbering_system="default") == 'أس' + assert numbers.get_exponential_symbol('ar_EG', numbering_system="arab") == 'أس' assert numbers.get_exponential_symbol('ar_EG', numbering_system="latn") == 'E' @@ -593,8 +593,10 @@ def test_format_currency_long_display_name(): assert (numbers.format_currency(2, 'EUR', locale='en_US', format_type='name') == '2.00 euros') # This tests that '{1} {0}' unitPatterns are found: + assert (numbers.format_currency(150, 'USD', locale='sw', format_type='name') + == 'dola za Marekani 150.00') assert (numbers.format_currency(1, 'USD', locale='sw', format_type='name') - == 'dola ya Marekani 1.00') + == '1.00 dola ya Marekani') # This tests unicode chars: assert (numbers.format_currency(1099.98, 'USD', locale='es_GT', format_type='name') == 'dólares estadounidenses 1,099.98') @@ -689,7 +691,7 @@ def test_format_scientific(): assert numbers.format_scientific(4234567, '##0.#####E00', locale='en_US') == '4.23457E06' assert numbers.format_scientific(4234567, '##0.##E00', locale='en_US') == '4.23E06' assert numbers.format_scientific(42, '00000.000000E0000', locale='en_US') == '42000.000000E-0003' - assert numbers.format_scientific(0.2, locale="ar_EG", numbering_system="default") == '2اس\u061c-1' + assert numbers.format_scientific(0.2, locale="ar_EG", numbering_system="default") == '2أس\u061c-1' def test_default_scientific_format(): diff --git a/tests/test_support.py b/tests/test_support.py index d0d1ac223..ccd8fe60b 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -345,7 +345,7 @@ def test_format_percent(self): def test_format_scientific(self): assert support.Format('en_US').scientific(10000) == '1E4' assert support.Format('en_US').scientific(Decimal("10000")) == '1E4' - assert support.Format('ar_EG', numbering_system="default").scientific(10000) == '1اس4' + assert support.Format('ar_EG', numbering_system="default").scientific(10000) == '1أس4' def test_lazy_proxy(): From c0fb56e6a5a7fa9268b5164db0ff0fc28524d648 Mon Sep 17 00:00:00 2001 From: Ronan Amicel Date: Mon, 22 Apr 2024 08:53:54 +0200 Subject: [PATCH 5/8] Allow alternative space characters as group separator when parsing numbers (#1007) The French group separator is `"\u202f"` (narrow non-breaking space), but when parsing numbers in the real world, you will most often encounter either a regular space character (`" "`) or a non-breaking space character (`"\xa0"`). The issue was partially adressed earlier in https://github.com/python-babel/babel/issues/637, but only to allow regular spaces instead of non-breaking spaces `"\xa0"` in `parse_decimal`. This commit goes further by changing both `parse_number` and `parse_decimal` to allow certain other space characters when the group character is itself a space character, but is not present in the string to parse. Unit tests are included. --- babel/numbers.py | 27 +++++++++++++++++++++++---- tests/test_numbers.py | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/babel/numbers.py b/babel/numbers.py index 2d46e0271..6df1db8cb 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -998,6 +998,15 @@ def __init__(self, message: str, suggestions: list[str] | None = None) -> None: self.suggestions = suggestions +SPACE_CHARS = { + ' ', # space + '\xa0', # no-break space + '\u202f', # narrow no-break space +} + +SPACE_CHARS_RE = re.compile('|'.join(SPACE_CHARS)) + + def parse_number( string: str, locale: Locale | str | None = LC_NUMERIC, @@ -1026,8 +1035,18 @@ def parse_number( :raise `NumberFormatError`: if the string can not be converted to a number :raise `UnsupportedNumberingSystemError`: if the numbering system is not supported by the locale. """ + group_symbol = get_group_symbol(locale, numbering_system=numbering_system) + + if ( + group_symbol in SPACE_CHARS and # if the grouping symbol is a kind of space, + group_symbol not in string and # and the string to be parsed does not contain it, + SPACE_CHARS_RE.search(string) # but it does contain any other kind of space instead, + ): + # ... it's reasonable to assume it is taking the place of the grouping symbol. + string = SPACE_CHARS_RE.sub(group_symbol, string) + try: - return int(string.replace(get_group_symbol(locale, numbering_system=numbering_system), '')) + return int(string.replace(group_symbol, '')) except ValueError as ve: raise NumberFormatError(f"{string!r} is not a valid number") from ve @@ -1085,12 +1104,12 @@ def parse_decimal( decimal_symbol = get_decimal_symbol(locale, numbering_system=numbering_system) if not strict and ( - group_symbol == '\xa0' and # if the grouping symbol is U+00A0 NO-BREAK SPACE, + group_symbol in SPACE_CHARS and # if the grouping symbol is a kind of space, group_symbol not in string and # and the string to be parsed does not contain it, - ' ' in string # but it does contain a space instead, + SPACE_CHARS_RE.search(string) # but it does contain any other kind of space instead, ): # ... it's reasonable to assume it is taking the place of the grouping symbol. - string = string.replace(' ', group_symbol) + string = SPACE_CHARS_RE.sub(group_symbol, string) try: parsed = decimal.Decimal(string.replace(group_symbol, '') diff --git a/tests/test_numbers.py b/tests/test_numbers.py index e58f0735f..eeb71a2fc 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -751,6 +751,15 @@ def test_parse_number(): with pytest.raises(numbers.UnsupportedNumberingSystemError): numbers.parse_number('1.099,98', locale='en', numbering_system="unsupported") +@pytest.mark.parametrize('string', [ + '1 099', + '1\xa0099', + '1\u202f099', +]) +def test_parse_number_group_separator_can_be_any_space(string): + assert numbers.parse_number(string, locale='fr') == 1099 + + def test_parse_decimal(): assert (numbers.parse_decimal('1,099.98', locale='en_US') == decimal.Decimal('1099.98')) @@ -761,6 +770,15 @@ def test_parse_decimal(): assert excinfo.value.args[0] == "'2,109,998' is not a valid decimal number" +@pytest.mark.parametrize('string', [ + '1 099,98', + '1\xa0099,98', + '1\u202f099,98', +]) +def test_parse_decimal_group_separator_can_be_any_space(string): + assert decimal.Decimal('1099.98') == numbers.parse_decimal(string, locale='fr') + + def test_parse_grouping(): assert numbers.parse_grouping('##') == (1000, 1000) assert numbers.parse_grouping('#,###') == (3, 3) From 1a03526e2dda9818424c400530163464a2e74b9b Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 22 Apr 2024 09:54:14 +0300 Subject: [PATCH 6/8] Include Unicode license in `locale-data` and in documentation (#1074) Fixes #1072 --- MANIFEST.in | 1 + babel/locale-data/LICENSE.unicode | 41 +++++++++++++++++++++++++++++++ docs/license.rst | 13 +++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 babel/locale-data/LICENSE.unicode diff --git a/MANIFEST.in b/MANIFEST.in index 1b79ce1d9..82cb37a22 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include Makefile CHANGES.rst LICENSE AUTHORS include conftest.py tox.ini include babel/global.dat include babel/locale-data/*.dat +include babel/locale-data/LICENSE* recursive-include docs * recursive-exclude docs/_build * include scripts/* diff --git a/babel/locale-data/LICENSE.unicode b/babel/locale-data/LICENSE.unicode new file mode 100644 index 000000000..a82da8263 --- /dev/null +++ b/babel/locale-data/LICENSE.unicode @@ -0,0 +1,41 @@ +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 2004-2024 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. + +SPDX-License-Identifier: Unicode-3.0 diff --git a/docs/license.rst b/docs/license.rst index 7c93ab426..1d93751c9 100644 --- a/docs/license.rst +++ b/docs/license.rst @@ -25,13 +25,24 @@ documentation. - "AUTHORS" hereby refers to all the authors listed in the :ref:`authors` section. -- The ":ref:`babel-license`" applies to all the sourcecode shipped as +- The ":ref:`babel-license`" applies to all the source code shipped as part of Babel (Babel itself as well as the examples and the unit tests) as well as documentation. +- The ":ref:`unicode-license`" applies to the transformed Unicode + Common Locale Data Repository (CLDR) data files shipped with Babel, + in the directory ``babel/locale-data``. + .. _babel-license: Babel License ------------- .. include:: ../LICENSE + +.. _unicode-license: + +Unicode License +--------------- + +.. include:: ../babel/locale-data/LICENSE.unicode From c2e6c6e538418f4c195275c1afff831c4706c2e1 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 25 Apr 2024 21:23:10 +0300 Subject: [PATCH 7/8] Encode support for the "fall back to short format" logic for time delta formatting (#1075) Fixes #892 --- babel/dates.py | 12 ++++++++++-- tests/test_dates.py | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/babel/dates.py b/babel/dates.py index 040820a6d..c2e4f0cc7 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -943,7 +943,14 @@ def _iter_patterns(a_unit): else: yield unit_rel_patterns['past'] a_unit = f"duration-{a_unit}" - yield locale._data['unit_patterns'].get(a_unit, {}).get(format) + unit_pats = locale._data['unit_patterns'].get(a_unit, {}) + yield unit_pats.get(format) + # We do not support `` tags at all while ingesting CLDR data, + # so these aliases specified in `root.xml` are hard-coded here: + # + # + if format in ("long", "narrow"): + yield unit_pats.get("short") for unit, secs_per_unit in TIMEDELTA_UNITS: value = abs(seconds) / secs_per_unit @@ -956,7 +963,8 @@ def _iter_patterns(a_unit): for patterns in _iter_patterns(unit): if patterns is not None: pattern = patterns.get(plural_form) or patterns.get('other') - break + if pattern: + break # This really should not happen if pattern is None: return '' diff --git a/tests/test_dates.py b/tests/test_dates.py index d3f2ad9e7..f25dd6146 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -742,3 +742,12 @@ def test_en_gb_first_weekday(): def test_issue_798(): assert dates.format_timedelta(timedelta(), format='narrow', locale='es_US') == '0s' + + +def test_issue_892(): + assert dates.format_timedelta(timedelta(seconds=1), format='narrow', locale='pt_BR') == '1 s' + assert dates.format_timedelta(timedelta(minutes=1), format='narrow', locale='pt_BR') == '1 min' + assert dates.format_timedelta(timedelta(hours=1), format='narrow', locale='pt_BR') == '1 h' + assert dates.format_timedelta(timedelta(days=1), format='narrow', locale='pt_BR') == '1 dia' + assert dates.format_timedelta(timedelta(days=30), format='narrow', locale='pt_BR') == '1 mês' + assert dates.format_timedelta(timedelta(days=365), format='narrow', locale='pt_BR') == '1 ano' From 40b194f4777366e95cc2dfb680fd696b86ef1c04 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Sun, 5 May 2024 16:48:29 +0300 Subject: [PATCH 8/8] Prepare for 2.15.0 release (#1079) --- AUTHORS | 2 ++ CHANGES.rst | 22 ++++++++++++++++++++++ LICENSE | 2 +- babel/__init__.py | 4 ++-- babel/core.py | 2 +- babel/dates.py | 2 +- babel/lists.py | 2 +- babel/localedata.py | 2 +- babel/localtime/__init__.py | 2 +- babel/localtime/_fallback.py | 2 +- babel/messages/__init__.py | 2 +- babel/messages/catalog.py | 2 +- babel/messages/checkers.py | 2 +- babel/messages/extract.py | 2 +- babel/messages/frontend.py | 2 +- babel/messages/jslexer.py | 2 +- babel/messages/mofile.py | 2 +- babel/messages/plurals.py | 2 +- babel/messages/pofile.py | 2 +- babel/numbers.py | 2 +- babel/plural.py | 2 +- babel/support.py | 2 +- babel/util.py | 2 +- docs/conf.py | 6 +++--- scripts/dump_data.py | 2 +- scripts/dump_global.py | 2 +- scripts/import_cldr.py | 2 +- tests/messages/test_catalog.py | 2 +- tests/messages/test_checkers.py | 2 +- tests/messages/test_extract.py | 2 +- tests/messages/test_frontend.py | 2 +- tests/messages/test_mofile.py | 2 +- tests/messages/test_plurals.py | 2 +- tests/messages/test_pofile.py | 2 +- tests/test_core.py | 2 +- tests/test_dates.py | 2 +- tests/test_localedata.py | 2 +- tests/test_numbers.py | 2 +- tests/test_plural.py | 2 +- tests/test_support.py | 2 +- tests/test_util.py | 2 +- 41 files changed, 66 insertions(+), 42 deletions(-) diff --git a/AUTHORS b/AUTHORS index cfea92e37..67335b7a3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -49,6 +49,8 @@ Babel is written and maintained by the Babel team and various contributors: - Arturas Moskvinas - Leonardo Pistone - Hyunjun Kim +- Ronan Amicel +- Christian Clauss - Best Olunusi - Teo - Ivan Koldakov diff --git a/CHANGES.rst b/CHANGES.rst index 31b1bd543..d3a7b00b3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,28 @@ Babel Changelog =============== +Version 2.15.0 +-------------- + +Python version support +~~~~~~~~~~~~~~~~~~~~~~ + +* Babel 2.15.0 will require Python 3.8 or newer. (:gh:`1048`) + +Features +~~~~~~~~ + +* CLDR: Upgrade to CLDR 44 (:gh:`1071`) (@akx) +* Dates: Support for the "fall back to short format" logic for time delta formatting (:gh:`1075`) (@akx) +* Message: More versatile .po IO functions (:gh:`1068`) (@akx) +* Numbers: Improved support for alternate spaces when parsing numbers (:gh:`1007`) (@ronnix's first contribution) + +Infrastructure +~~~~~~~~~~~~~~ + +* Upgrade GitHub Actions (:gh:`1054`) (@cclauss's first contribution) +* The Unicode license is now included in `locale-data` and in the documentation (:gh:`1074`) (@akx) + Version 2.14.0 -------------- diff --git a/LICENSE b/LICENSE index 41a1e593b..83b6e513d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2023 by the Babel Team, see AUTHORS for more information. +Copyright (c) 2013-2024 by the Babel Team, see AUTHORS for more information. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/babel/__init__.py b/babel/__init__.py index ff986e9ce..6a9789c52 100644 --- a/babel/__init__.py +++ b/babel/__init__.py @@ -12,7 +12,7 @@ access to various locale display names, localized number and date formatting, etc. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ @@ -25,7 +25,7 @@ parse_locale, ) -__version__ = '2.14.0' +__version__ = '2.15.0' __all__ = [ 'Locale', diff --git a/babel/core.py b/babel/core.py index 207c13b92..56a4ee503 100644 --- a/babel/core.py +++ b/babel/core.py @@ -4,7 +4,7 @@ Core locale representation and locale data access. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/dates.py b/babel/dates.py index c2e4f0cc7..df129604a 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -11,7 +11,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/lists.py b/babel/lists.py index a8471fd1e..376bc963e 100644 --- a/babel/lists.py +++ b/babel/lists.py @@ -10,7 +10,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2015-2023 by the Babel Team. + :copyright: (c) 2015-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/localedata.py b/babel/localedata.py index f1e8a1291..a9f7d4bf9 100644 --- a/babel/localedata.py +++ b/babel/localedata.py @@ -7,7 +7,7 @@ :note: The `Locale` class, which uses this module under the hood, provides a more convenient interface for accessing the locale data. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/localtime/__init__.py b/babel/localtime/__init__.py index e1ece7342..1066c9511 100644 --- a/babel/localtime/__init__.py +++ b/babel/localtime/__init__.py @@ -5,7 +5,7 @@ Babel specific fork of tzlocal to determine the local timezone of the system. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/localtime/_fallback.py b/babel/localtime/_fallback.py index 14979a53b..7c99f488c 100644 --- a/babel/localtime/_fallback.py +++ b/babel/localtime/_fallback.py @@ -4,7 +4,7 @@ Emulated fallback local timezone when all else fails. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/__init__.py b/babel/messages/__init__.py index 883a2e055..5a81b910f 100644 --- a/babel/messages/__init__.py +++ b/babel/messages/__init__.py @@ -4,7 +4,7 @@ Support for ``gettext`` message catalogs. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py index 41adfaeee..9f215cf18 100644 --- a/babel/messages/catalog.py +++ b/babel/messages/catalog.py @@ -4,7 +4,7 @@ Data structures for message catalogs. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/messages/checkers.py b/babel/messages/checkers.py index e5448e06b..df1159ded 100644 --- a/babel/messages/checkers.py +++ b/babel/messages/checkers.py @@ -6,7 +6,7 @@ :since: version 0.9 - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/messages/extract.py b/babel/messages/extract.py index 8e3779871..862e63743 100644 --- a/babel/messages/extract.py +++ b/babel/messages/extract.py @@ -12,7 +12,7 @@ The main entry points into the extraction functionality are the functions `extract_from_dir` and `extract_from_file`. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py index 34f9e8902..aeda9763b 100644 --- a/babel/messages/frontend.py +++ b/babel/messages/frontend.py @@ -4,7 +4,7 @@ Frontends for the message extraction functionality. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ diff --git a/babel/messages/jslexer.py b/babel/messages/jslexer.py index 6456bd038..31f0582e3 100644 --- a/babel/messages/jslexer.py +++ b/babel/messages/jslexer.py @@ -5,7 +5,7 @@ A simple JavaScript 1.5 lexer which is used for the JavaScript extractor. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py index ca02e6837..0291b07cc 100644 --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -4,7 +4,7 @@ Writing of files in the ``gettext`` MO (machine object) format. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/messages/plurals.py b/babel/messages/plurals.py index fa3f03ed8..01c38f4cd 100644 --- a/babel/messages/plurals.py +++ b/babel/messages/plurals.py @@ -4,7 +4,7 @@ Plural form definitions. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py index 6e9dbdf03..89a924255 100644 --- a/babel/messages/pofile.py +++ b/babel/messages/pofile.py @@ -5,7 +5,7 @@ Reading and writing of files in the ``gettext`` PO (portable object) format. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/numbers.py b/babel/numbers.py index 6df1db8cb..624e8d61e 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -11,7 +11,7 @@ * ``LC_ALL``, and * ``LANG`` - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ # TODO: diff --git a/babel/plural.py b/babel/plural.py index 01df16c6d..3be412f69 100644 --- a/babel/plural.py +++ b/babel/plural.py @@ -4,7 +4,7 @@ CLDR Plural support. See UTS #35. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/support.py b/babel/support.py index 1774d9d85..e6ee37805 100644 --- a/babel/support.py +++ b/babel/support.py @@ -7,7 +7,7 @@ .. note: the code in this module is not used by Babel itself - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/babel/util.py b/babel/util.py index 093197f9f..605695b1f 100644 --- a/babel/util.py +++ b/babel/util.py @@ -4,7 +4,7 @@ Various utility classes and functions. - :copyright: (c) 2013-2023 by the Babel Team. + :copyright: (c) 2013-2024 by the Babel Team. :license: BSD, see LICENSE for more details. """ from __future__ import annotations diff --git a/docs/conf.py b/docs/conf.py index 3be498181..128eacd7d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,16 +44,16 @@ # General information about the project. project = 'Babel' -copyright = '2023, The Babel Team' +copyright = '2024, The Babel Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '2.14' +version = '2.15' # The full version, including alpha/beta/rc tags. -release = '2.14.0' +release = '2.15.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/scripts/dump_data.py b/scripts/dump_data.py index b17beb7be..639c14d43 100755 --- a/scripts/dump_data.py +++ b/scripts/dump_data.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/scripts/dump_global.py b/scripts/dump_global.py index 1018a5392..8b8294014 100755 --- a/scripts/dump_global.py +++ b/scripts/dump_global.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 1ff39a8d9..633ca9a01 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py index 4e133304f..2df85debb 100644 --- a/tests/messages/test_catalog.py +++ b/tests/messages/test_catalog.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_checkers.py b/tests/messages/test_checkers.py index ac1e1bc46..b2dc9dc4e 100644 --- a/tests/messages/test_checkers.py +++ b/tests/messages/test_checkers.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_extract.py b/tests/messages/test_extract.py index 086702ed7..7d3a05aa7 100644 --- a/tests/messages/test_extract.py +++ b/tests/messages/test_extract.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_frontend.py b/tests/messages/test_frontend.py index 225d6b358..448dd7357 100644 --- a/tests/messages/test_frontend.py +++ b/tests/messages/test_frontend.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_mofile.py b/tests/messages/test_mofile.py index 986a74d6c..7a699041d 100644 --- a/tests/messages/test_mofile.py +++ b/tests/messages/test_mofile.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_plurals.py b/tests/messages/test_plurals.py index 41438f58d..e9f8e80fd 100644 --- a/tests/messages/test_plurals.py +++ b/tests/messages/test_plurals.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py index d322857d5..99958b7b7 100644 --- a/tests/messages/test_pofile.py +++ b/tests/messages/test_pofile.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_core.py b/tests/test_core.py index 2e7846214..1bec2155d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_dates.py b/tests/test_dates.py index f25dd6146..fb9013143 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_localedata.py b/tests/test_localedata.py index d4eb4e597..8a4fbef1c 100644 --- a/tests/test_localedata.py +++ b/tests/test_localedata.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_numbers.py b/tests/test_numbers.py index eeb71a2fc..ed0531c27 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_plural.py b/tests/test_plural.py index 2fa56cf4e..b10ccf5ad 100644 --- a/tests/test_plural.py +++ b/tests/test_plural.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_support.py b/tests/test_support.py index ccd8fe60b..126272051 100644 --- a/tests/test_support.py +++ b/tests/test_support.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which diff --git a/tests/test_util.py b/tests/test_util.py index 8ea68b2bb..c661894c8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2007-2011 Edgewall Software, 2013-2023 the Babel team +# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team # All rights reserved. # # This software is licensed as described in the file LICENSE, which