Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.9.1
hooks:
- id: ruff
args:
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Details can be found in the HTML files in the ``docs`` folder.

For more information please visit the Babel web site:

http://babel.pocoo.org/
https://babel.pocoo.org/

Join the chat at https://gitter.im/python-babel/babel

Expand Down
1 change: 1 addition & 0 deletions babel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
__all__ = [
'Locale',
'UnknownLocaleError',
'__version__',
'default_locale',
'get_locale_identifier',
'negotiate_locale',
Expand Down
18 changes: 13 additions & 5 deletions babel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@
from babel import localedata
from babel.plural import PluralRule

__all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale',
'parse_locale']
__all__ = [
'Locale',
'UnknownLocaleError',
'default_locale',
'get_global',
'get_locale_identifier',
'negotiate_locale',
'parse_locale',
]

if TYPE_CHECKING:
from typing_extensions import Literal, TypeAlias
Expand Down Expand Up @@ -259,6 +266,7 @@ def negotiate(
:param preferred: the list of locale identifiers preferred by the user
:param available: the list of locale identifiers available
:param aliases: a dictionary of aliases for locale identifiers
:param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
"""
identifier = negotiate_locale(preferred, available, sep=sep,
aliases=aliases)
Expand Down Expand Up @@ -575,7 +583,7 @@ def languages(self) -> localedata.LocaleDataDict:
>>> Locale('de', 'DE').languages['ja']
u'Japanisch'

See `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ for
See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
more information.
"""
return self._data['languages']
Expand All @@ -587,7 +595,7 @@ def scripts(self) -> localedata.LocaleDataDict:
>>> Locale('en', 'US').scripts['Hira']
u'Hiragana'

See `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_
See `ISO 15924 <https://www.unicode.org/iso15924/>`_
for more information.
"""
return self._data['scripts']
Expand All @@ -599,7 +607,7 @@ def territories(self) -> localedata.LocaleDataDict:
>>> Locale('es', 'CO').territories['DE']
u'Alemania'

See `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_
See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
for more information.
"""
return self._data['territories']
Expand Down
6 changes: 5 additions & 1 deletion babel/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1935,14 +1935,18 @@ def match_skeleton(skeleton: str, options: Iterable[str], allow_different_fields
:type skeleton: str
:param options: An iterable of other skeletons to match against
:type options: Iterable[str]
:param allow_different_fields: Whether to allow a match that uses different fields
than the skeleton requested.
:type allow_different_fields: bool

:return: The closest skeleton match, or if no match was found, None.
:rtype: str|None
"""

# TODO: maybe implement pattern expansion?

# Based on the implementation in
# http://source.icu-project.org/repos/icu/icu4j/trunk/main/classes/core/src/com/ibm/icu/text/DateIntervalInfo.java
# https://github.com/unicode-org/icu/blob/main/icu4j/main/core/src/main/java/com/ibm/icu/text/DateIntervalInfo.java

# Filter out falsy values and sort for stability; when `interval_formats` is passed in, there may be a None key.
options = sorted(option for option in options if option)
Expand Down
75 changes: 43 additions & 32 deletions babel/messages/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@

_MessageID: TypeAlias = str | tuple[str, ...] | list[str]

__all__ = ['Message', 'Catalog', 'TranslationError']
__all__ = [
'DEFAULT_HEADER',
'PYTHON_FORMAT',
'Catalog',
'Message',
'TranslationError',
]


def get_close_matches(word, possibilities, n=3, cutoff=0.6):
"""A modified version of ``difflib.get_close_matches``.
Expand All @@ -42,7 +49,7 @@ def get_close_matches(word, possibilities, n=3, cutoff=0.6):
if not 0.0 <= cutoff <= 1.0: # pragma: no cover
raise ValueError(f"cutoff must be in [0.0, 1.0]: {cutoff!r}")
result = []
s = SequenceMatcher(autojunk=False) # only line changed from difflib.py
s = SequenceMatcher(autojunk=False) # only line changed from difflib.py
s.set_seq2(word)
for x in possibilities:
s.set_seq1(x)
Expand Down Expand Up @@ -274,6 +281,14 @@ def parse_separated_header(value: str) -> dict[str, str]:
return dict(m.get_params())


def _force_text(s: str | bytes, encoding: str = 'utf-8', errors: str = 'strict') -> str:
if isinstance(s, str):
return s
if isinstance(s, bytes):
return s.decode(encoding, errors)
return str(s)


class Catalog:
"""Representation of a message catalog."""

Expand Down Expand Up @@ -428,46 +443,39 @@ def _set_header_comment(self, string: str | None) -> None:
""")

def _get_mime_headers(self) -> list[tuple[str, str]]:
headers: list[tuple[str, str]] = []
headers.append(("Project-Id-Version", f"{self.project} {self.version}"))
headers.append(('Report-Msgid-Bugs-To', self.msgid_bugs_address))
headers.append(('POT-Creation-Date',
format_datetime(self.creation_date, 'yyyy-MM-dd HH:mmZ',
locale='en')))
if isinstance(self.revision_date, (datetime.datetime, datetime.time, int, float)):
headers.append(('PO-Revision-Date',
format_datetime(self.revision_date,
'yyyy-MM-dd HH:mmZ', locale='en')))
revision_date = format_datetime(self.revision_date, 'yyyy-MM-dd HH:mmZ', locale='en')
else:
headers.append(('PO-Revision-Date', self.revision_date))
headers.append(('Last-Translator', self.last_translator))
revision_date = self.revision_date

language_team = self.language_team
if self.locale_identifier and 'LANGUAGE' in language_team:
language_team = language_team.replace('LANGUAGE', str(self.locale_identifier))

headers: list[tuple[str, str]] = [
("Project-Id-Version", f"{self.project} {self.version}"),
('Report-Msgid-Bugs-To', self.msgid_bugs_address),
('POT-Creation-Date', format_datetime(self.creation_date, 'yyyy-MM-dd HH:mmZ', locale='en')),
('PO-Revision-Date', revision_date),
('Last-Translator', self.last_translator),
]
if self.locale_identifier:
headers.append(('Language', str(self.locale_identifier)))
if self.locale_identifier and ('LANGUAGE' in self.language_team):
headers.append(('Language-Team',
self.language_team.replace('LANGUAGE',
str(self.locale_identifier))))
else:
headers.append(('Language-Team', self.language_team))
headers.append(('Language-Team', language_team))
if self.locale is not None:
headers.append(('Plural-Forms', self.plural_forms))
headers.append(('MIME-Version', '1.0'))
headers.append(("Content-Type", f"text/plain; charset={self.charset}"))
headers.append(('Content-Transfer-Encoding', '8bit'))
headers.append(("Generated-By", f"Babel {VERSION}\n"))
headers += [
('MIME-Version', '1.0'),
("Content-Type", f"text/plain; charset={self.charset}"),
('Content-Transfer-Encoding', '8bit'),
("Generated-By", f"Babel {VERSION}\n"),
]
return headers

def _force_text(self, s: str | bytes, encoding: str = 'utf-8', errors: str = 'strict') -> str:
if isinstance(s, str):
return s
if isinstance(s, bytes):
return s.decode(encoding, errors)
return str(s)

def _set_mime_headers(self, headers: Iterable[tuple[str, str]]) -> None:
for name, value in headers:
name = self._force_text(name.lower(), encoding=self.charset)
value = self._force_text(value, encoding=self.charset)
name = _force_text(name.lower(), encoding=self.charset)
value = _force_text(value, encoding=self.charset)
if name == 'project-id-version':
parts = value.split(' ')
self.project = ' '.join(parts[:-1])
Expand Down Expand Up @@ -824,6 +832,9 @@ def update(

:param template: the reference catalog, usually read from a POT file
:param no_fuzzy_matching: whether to use fuzzy matching of message IDs
:param update_header_comment: whether to copy the header comment from the template
:param keep_user_comments: whether to keep user comments from the old catalog
:param update_creation_date: whether to copy the creation date from the template
"""
messages = self._messages
remaining = messages.copy()
Expand Down
16 changes: 9 additions & 7 deletions babel/messages/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,20 @@ def extract_from_file(
options, strip_comment_tags))


def _match_messages_against_spec(lineno: int, messages: list[str|None], comments: list[str],
fileobj: _FileObj, spec: tuple[int|tuple[int, str], ...]):
def _match_messages_against_spec(
lineno: int,
messages: list[str | None],
comments: list[str],
fileobj: _FileObj,
spec: tuple[int | tuple[int, str], ...],
):
translatable = []
context = None

# last_index is 1 based like the keyword spec
last_index = len(messages)
for index in spec:
if isinstance(index, tuple): # (n, 'c')
if isinstance(index, tuple): # (n, 'c')
context = messages[index[0] - 1]
continue
if last_index < index:
Expand Down Expand Up @@ -421,7 +426,6 @@ def extract(
:returns: iterable of tuples of the form ``(lineno, message, comments, context)``
:rtype: Iterable[tuple[int, str|tuple[str], list[str], str|None]
"""
func = None
if callable(method):
func = method
elif ':' in method or '.' in method:
Expand Down Expand Up @@ -622,9 +626,7 @@ def extract_python(
elif tok == NAME and value in keywords:
funcname = value

if (current_fstring_start is not None
and tok not in {FSTRING_START, FSTRING_MIDDLE}
):
if current_fstring_start is not None and tok not in {FSTRING_START, FSTRING_MIDDLE}:
# In Python 3.12, tokens other than FSTRING_* mean the
# f-string is dynamic, so we don't wan't to extract it.
# And if it's FSTRING_END, we've already handled it above.
Expand Down
3 changes: 2 additions & 1 deletion babel/messages/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ def _configure_command(self, cmdname, argv):
usage=self.usage % (cmdname, ''),
description=self.commands[cmdname],
)
as_args = getattr(cmdclass, "as_args", ())
as_args: str | None = getattr(cmdclass, "as_args", None)
for long, short, help in cmdclass.user_options:
name = long.strip("=")
default = getattr(cmdinst, name.replace("-", "_"))
Expand Down Expand Up @@ -1012,6 +1012,7 @@ def parse_mapping_cfg(fileobj, filename=None):

:param fileobj: a readable file-like object containing the configuration
text to parse
:param filename: the name of the file being parsed, for error messages
"""
extractors = {}
method_map = []
Expand Down
1 change: 1 addition & 0 deletions babel/messages/jslexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def tokenize(source: str, jsx: bool = True, dotted: bool = True, template_string
"""
Tokenize JavaScript/JSX source. Returns a generator of tokens.

:param source: The JavaScript source to tokenize.
:param jsx: Enable (limited) JSX parsing.
:param dotted: Read dotted names as single name token.
:param template_string: Support ES6 template strings
Expand Down
23 changes: 15 additions & 8 deletions babel/messages/plurals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
"""
from __future__ import annotations

from operator import itemgetter

from babel.core import Locale, default_locale

# XXX: remove this file, duplication with babel.plural
Expand Down Expand Up @@ -209,12 +207,21 @@ class _PluralTuple(tuple):
"""A tuple with plural information."""

__slots__ = ()
num_plurals = property(itemgetter(0), doc="""
The number of plurals used by the locale.""")
plural_expr = property(itemgetter(1), doc="""
The plural expression used by the locale.""")
plural_forms = property(lambda x: 'nplurals={}; plural={};'.format(*x), doc="""
The plural expression used by the catalog or locale.""")

@property
def num_plurals(self) -> int:
"""The number of plurals used by the locale."""
return self[0]

@property
def plural_expr(self) -> str:
"""The plural expression used by the locale."""
return self[1]

@property
def plural_forms(self) -> str:
"""The plural expression used by the catalog or locale."""
return f'nplurals={self[0]}; plural={self[1]};'

def __str__(self) -> str:
return self.plural_forms
Expand Down
3 changes: 2 additions & 1 deletion babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def get_currency_precision(currency: str) -> int:


def get_currency_unit_pattern(
currency: str,
currency: str, # TODO: unused?!
count: float | decimal.Decimal | None = None,
locale: Locale | str | None = None,
) -> str:
Expand Down Expand Up @@ -1404,6 +1404,7 @@ def apply(
:type decimal_quantization: bool
:param force_frac: DEPRECATED - a forced override for `self.frac_prec`
for a single formatting invocation.
:param group_separator: Whether to use the locale's number group separator.
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
The special value "default" will use the default numbering system of the locale.
:return: Formatted decimal string.
Expand Down
2 changes: 1 addition & 1 deletion babel/plural.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def within_range_list(num: float | decimal.Decimal, range_list: Iterable[Iterabl
>>> within_range_list(10.5, [(1, 4), (20, 30)])
False
"""
return any(num >= min_ and num <= max_ for min_, max_ in range_list)
return any(min_ <= num <= max_ for min_, max_ in range_list)


def cldr_modulo(a: float, b: float) -> float:
Expand Down
Loading
Loading