"""
- babel
- ~~~~~
+babel
+~~~~~
- Integrated collection of utilities that assist in internationalizing and
- localizing applications.
+Integrated collection of utilities that assist in internationalizing and
+localizing applications.
- This package is basically composed of two major parts:
+This package is basically composed of two major parts:
- * tools to build and work with ``gettext`` message catalogs
- * a Python interface to the CLDR (Common Locale Data Repository), providing
- access to various locale display names, localized number and date
- formatting, etc.
+ * tools to build and work with ``gettext`` message catalogs
+ * a Python interface to the CLDR (Common Locale Data Repository), providing
+ access to various locale display names, localized number and date
+ formatting, etc.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
from babel.core import (
"""
- babel.core
- ~~~~~~~~~~
+babel.core
+~~~~~~~~~~
- Core locale representation and locale data access.
+Core locale representation and locale data access.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
def _raise_no_data_error():
- raise RuntimeError('The babel data files are not available. '
- 'This usually happens because you are using '
- 'a source checkout from Babel and you did '
- 'not build the data files. Just make sure '
- 'to run "python setup.py import_cldr" before '
- 'installing the library.')
+ raise RuntimeError(
+ 'The babel data files are not available. '
+ 'This usually happens because you are using '
+ 'a source checkout from Babel and you did '
+ 'not build the data files. Just make sure '
+ 'to run "python setup.py import_cldr" before '
+ 'installing the library.',
+ )
def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]:
raise UnknownLocaleError(identifier)
@classmethod
- def default(cls, category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> Locale:
+ def default(
+ cls,
+ category: str | None = None,
+ aliases: Mapping[str, str] = LOCALE_ALIASES,
+ ) -> Locale:
"""Return the system default locale for the specified category.
>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
: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)
+ identifier = negotiate_locale(preferred, available, sep=sep, aliases=aliases)
if identifier:
return Locale.parse(identifier, sep=sep)
return None
else:
language2, _, script2, variant2 = parts2
modifier2 = None
- locale = _try_load_reducing((language2, territory, script2, variant2, modifier2))
+ locale = _try_load_reducing(
+ (language2, territory, script2, variant2, modifier2),
+ )
if locale is not None:
return locale
if not hasattr(other, key):
return False
return (
- self.language == getattr(other, 'language') and # noqa: B009
- self.territory == getattr(other, 'territory') and # noqa: B009
- self.script == getattr(other, 'script') and # noqa: B009
- self.variant == getattr(other, 'variant') and # noqa: B009
- self.modifier == getattr(other, 'modifier') # noqa: B009
+ self.language == getattr(other, 'language') # noqa: B009
+ and self.territory == getattr(other, 'territory') # noqa: B009
+ and self.script == getattr(other, 'script') # noqa: B009
+ and self.variant == getattr(other, 'variant') # noqa: B009
+ and self.modifier == getattr(other, 'modifier') # noqa: B009
)
def __ne__(self, other: object) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
- return hash((self.language, self.territory, self.script,
- self.variant, self.modifier))
+ return hash((self.language, self.territory, self.script, self.variant, self.modifier))
def __repr__(self) -> str:
parameters = ['']
return f"Locale({self.language!r}{', '.join(parameters)})"
def __str__(self) -> str:
- return get_locale_identifier((self.language, self.territory,
- self.script, self.variant,
- self.modifier))
+ return get_locale_identifier(
+ (self.language, self.territory, self.script, self.variant, self.modifier),
+ )
@property
def _data(self) -> localedata.LocaleDataDict:
retval += f" ({detail_string})"
return retval
- display_name = property(get_display_name, doc="""\
+ display_name = property(
+ get_display_name,
+ doc="""\
The localized display name of the locale.
>>> Locale('en').display_name
u'svenska'
:type: `unicode`
- """)
+ """,
+ )
def get_language_name(self, locale: Locale | str | None = None) -> str | None:
"""Return the language of this locale in the given locale.
locale = Locale.parse(locale)
return locale.languages.get(self.language)
- language_name = property(get_language_name, doc="""\
+ language_name = property(
+ get_language_name,
+ doc="""\
The localized language name of the locale.
>>> Locale('en', 'US').language_name
u'English'
- """)
+ """,
+ )
def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
"""Return the territory name in the given locale."""
locale = Locale.parse(locale)
return locale.territories.get(self.territory or '')
- territory_name = property(get_territory_name, doc="""\
+ territory_name = property(
+ get_territory_name,
+ doc="""\
The localized territory name of the locale if available.
>>> Locale('de', 'DE').territory_name
u'Deutschland'
- """)
+ """,
+ )
def get_script_name(self, locale: Locale | str | None = None) -> str | None:
"""Return the script name in the given locale."""
locale = Locale.parse(locale)
return locale.scripts.get(self.script or '')
- script_name = property(get_script_name, doc="""\
+ script_name = property(
+ get_script_name,
+ doc="""\
The localized script name of the locale if available.
>>> Locale('sr', 'ME', script='Latn').script_name
u'latinica'
- """)
+ """,
+ )
@property
def english_name(self) -> str | None:
@property
def day_period_rules(self) -> localedata.LocaleDataDict:
- """Day period rules for the locale. Used by `get_period_id`.
- """
+ """Day period rules for the locale. Used by `get_period_id`."""
return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
@property
return None
-def negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
+def negotiate_locale(
+ preferred: Iterable[str],
+ available: Iterable[str],
+ sep: str = '_',
+ aliases: Mapping[str, str] = LOCALE_ALIASES,
+) -> str | None:
"""Find the best match between available and requested locale strings.
>>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
def parse_locale(
identifier: str,
sep: str = '_',
-) -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]:
+) -> (
+ tuple[str, str | None, str | None, str | None]
+ | tuple[str, str | None, str | None, str | None, str | None]
+):
"""Parse a locale identifier into a tuple of the form ``(language,
territory, script, variant, modifier)``.
territory = parts.pop(0)
if parts and (
- len(parts[0]) == 4 and parts[0][0].isdigit() or
- len(parts[0]) >= 5 and parts[0][0].isalpha()
+ len(parts[0]) == 4
+ and parts[0][0].isdigit()
+ or len(parts[0]) >= 5
+ and parts[0][0].isalpha()
):
variant = parts.pop().upper()
"""
- babel.dates
- ~~~~~~~~~~~
+babel.dates
+~~~~~~~~~~~
- Locale dependent formatting and parsing of dates and times.
+Locale dependent formatting and parsing of dates and times.
- The default locale for the functions in this module is determined by the
- following environment variables, in that order:
+The default locale for the functions in this module is determined by the
+following environment variables, in that order:
- * ``LC_TIME``,
- * ``LC_ALL``, and
- * ``LANG``
+ * ``LC_TIME``,
+ * ``LC_ALL``, and
+ * ``LANG``
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
if TYPE_CHECKING:
from typing_extensions import TypeAlias
+
_Instant: TypeAlias = datetime.date | datetime.time | float | None
_PredefinedTimeFormat: TypeAlias = Literal['full', 'long', 'medium', 'short']
_Context: TypeAlias = Literal['format', 'stand-alone']
return dt.astimezone(tz)
-def _get_dt_and_tzinfo(dt_or_tzinfo: _DtOrTzinfo) -> tuple[datetime.datetime | None, datetime.tzinfo]:
+def _get_dt_and_tzinfo(
+ dt_or_tzinfo: _DtOrTzinfo,
+) -> tuple[datetime.datetime | None, datetime.tzinfo]:
"""
Parse a `dt_or_tzinfo` value into a datetime and a tzinfo.
return datetime.datetime.fromtimestamp(instant, UTC).replace(tzinfo=None)
elif isinstance(instant, datetime.time):
return datetime.datetime.combine(datetime.date.today(), instant)
- elif isinstance(instant, datetime.date) and not isinstance(instant, datetime.datetime):
+ elif isinstance(instant, datetime.date) and not isinstance(instant, datetime.datetime): # fmt: skip
return datetime.datetime.combine(instant, datetime.time())
# TODO (3.x): Add an assertion/type check for this fallthrough branch:
return instant
-def _ensure_datetime_tzinfo(dt: datetime.datetime, tzinfo: datetime.tzinfo | None = None) -> datetime.datetime:
+def _ensure_datetime_tzinfo(
+ dt: datetime.datetime,
+ tzinfo: datetime.tzinfo | None = None,
+) -> datetime.datetime:
"""
Ensure the datetime passed has an attached tzinfo.
if territory not in locale.territories:
territory = 'ZZ' # invalid/unknown
territory_name = locale.territories[territory]
- if not return_city and territory and len(get_global('territory_zones').get(territory, [])) == 1:
+ if (
+ not return_city
+ and territory
+ and len(get_global('territory_zones').get(territory, [])) == 1
+ ):
return region_format % territory_name
# Otherwise, include the city in the output
if return_city:
return city_name
- return region_format % (fallback_format % {
- '0': city_name,
- '1': territory_name,
- })
+ return region_format % (
+ fallback_format
+ % {
+ '0': city_name,
+ '1': territory_name,
+ }
+ )
def get_timezone_name(
locale = Locale.parse(locale or LC_TIME)
if format in ('full', 'long', 'medium', 'short'):
- return get_datetime_format(format, locale=locale) \
- .replace("'", "") \
- .replace('{0}', format_time(datetime, format, tzinfo=None,
- locale=locale)) \
+ return (
+ get_datetime_format(format, locale=locale)
+ .replace("'", "")
+ .replace('{0}', format_time(datetime, format, tzinfo=None, locale=locale))
.replace('{1}', format_date(datetime, format, locale=locale))
+ )
else:
return parse_pattern(format).apply(datetime, locale)
def format_timedelta(
delta: datetime.timedelta | int,
- granularity: Literal['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] = 'second',
- threshold: float = .85,
+ granularity: Literal[
+ 'year',
+ 'month',
+ 'week',
+ 'day',
+ 'hour',
+ 'minute',
+ 'second',
+ ] = 'second',
+ threshold: float = 0.85,
add_direction: bool = False,
format: Literal['narrow', 'short', 'medium', 'long'] = 'long',
locale: Locale | str | None = None,
raise TypeError('Format must be one of "narrow", "short" or "long"')
if format == 'medium':
warnings.warn(
- '"medium" value for format param of format_timedelta'
- ' is deprecated. Use "long" instead',
+ '"medium" value for format param of format_timedelta is deprecated. Use "long" instead',
category=DeprecationWarning,
stacklevel=2,
)
if add_direction:
# Try to find the length variant version first ("year-narrow")
# before falling back to the default.
- unit_rel_patterns = (date_fields.get(f"{a_unit}-{format}") or date_fields[a_unit])
+ unit_rel_patterns = date_fields.get(f"{a_unit}-{format}") or date_fields[a_unit]
if seconds >= 0:
yield unit_rel_patterns['future']
else:
return format(start)
return (
- locale.interval_formats.get(None, "{0}-{1}").
- replace("{0}", formatted_start).
- replace("{1}", formatted_end)
+ locale.interval_formats.get(None, "{0}-{1}")
+ .replace("{0}", formatted_start)
+ .replace("{1}", formatted_end)
)
# > format the start and end datetime, as above.
return "".join(
parse_pattern(pattern).apply(instant, locale)
- for pattern, instant
- in zip(skel_formats[field], (start, end))
+ for pattern, instant in zip(skel_formats[field], (start, end))
)
# > Otherwise, format the start and end datetime using the fallback pattern.
return rule_id
else:
# e.g. from="21:00" before="06:00"
- if rule["from"] <= seconds_past_midnight < 86400 or \
- 0 <= seconds_past_midnight < rule["before"]:
+ if (
+ rule["from"] <= seconds_past_midnight < 86400
+ or 0 <= seconds_past_midnight < rule["before"]
+ ):
return rule_id
start_ok = end_ok = False
class DateTimePattern:
-
def __init__(self, pattern: str, format: DateTimeFormat):
self.pattern = pattern
self.format = format
class DateTimeFormat:
-
def __init__(
self,
value: datetime.date | datetime.time,
elif char == 'a':
return int(self.value.hour >= 12) # 0 for am, 1 for pm
else:
- raise NotImplementedError(f"Not implemented: extracting {char!r} from {self.value!r}")
+ raise NotImplementedError(
+ f"Not implemented: extracting {char!r} from {self.value!r}",
+ )
def format_era(self, char: str, num: int) -> str:
width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
:param num: count of format character
"""
- widths = [{3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)],
- 'wide', 'narrow', 'abbreviated']
+ widths = [
+ {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)],
+ 'wide',
+ 'narrow',
+ 'abbreviated',
+ ]
if char == 'a':
period = 'pm' if self.value.hour >= 12 else 'am'
context = 'format'
return self.format(round(value, num) * 10**num, num)
def format_milliseconds_in_day(self, num):
- msecs = self.value.microsecond // 1000 + self.value.second * 1000 + \
- self.value.minute * 60000 + self.value.hour * 3600000
+ msecs = (
+ self.value.microsecond // 1000
+ + self.value.second * 1000
+ + self.value.minute * 60000
+ + self.value.hour * 3600000
+ )
return self.format(msecs, num)
def format_timezone(self, char: str, num: int) -> str:
week = self.get_week_number(day_of_year)
if week == 0:
date = datetime.date(self.value.year - 1, 12, 31)
- week = self.get_week_number(self.get_day_of_year(date),
- date.weekday())
+ week = self.get_week_number(self.get_day_of_year(date), date.weekday())
elif week > 52:
weekday = datetime.date(self.value.year + 1, 1, 1).weekday()
- if self.get_week_number(1, weekday) == 1 and \
- 32 - (weekday - self.locale.first_week_day) % 7 <= self.value.day:
+ if (
+ self.get_week_number(1, weekday) == 1
+ and 32 - (weekday - self.locale.first_week_day) % 7 <= self.value.day
+ ):
week = 1
return week
"""
if day_of_week is None:
day_of_week = self.value.weekday()
- first_day = (day_of_week - self.locale.first_week_day -
- day_of_period + 1) % 7
+ first_day = (day_of_week - self.locale.first_week_day - day_of_period + 1) % 7
if first_day < 0:
first_day += 7
week_number = (day_of_period + first_day - 1) // 7
return [untokenize_pattern(tokens) for tokens in parts]
-def match_skeleton(skeleton: str, options: Iterable[str], allow_different_fields: bool = False) -> str | None:
+def match_skeleton(
+ skeleton: str,
+ options: Iterable[str],
+ allow_different_fields: bool = False,
+) -> str | None:
"""
Find the closest match for the given datetime skeleton among the options given.
from babel.core import get_global
-def get_official_languages(territory: str, regional: bool = False, de_facto: bool = False) -> tuple[str, ...]:
+def get_official_languages(
+ territory: str,
+ regional: bool = False,
+ de_facto: bool = False,
+) -> tuple[str, ...]:
"""
Get the official language(s) for the given territory.
return tuple(lang for _, lang in pairs)
-def get_territory_language_info(territory: str) -> dict[str, dict[str, float | str | None]]:
+def get_territory_language_info(
+ territory: str,
+) -> dict[str, dict[str, float | str | None]]:
"""
Get a dictionary of language information for a territory.
"""
- babel.lists
- ~~~~~~~~~~~
+babel.lists
+~~~~~~~~~~~
- Locale dependent formatting of lists.
+Locale dependent formatting of lists.
- The default locale for the functions in this module is determined by the
- following environment variables, in that order:
+The default locale for the functions in this module is determined by the
+following environment variables, in that order:
- * ``LC_ALL``, and
- * ``LANG``
+ * ``LC_ALL``, and
+ * ``LANG``
- :copyright: (c) 2015-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2015-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import warnings
def format_list(
lst: Sequence[str],
- style: Literal['standard', 'standard-short', 'or', 'or-short', 'unit', 'unit-short', 'unit-narrow'] = 'standard',
+ style: Literal[
+ 'standard',
+ 'standard-short',
+ 'or',
+ 'or-short',
+ 'unit',
+ 'unit-short',
+ 'unit-narrow',
+ ] = 'standard',
locale: Locale | str | None = None,
) -> str:
"""
"""
- babel.localedata
- ~~~~~~~~~~~~~~~~
+babel.localedata
+~~~~~~~~~~~~~~~~
- Low-level locale data access.
+Low-level locale data access.
- :note: The `Locale` class, which uses this module under the hood, provides a
- more convenient interface for accessing the locale data.
+:note: The `Locale` class, which uses this module under the hood, provides a
+ more convenient interface for accessing the locale data.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
"""
return [
stem
- for stem, extension in
- (os.path.splitext(filename) for filename in os.listdir(_dirname))
+ for stem, extension in (
+ os.path.splitext(filename) for filename in os.listdir(_dirname)
+ )
if extension == '.dat' and stem != 'root'
]
data = {}
else:
from babel.core import get_global
+
parent = get_global('parent_exceptions').get(name)
if not parent:
if _is_non_likely_script(name):
values.
"""
- def __init__(self, data: MutableMapping[str | int | None, Any], base: Mapping[str | int | None, Any] | None = None):
+ def __init__(
+ self,
+ data: MutableMapping[str | int | None, Any],
+ base: Mapping[str | int | None, Any] | None = None,
+ ):
self._data = data
if base is None:
base = data
"""
- babel.localtime
- ~~~~~~~~~~~~~~~
+babel.localtime
+~~~~~~~~~~~~~~~
- Babel specific fork of tzlocal to determine the local timezone
- of the system.
+Babel specific fork of tzlocal to determine the local timezone
+of the system.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
import datetime
"""
- babel.localtime._fallback
- ~~~~~~~~~~~~~~~~~~~~~~~~~
+babel.localtime._fallback
+~~~~~~~~~~~~~~~~~~~~~~~~~
- Emulated fallback local timezone when all else fails.
+Emulated fallback local timezone when all else fails.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
import datetime
class _FallbackLocalTimezone(datetime.tzinfo):
-
def utcoffset(self, dt: datetime.datetime) -> datetime.timedelta:
if self._isdst(dt):
return DSTOFFSET
# `None` (as a fix for #1092).
# Instead, let's just "fix" the double slash symlink by stripping
# leading slashes before passing the assumed zone name forward.
- zone_name = link_dst[pos + 10:].lstrip("/")
+ zone_name = link_dst[pos + 10 :].lstrip("/")
tzinfo = _get_tzinfo(zone_name)
if tzinfo is not None:
return tzinfo
def _get_localzone() -> datetime.tzinfo:
if winreg is None:
- raise LookupError(
- 'Runtime support not available')
+ raise LookupError('Runtime support not available')
return _get_tzinfo_or_raise(get_localzone_name())
"""
- babel.messages
- ~~~~~~~~~~~~~~
+babel.messages
+~~~~~~~~~~~~~~
- Support for ``gettext`` message catalogs.
+Support for ``gettext`` message catalogs.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
from babel.messages.catalog import (
"""
- babel.messages.catalog
- ~~~~~~~~~~~~~~~~~~~~~~
+babel.messages.catalog
+~~~~~~~~~~~~~~~~~~~~~~
- Data structures for message catalogs.
+Data structures for message catalogs.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import datetime
s.set_seq2(word)
for x in possibilities:
s.set_seq1(x)
- if s.real_quick_ratio() >= cutoff and \
- s.quick_ratio() >= cutoff and \
- s.ratio() >= cutoff:
+ if (
+ s.real_quick_ratio() >= cutoff
+ and s.quick_ratio() >= cutoff
+ and s.ratio() >= cutoff
+ ):
result.append((s.ratio(), x))
# Move the best scorers to head of list
return [x for score, x in result]
-PYTHON_FORMAT = re.compile(r'''
+PYTHON_FORMAT = re.compile(
+ r'''
\%
(?:\(([\w]*)\))?
(
[hlL]?
)
([diouxXeEfFgGcrs%])
-''', re.VERBOSE)
+''',
+ re.VERBOSE,
+)
def _has_python_brace_format(string: str) -> bool:
def __cmp__(self, other: object) -> int:
"""Compare Messages, taking into account plural ids"""
+
def values_to_compare(obj):
if isinstance(obj, Message) and obj.pluralizable:
return obj.id[0], obj.context or ''
return obj.id, obj.context or ''
+
return _cmp(values_to_compare(self), values_to_compare(other))
def __gt__(self, other: object) -> bool:
in a catalog.
"""
from babel.messages.checkers import checkers
+
errors: list[TranslationError] = []
for checker in checkers:
try:
def parse_separated_header(value: str) -> dict[str, str]:
# Adapted from https://peps.python.org/pep-0594/#cgi
from email.message import Message
+
m = Message()
m['content-type'] = value
return dict(m.get_params())
self._locale = None
return
- raise TypeError(f"`locale` must be a Locale, a locale identifier string, or None; got {locale!r}")
+ raise TypeError(
+ f"`locale` must be a Locale, a locale identifier string, or None; got {locale!r}",
+ )
def _get_locale(self) -> Locale | None:
return self._locale
year = datetime.datetime.now(LOCALTZ).strftime('%Y')
if hasattr(self.revision_date, 'strftime'):
year = self.revision_date.strftime('%Y')
- comment = comment.replace('PROJECT', self.project) \
- .replace('VERSION', self.version) \
- .replace('YEAR', year) \
- .replace('ORGANIZATION', self.copyright_holder)
- locale_name = (self.locale.english_name if self.locale else self.locale_identifier)
+ comment = (
+ comment.replace('PROJECT', self.project)
+ .replace('VERSION', self.version)
+ .replace('YEAR', year)
+ .replace('ORGANIZATION', self.copyright_holder)
+ )
+ locale_name = self.locale.english_name if self.locale else self.locale_identifier
if locale_name:
comment = comment.replace("Translations template", f"{locale_name} translations")
return comment
def _set_header_comment(self, string: str | None) -> None:
self._header_comment = string
- header_comment = property(_get_header_comment, _set_header_comment, doc="""\
+ header_comment = property(
+ _get_header_comment,
+ _set_header_comment,
+ doc="""\
The header comment for the catalog.
>>> catalog = Catalog(project='Foobar', version='1.0',
#
:type: `unicode`
- """)
+ """,
+ )
def _get_mime_headers(self) -> list[tuple[str, str]]:
if isinstance(self.revision_date, (datetime.datetime, datetime.time, int, float)):
- 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:
revision_date = self.revision_date
if 'YEAR' not in value:
self.revision_date = _parse_datetime_header(value)
- mime_headers = property(_get_mime_headers, _set_mime_headers, doc="""\
+ mime_headers = property(
+ _get_mime_headers,
+ _set_mime_headers,
+ doc="""\
The MIME headers of the catalog, used for the special ``msgid ""`` entry.
The behavior of this property changes slightly depending on whether a locale
Generated-By: Babel ...
:type: `list`
- """)
+ """,
+ )
@property
def num_plurals(self) -> int:
self.fuzzy = message.fuzzy
else:
if isinstance(id, (list, tuple)):
- assert isinstance(message.string, (list, tuple)), \
+ assert isinstance(message.string, (list, tuple)), (
f"Expected sequence but got {type(message.string)}"
+ )
self._messages[key] = message
def add(
PO file, if any
:param context: the message context
"""
- message = Message(id, string, list(locations), flags, auto_comments,
- user_comments, previous_id, lineno=lineno,
- context=context)
+ message = Message(
+ id,
+ string,
+ list(locations),
+ flags,
+ auto_comments,
+ user_comments,
+ previous_id,
+ lineno=lineno,
+ context=context,
+ )
self[id] = message
return message
fuzzy_candidates[self._to_fuzzy_match_key(key)] = (key, ctxt)
fuzzy_matches = set()
- def _merge(message: Message, oldkey: tuple[str, str] | str, newkey: tuple[str, str] | str) -> None:
+ def _merge(
+ message: Message,
+ oldkey: tuple[str, str] | str,
+ newkey: tuple[str, str] | str,
+ ) -> None:
message = message.clone()
fuzzy = False
if oldkey != newkey:
)
elif len(message.string) != self.num_plurals:
fuzzy = True
- message.string = tuple(message.string[:len(oldmsg.string)])
+ message.string = tuple(message.string[: len(oldmsg.string)])
elif isinstance(message.string, (list, tuple)):
fuzzy = True
message.string = message.string[0]
matchkey = key
return matchkey.lower().strip()
- def _key_for(self, id: _MessageID, context: str | None = None) -> tuple[str, str] | str:
+ def _key_for(
+ self,
+ id: _MessageID,
+ context: str | None = None,
+ ) -> tuple[str, str] | str:
"""The key for a message is just the singular ID even for pluralizable
messages, but is a ``(msgid, msgctxt)`` tuple for context-specific
messages.
for key in self._messages.keys() | other._messages.keys():
message_1 = self.get(key)
message_2 = other.get(key)
- if (
- message_1 is None
- or message_2 is None
- or not message_1.is_identical(message_2)
- ):
+ if message_1 is None or message_2 is None or not message_1.is_identical(message_2):
return False
return dict(self.mime_headers) == dict(other.mime_headers)
"""
- babel.messages.checkers
- ~~~~~~~~~~~~~~~~~~~~~~~
+babel.messages.checkers
+~~~~~~~~~~~~~~~~~~~~~~~
- Various routines that help with validation of translations.
+Various routines that help with validation of translations.
- :since: version 0.9
+:since: version 0.9
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
from collections.abc import Callable
positional = name is None
else:
if (name is None) != positional:
- raise TranslationError('format string mixes positional '
- 'and named placeholders')
+ raise TranslationError(
+ 'format string mixes positional and named placeholders',
+ )
return bool(positional)
a = _parse(format)
def _find_checkers() -> list[Callable[[Catalog | None, Message], object]]:
from babel.messages._compat import find_entrypoints
+
checkers: list[Callable[[Catalog | None, Message], object]] = []
checkers.extend(load() for (name, load) in find_entrypoints('babel.checkers'))
if len(checkers) == 0:
"""
- babel.messages.extract
- ~~~~~~~~~~~~~~~~~~~~~~
+babel.messages.extract
+~~~~~~~~~~~~~~~~~~~~~~
- Basic infrastructure for extracting localizable messages from source files.
+Basic infrastructure for extracting localizable messages from source files.
- This module defines an extensible system for collecting localizable message
- strings from a variety of sources. A native extractor for Python source
- files is builtin, extractors for other sources can be added using very
- simple plugins.
+This module defines an extensible system for collecting localizable message
+strings from a variety of sources. A native extractor for Python source
+files is builtin, extractors for other sources can be added using very
+simple plugins.
- The main entry points into the extraction functionality are the functions
- `extract_from_dir` and `extract_from_file`.
+The main entry points into the extraction functionality are the functions
+`extract_from_dir` and `extract_from_file`.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import ast
"""Helper function for `extract` that strips comment tags from strings
in a list of comment lines. This functions operates in-place.
"""
+
def _strip(line: str):
for tag in tags:
if line.startswith(tag):
- return line[len(tag):].strip()
+ return line[len(tag) :].strip()
return line
+
comments[:] = [_strip(c) for c in comments]
absname = os.path.abspath(dirname)
for root, dirnames, filenames in os.walk(absname):
dirnames[:] = [
- subdir for subdir in dirnames
- if directory_filter(os.path.join(root, subdir))
+ subdir for subdir in dirnames if directory_filter(os.path.join(root, subdir))
]
dirnames.sort()
filenames.sort()
if callback:
callback(filename, method, options)
for message_tuple in extract_from_file(
- method, filepath,
+ method,
+ filepath,
keywords=keywords,
comment_tags=comment_tags,
options=options,
return []
with open(filename, 'rb') as fileobj:
- return list(extract(method, fileobj, keywords, comment_tags,
- options, strip_comment_tags))
+ return list(
+ extract(method, fileobj, keywords, comment_tags, options, strip_comment_tags),
+ )
def _match_messages_against_spec(
first_msg_index = spec[0] - 1
# An empty string msgid isn't valid, emit a warning
if not messages[first_msg_index]:
- filename = (getattr(fileobj, "name", None) or "(unknown)")
+ filename = getattr(fileobj, "name", None) or "(unknown)"
sys.stderr.write(
f"{filename}:{lineno}: warning: Empty msgid. It is reserved by GNU gettext: gettext(\"\") "
f"returns the header entry with meta information, not the empty string.\n",
elif ':' in method or '.' in method:
if ':' not in method:
lastdot = method.rfind('.')
- module, attrname = method[:lastdot], method[lastdot + 1:]
+ module, attrname = method[:lastdot], method[lastdot + 1 :]
else:
module, attrname = method.split(':', 1)
func = getattr(__import__(module, {}, {}, [attrname]), attrname)
if func is None:
raise ValueError(f"Unknown extraction method {method!r}")
- results = func(fileobj, keywords.keys(), comment_tags,
- options=options or {})
+ results = func(fileobj, keywords.keys(), comment_tags, options=options or {})
for lineno, funcname, messages, comments in results:
if not isinstance(messages, (list, tuple)):
elif not call_stack and tok == COMMENT:
# Strip the comment token from the line
value = value[1:].strip()
- if in_translator_comments and \
- translator_comments[-1][0] == lineno - 1:
+ if in_translator_comments and translator_comments[-1][0] == lineno - 1:
# We're already inside a translator comment, continue appending
translator_comments.append((lineno, value))
continue
translator_comments.append((lineno, value))
break
elif funcname and len(call_stack) == 1:
- nested = (tok == NAME and value in keywords)
+ nested = tok == NAME and value in keywords
if (tok == OP and value == ')') or nested:
if buf:
messages.append(''.join(buf))
# to start this message's translation call is.
translator_comments.clear()
- yield (message_lineno, funcname, messages,
- [comment[1] for comment in translator_comments])
+ yield (
+ message_lineno,
+ funcname,
+ messages,
+ [comment[1] for comment in translator_comments],
+ )
funcname = lineno = message_lineno = None
call_stack.clear()
:param lineno: line number offset (for parsing embedded fragments)
"""
from babel.messages.jslexer import Token, tokenize, unquote_string
+
funcname = message_lineno = None
messages = []
last_argument = None
call_stack = 0
token = Token('operator', ')', token.lineno)
- if options.get('parse_template_string') and not funcname and token.type == 'template_string':
- yield from parse_template_string(token.value, keywords, comment_tags, options, token.lineno)
+ if (
+ options.get('parse_template_string')
+ and not funcname
+ and token.type == 'template_string'
+ ):
+ yield from parse_template_string(
+ token.value,
+ keywords,
+ comment_tags,
+ options,
+ token.lineno,
+ )
elif token.type == 'operator' and token.value == '(':
if funcname:
elif call_stack == -1 and token.type == 'linecomment':
value = token.value[2:].strip()
- if translator_comments and \
- translator_comments[-1][0] == token.lineno - 1:
+ if translator_comments and translator_comments[-1][0] == token.lineno - 1:
translator_comments.append((token.lineno, value))
continue
lines[0] = lines[0].strip()
lines[1:] = dedent('\n'.join(lines[1:])).splitlines()
for offset, line in enumerate(lines):
- translator_comments.append((token.lineno + offset,
- line))
+ translator_comments.append((token.lineno + offset, line))
break
elif funcname and call_stack == 0:
# Comments don't apply unless they immediately precede the
# message
- if translator_comments and \
- translator_comments[-1][0] < message_lineno - 1:
+ if translator_comments and translator_comments[-1][0] < message_lineno - 1:
translator_comments = []
if messages is not None:
- yield (message_lineno, funcname, messages,
- [comment[1] for comment in translator_comments])
+ yield (
+ message_lineno,
+ funcname,
+ messages,
+ [comment[1] for comment in translator_comments],
+ )
funcname = message_lineno = last_argument = None
concatenate_next = False
elif token.value == '+':
concatenate_next = True
- elif call_stack > 0 and token.type == 'operator' \
- and token.value == ')':
+ elif call_stack > 0 and token.type == 'operator' and token.value == ')':
call_stack -= 1
elif funcname and call_stack == -1:
funcname = None
- elif call_stack == -1 and token.type == 'name' and \
- token.value in keywords and \
- (last_token is None or last_token.type != 'name' or
- last_token.value != 'function'):
+ elif (
+ call_stack == -1
+ and token.type == 'name'
+ and token.value in keywords
+ and (
+ last_token is None
+ or last_token.type != 'name'
+ or last_token.value != 'function'
+ )
+ ):
funcname = token.value
last_token = token
:param lineno: starting line number (optional)
"""
from babel.messages.jslexer import line_re
+
prev_character = None
level = 0
inside_str = False
if level == 0 and expression_contents:
expression_contents = expression_contents[0:-1]
fake_file_obj = io.BytesIO(expression_contents.encode())
- yield from extract_javascript(fake_file_obj, keywords, comment_tags, options, lineno)
+ yield from extract_javascript(
+ fake_file_obj,
+ keywords,
+ comment_tags,
+ options,
+ lineno,
+ )
lineno += len(line_re.findall(expression_contents))
expression_contents = ''
prev_character = character
"""
- babel.messages.frontend
- ~~~~~~~~~~~~~~~~~~~~~~~
+babel.messages.frontend
+~~~~~~~~~~~~~~~~~~~~~~~
- Frontends for the message extraction functionality.
+Frontends for the message extraction functionality.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
from __future__ import annotations
n_errors += len(errors)
if n_errors:
self.log.error('%d errors encountered.', n_errors)
- return (1 if n_errors else 0)
+ return 1 if n_errors else 0
def _get_po_mo_triples(self, domain: str):
if not self.input_file:
yield self.locale, po_file, po_file.with_suffix(".mo")
else:
for locale_path in dir_path.iterdir():
- po_file = locale_path / "LC_MESSAGES"/ f"{domain}.po"
+ po_file = locale_path / "LC_MESSAGES" / f"{domain}.po"
if po_file.exists():
yield locale_path.name, po_file, po_file.with_suffix(".mo")
else:
if self.output_file:
mo_file = pathlib.Path(self.output_file)
else:
- mo_file = pathlib.Path(self.directory) / self.locale / "LC_MESSAGES" / f"{domain}.mo"
+ mo_file = (
+ pathlib.Path(self.directory) / self.locale / "LC_MESSAGES" / f"{domain}.mo"
+ )
yield self.locale, po_file, mo_file
def _run_domain(self, domain):
percentage = translated * 100 // len(catalog)
self.log.info(
'%d of %d messages (%d%%) translated in %s',
- translated, len(catalog), percentage, po_file,
+ translated,
+ len(catalog),
+ percentage,
+ po_file,
)
if catalog.fuzzy and not self.use_fuzzy:
catalogs_and_errors[catalog] = catalog_errors = list(catalog.check())
for message, errors in catalog_errors:
for error in errors:
- self.log.error(
- 'error: %s:%d: %s', po_file, message.lineno, error,
- )
+ self.log.error('error: %s:%d: %s', po_file, message.lineno, error)
self.log.info('compiling catalog %s to %s', po_file, mo_file)
def cli_directory_filter(dirname):
basename = os.path.basename(dirname)
return not any(
- fnmatch.fnmatch(basename, ignore_pattern)
- for ignore_pattern
- in ignore_patterns
+ fnmatch.fnmatch(basename, ignore_pattern) for ignore_pattern in ignore_patterns
)
return cli_directory_filter
'set the name and email of the last translator in output'),
] # fmt: skip
boolean_options = [
- 'no-default-keywords', 'no-location', 'omit-header', 'no-wrap',
- 'sort-output', 'sort-by-file', 'strip-comments',
+ 'no-default-keywords',
+ 'no-location',
+ 'omit-header',
+ 'no-wrap',
+ 'sort-output',
+ 'sort-by-file',
+ 'strip-comments',
]
as_args = 'input-paths'
multiple_value_options = (
def run(self):
mappings = self._get_mappings()
with open(self.output_file, 'wb') as outfile:
- catalog = Catalog(project=self.project,
- version=self.version,
- msgid_bugs_address=self.msgid_bugs_address,
- copyright_holder=self.copyright_holder,
- charset=self.charset,
- header_comment=(self.header_comment or DEFAULT_HEADER),
- last_translator=self.last_translator)
+ catalog = Catalog(
+ project=self.project,
+ version=self.version,
+ msgid_bugs_address=self.msgid_bugs_address,
+ copyright_holder=self.copyright_holder,
+ charset=self.charset,
+ header_comment=(self.header_comment or DEFAULT_HEADER),
+ last_translator=self.last_translator,
+ )
for path, method_map, options_map in mappings:
callback = self._build_callback(path)
else:
filepath = os.path.normpath(os.path.join(path, filename))
- catalog.add(message, None, [(filepath, lineno)],
- auto_comments=comments, context=context)
+ catalog.add(
+ message,
+ None,
+ [(filepath, lineno)],
+ auto_comments=comments,
+ context=context,
+ )
self.log.info('writing PO template file to %s', self.output_file)
write_po(
)
else:
with open(self.mapping_file) as fileobj:
- method_map, options_map = parse_mapping_cfg(fileobj, filename=self.mapping_file)
+ method_map, options_map = parse_mapping_cfg(
+ fileobj,
+ filename=self.mapping_file,
+ )
for path in self.input_paths:
mappings.append((path, method_map, options_map))
def run(self):
self.log.info(
- 'creating catalog %s based on %s', self.output_file, self.input_file,
+ 'creating catalog %s based on %s',
+ self.output_file,
+ self.input_file,
)
with open(self.input_file, 'rb') as infile:
'ignore changes to POT-Creation-Date when updating or checking'),
] # fmt: skip
boolean_options = [
- 'omit-header', 'no-wrap', 'ignore-obsolete', 'init-missing',
- 'no-fuzzy-matching', 'previous', 'update-header-comment',
- 'check', 'ignore-pot-creation-date',
+ 'omit-header',
+ 'no-wrap',
+ 'ignore-obsolete',
+ 'init-missing',
+ 'no-fuzzy-matching',
+ 'previous',
+ 'update-header-comment',
+ 'check',
+ 'ignore-pot-creation-date',
]
def initialize_options(self):
if self.init_missing:
if not self.locale:
raise OptionError(
- 'you must specify the locale for '
- 'the init-missing option to work',
+ 'you must specify the locale for the init-missing option to work',
)
try:
check_status[filename] = False
continue
self.log.info(
- 'creating catalog %s based on %s', filename, self.input_file,
+ 'creating catalog %s based on %s',
+ filename,
+ self.input_file,
)
with open(self.input_file, 'rb') as infile:
update_creation_date=not self.ignore_pot_creation_date,
)
- tmpname = os.path.join(os.path.dirname(filename),
- tempfile.gettempprefix() +
- os.path.basename(filename))
+ tmpname = os.path.join(
+ os.path.dirname(filename),
+ tempfile.gettempprefix() + os.path.basename(filename),
+ )
try:
with open(tmpname, 'wb') as tmpfile:
write_po(
if argv is None:
argv = sys.argv
- self.parser = optparse.OptionParser(usage=self.usage % ('command', '[args]'),
- version=self.version)
+ self.parser = optparse.OptionParser(
+ usage=self.usage % ('command', '[args]'),
+ version=self.version,
+ )
self.parser.disable_interspersed_args()
self.parser.print_help = self._help
self.parser.add_option(
extractors_read = config.get("extractors", {})
if not isinstance(extractors_read, dict):
- raise ConfigurationError(f"{filename}: extractors: Expected a dictionary, got {type(extractors_read)!r}")
+ raise ConfigurationError(
+ f"{filename}: extractors: Expected a dictionary, got {type(extractors_read)!r}",
+ )
for method, callable_spec in extractors_read.items():
if not isinstance(method, str):
# Impossible via TOML, but could happen with a custom object.
- raise ConfigurationError(f"{filename}: extractors: Extraction method must be a string, got {method!r}")
+ raise ConfigurationError(
+ f"{filename}: extractors: Extraction method must be a string, got {method!r}",
+ )
if not isinstance(callable_spec, str):
- raise ConfigurationError(f"{filename}: extractors: Callable specification must be a string, got {callable_spec!r}")
+ raise ConfigurationError(
+ f"{filename}: extractors: Callable specification must be a string, got {callable_spec!r}",
+ )
extractors[method] = callable_spec
if "mapping" in config:
- raise ConfigurationError(f"{filename}: 'mapping' is not a valid key, did you mean 'mappings'?")
+ raise ConfigurationError(
+ f"{filename}: 'mapping' is not a valid key, did you mean 'mappings'?",
+ )
mappings_read = config.get("mappings", [])
if not isinstance(mappings_read, list):
- raise ConfigurationError(f"{filename}: mappings: Expected a list, got {type(mappings_read)!r}")
+ raise ConfigurationError(
+ f"{filename}: mappings: Expected a list, got {type(mappings_read)!r}",
+ )
for idx, entry in enumerate(mappings_read):
if not isinstance(entry, dict):
- raise ConfigurationError(f"{filename}: mappings[{idx}]: Expected a dictionary, got {type(entry)!r}")
+ raise ConfigurationError(
+ f"{filename}: mappings[{idx}]: Expected a dictionary, got {type(entry)!r}",
+ )
entry = entry.copy()
method = entry.pop("method", None)
if not isinstance(method, str):
- raise ConfigurationError(f"{filename}: mappings[{idx}]: 'method' must be a string, got {method!r}")
+ raise ConfigurationError(
+ f"{filename}: mappings[{idx}]: 'method' must be a string, got {method!r}",
+ )
method = extractors.get(method, method) # Map the extractor name to the callable now
pattern = entry.pop("pattern", None)
if not isinstance(pattern, (list, str)):
- raise ConfigurationError(f"{filename}: mappings[{idx}]: 'pattern' must be a list or a string, got {pattern!r}")
+ raise ConfigurationError(
+ f"{filename}: mappings[{idx}]: 'pattern' must be a list or a string, got {pattern!r}",
+ )
if not isinstance(pattern, list):
pattern = [pattern]
for pat in pattern:
if not isinstance(pat, str):
- raise ConfigurationError(f"{filename}: mappings[{idx}]: 'pattern' elements must be strings, got {pat!r}")
+ raise ConfigurationError(
+ f"{filename}: mappings[{idx}]: 'pattern' elements must be strings, got {pat!r}",
+ )
method_map.append((pat, method))
options_map[pat] = entry
try:
babel_data = parsed_data["tool"]["babel"]
except (TypeError, KeyError) as e:
- raise ConfigurationError(f"{filename}: No 'tool.babel' section found in file") from e
+ raise ConfigurationError(
+ f"{filename}: No 'tool.babel' section found in file",
+ ) from e
elif style == "standalone":
babel_data = parsed_data
if "babel" in babel_data:
- raise ConfigurationError(f"{filename}: 'babel' should not be present in a stand-alone configuration file")
+ raise ConfigurationError(
+ f"{filename}: 'babel' should not be present in a stand-alone configuration file",
+ )
else: # pragma: no cover
raise ValueError(f"Unknown TOML style {style!r}")
def __getattr__(name: str):
# Re-exports for backwards compatibility;
# `setuptools_frontend` is the canonical import location.
- if name in {'check_message_extractors', 'compile_catalog', 'extract_messages', 'init_catalog', 'update_catalog'}:
+ if name in {
+ 'check_message_extractors',
+ 'compile_catalog',
+ 'extract_messages',
+ 'init_catalog',
+ 'update_catalog',
+ }:
from babel.messages import setuptools_frontend
return getattr(setuptools_frontend, name)
"""
- babel.messages.jslexer
- ~~~~~~~~~~~~~~~~~~~~~~
+babel.messages.jslexer
+~~~~~~~~~~~~~~~~~~~~~~
- A simple JavaScript 1.5 lexer which is used for the JavaScript
- extractor.
+A simple JavaScript 1.5 lexer which is used for the JavaScript
+extractor.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import re
] # fmt: skip
-def get_rules(jsx: bool, dotted: bool, template_string: bool) -> list[tuple[str | None, re.Pattern[str]]]:
+def get_rules(
+ jsx: bool,
+ dotted: bool,
+ template_string: bool,
+) -> list[tuple[str | None, re.Pattern[str]]]:
"""
Get a tokenization rule list given the passed syntax options.
"""Unquote a string with JavaScript rules. The string has to start with
string delimiters (``'``, ``"`` or the back-tick/grave accent (for template strings).)
"""
- assert string and string[0] == string[-1] and string[0] in '"\'`', \
+ assert string and string[0] == string[-1] and string[0] in '"\'`', (
'string provided is not properly delimited'
+ )
string = line_join_re.sub('\\1', string[1:-1])
result: list[str] = []
add = result.append
return ''.join(result)
-def tokenize(source: str, jsx: bool = True, dotted: bool = True, template_string: bool = True, lineno: int = 1) -> Generator[Token, None, None]:
+def tokenize(
+ source: str,
+ jsx: bool = True,
+ dotted: bool = True,
+ template_string: bool = True,
+ lineno: int = 1,
+) -> Generator[Token, None, None]:
"""
Tokenize JavaScript/JSX source. Returns a generator of tokens.
"""
- babel.messages.mofile
- ~~~~~~~~~~~~~~~~~~~~~
+babel.messages.mofile
+~~~~~~~~~~~~~~~~~~~~~
- Writing of files in the ``gettext`` MO (machine object) format.
+Writing of files in the ``gettext`` MO (machine object) format.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import array
if TYPE_CHECKING:
from _typeshed import SupportsRead, SupportsWrite
-LE_MAGIC: int = 0x950412de
-BE_MAGIC: int = 0xde120495
+LE_MAGIC: int = 0x950412DE
+BE_MAGIC: int = 0xDE120495
def read_mo(fileobj: SupportsRead[bytes]) -> Catalog:
# Now put all messages from the .mo file buffer into the catalog
# dictionary
for _i in range(msgcount):
- mlen, moff = unpack(ii, buf[origidx:origidx + 8])
+ mlen, moff = unpack(ii, buf[origidx : origidx + 8])
mend = moff + mlen
- tlen, toff = unpack(ii, buf[transidx:transidx + 8])
+ tlen, toff = unpack(ii, buf[transidx : transidx + 8])
tend = toff + tlen
if mend < buflen and tend < buflen:
msg = buf[moff:mend]
in the output
"""
messages = list(catalog)
- messages[1:] = [m for m in messages[1:]
- if m.string and (use_fuzzy or not m.fuzzy)]
+ messages[1:] = [m for m in messages[1:] if m.string and (use_fuzzy or not m.fuzzy)]
messages.sort()
ids = strs = b''
"""
- babel.messages.plurals
- ~~~~~~~~~~~~~~~~~~~~~~
+babel.messages.plurals
+~~~~~~~~~~~~~~~~~~~~~~
- Plural form definitions.
+Plural form definitions.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
from babel.core import Locale, default_locale
"""
- babel.messages.pofile
- ~~~~~~~~~~~~~~~~~~~~~
+babel.messages.pofile
+~~~~~~~~~~~~~~~~~~~~~
- Reading and writing of files in the ``gettext`` PO (portable object)
- format.
+Reading and writing of files in the ``gettext`` PO (portable object)
+format.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import os
:param string: the string to unescape
"""
+
def replace_escapes(match):
m = match.group(1)
if m == 'n':
for c in line:
if c == "\u2068":
if in_filename:
- raise ValueError("location comment contains more First Strong Isolate "
- "characters, than Pop Directional Isolate characters")
+ raise ValueError(
+ "location comment contains more First Strong Isolate "
+ "characters, than Pop Directional Isolate characters",
+ )
in_filename = True
continue
elif c == "\u2069":
if not in_filename:
- raise ValueError("location comment contains more Pop Directional Isolate "
- "characters, than First Strong Isolate characters")
+ raise ValueError(
+ "location comment contains more Pop Directional Isolate "
+ "characters, than First Strong Isolate characters",
+ )
in_filename = False
continue
elif c == " ":
else:
if location:
if in_filename:
- raise ValueError("location comment contains more First Strong Isolate "
- "characters, than Pop Directional Isolate characters")
+ raise ValueError(
+ "location comment contains more First Strong Isolate "
+ "characters, than Pop Directional Isolate characters",
+ )
locations.append(location)
return locations
See `read_po` for simple cases.
"""
- def __init__(self, catalog: Catalog, ignore_obsolete: bool = False, abort_invalid: bool = False) -> None:
+ def __init__(
+ self,
+ catalog: Catalog,
+ ignore_obsolete: bool = False,
+ abort_invalid: bool = False,
+ ) -> None:
self.catalog = catalog
self.ignore_obsolete = ignore_obsolete
self.counter = 0
string = ['' for _ in range(self.catalog.num_plurals)]
for idx, translation in sorted(self.translations):
if idx >= self.catalog.num_plurals:
- self._invalid_pofile("", self.offset, "msg has more translations than num_plurals of catalog")
+ self._invalid_pofile(
+ "",
+ self.offset,
+ "msg has more translations than num_plurals of catalog",
+ )
continue
string[idx] = translation.denormalize()
string = tuple(string)
msgid = self.messages[0].denormalize()
string = self.translations[0][1].denormalize()
msgctxt = self.context.denormalize() if self.context else None
- message = Message(msgid, string, self.locations, self.flags,
- self.auto_comments, self.user_comments, lineno=self.offset + 1,
- context=msgctxt)
+ message = Message(
+ msgid,
+ string,
+ self.locations,
+ self.flags,
+ self.auto_comments,
+ self.user_comments,
+ lineno=self.offset + 1,
+ context=msgctxt,
+ )
if self.obsolete:
if not self.ignore_obsolete:
self.catalog.obsolete[self.catalog._key_for(msgid, msgctxt)] = message
def _finish_current_message(self) -> None:
if self.messages:
if not self.translations:
- self._invalid_pofile("", self.offset, f"missing msgstr for msgid '{self.messages[0].denormalize()}'")
+ self._invalid_pofile(
+ "",
+ self.offset,
+ f"missing msgstr for msgid '{self.messages[0].denormalize()}'",
+ )
self.translations.append([0, _NormalizedString()])
self._add_message()
elif self.in_msgctxt:
s = self.context
else:
- self._invalid_pofile(line, lineno, "Got line starting with \" but not in msgid, msgstr or msgctxt")
+ self._invalid_pofile(
+ line,
+ lineno,
+ "Got line starting with \" but not in msgid, msgstr or msgctxt",
+ )
return
# For performance reasons, `NormalizedString` doesn't strip internally
s.append(line.strip())
"""
- babel.numbers
- ~~~~~~~~~~~~~
+babel.numbers
+~~~~~~~~~~~~~
- Locale dependent formatting and parsing of numeric data.
+Locale dependent formatting and parsing of numeric data.
- The default locale for the functions in this module is determined by the
- following environment variables, in that order:
+The default locale for the functions in this module is determined by the
+following environment variables, in that order:
- * ``LC_MONETARY`` for currency related functions,
- * ``LC_NUMERIC``, and
- * ``LC_ALL``, and
- * ``LANG``
+ * ``LC_MONETARY`` for currency related functions,
+ * ``LC_NUMERIC``, and
+ * ``LC_ALL``, and
+ * ``LANG``
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
# TODO:
# Padding and rounding increments in pattern:
# - https://www.unicode.org/reports/tr35/ (Appendix G.6)
class UnknownCurrencyError(Exception):
- """Exception thrown when a currency is requested for which no data is available.
- """
+ """Exception thrown when a currency is requested for which no data is available."""
def __init__(self, identifier: str) -> None:
"""Create the exception.
def list_currencies(locale: Locale | str | None = None) -> set[str]:
- """ Return a `set` of normalized currency codes.
+ """Return a `set` of normalized currency codes.
.. versionadded:: 2.5.0
def validate_currency(currency: str, locale: Locale | str | None = None) -> None:
- """ Check the currency code is recognized by Babel.
+ """Check the currency code is recognized by Babel.
Accepts a ``locale`` parameter for fined-grained validation, working as
the one defined above in ``list_currencies()`` method.
def is_currency(currency: str, locale: Locale | str | None = None) -> bool:
- """ Returns `True` only if a currency is recognized by Babel.
+ """Returns `True` only if a currency is recognized by Babel.
This method always return a Boolean and never raise.
"""
tender: bool = ...,
non_tender: bool = ...,
include_details: Literal[False] = ...,
-) -> list[str]:
- ... # pragma: no cover
+) -> list[str]: ... # pragma: no cover
@overload
tender: bool = ...,
non_tender: bool = ...,
include_details: Literal[True] = ...,
-) -> list[dict[str, Any]]:
- ... # pragma: no cover
+) -> list[dict[str, Any]]: ... # pragma: no cover
def get_territory_currencies(
# TODO: validate that the territory exists
def _is_active(start, end):
- return (start is None or start <= end_date) and \
- (end is None or end >= start_date)
+ return (start is None or start <= end_date) and (end is None or end >= start_date)
result = []
for currency_code, start, end, is_tender in curs:
start = datetime.date(*start)
if end:
end = datetime.date(*end)
- if ((is_tender and tender) or
- (not is_tender and non_tender)) and _is_active(start, end):
+ if ((is_tender and tender) or (not is_tender and non_tender)) and _is_active(
+ start,
+ end,
+ ):
if include_details:
- result.append({
- 'currency': currency_code,
- 'from': start,
- 'to': end,
- 'tender': is_tender,
- })
+ result.append(
+ {
+ 'currency': currency_code,
+ 'from': start,
+ 'to': end,
+ 'tender': is_tender,
+ },
+ )
else:
result.append(currency_code)
return result
-def _get_numbering_system(locale: Locale, numbering_system: Literal["default"] | str = "latn") -> str:
+def _get_numbering_system(
+ locale: Locale,
+ numbering_system: Literal["default"] | str = "latn",
+) -> str:
if numbering_system == "default":
return locale.default_numbering_system
else:
try:
return locale.number_symbols[numbering_system]
except KeyError as error:
- raise UnsupportedNumberingSystemError(f"Unknown numbering system {numbering_system} for Locale {locale}.") from error
+ raise UnsupportedNumberingSystemError(
+ f"Unknown numbering system {numbering_system} for Locale {locale}.",
+ ) from error
class UnsupportedNumberingSystemError(Exception):
"""Exception thrown when an unsupported numbering system is requested for the given Locale."""
+
pass
return _get_number_symbols(locale, numbering_system=numbering_system).get('infinity', '∞')
-def format_number(number: float | decimal.Decimal | str, locale: Locale | str | None = None) -> str:
+def format_number(
+ number: float | decimal.Decimal | str,
+ locale: Locale | str | None = None,
+) -> str:
"""Return the given number formatted for a specific locale.
>>> format_number(1099, locale='en_US') # doctest: +SKIP
"""
- warnings.warn('Use babel.numbers.format_decimal() instead.', DeprecationWarning, stacklevel=2)
+ warnings.warn(
+ 'Use babel.numbers.format_decimal() instead.',
+ DeprecationWarning,
+ stacklevel=2,
+ )
return format_decimal(number, locale=locale)
format = locale.decimal_formats[format]
pattern = parse_pattern(format)
return pattern.apply(
- number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)
+ number,
+ locale,
+ decimal_quantization=decimal_quantization,
+ group_separator=group_separator,
+ numbering_system=numbering_system,
+ )
def format_compact_decimal(
if format is None:
format = locale.decimal_formats[None]
pattern = parse_pattern(format)
- return pattern.apply(number, locale, decimal_quantization=False, numbering_system=numbering_system)
+ return pattern.apply(
+ number,
+ locale,
+ decimal_quantization=False,
+ numbering_system=numbering_system,
+ )
def _get_compact_format(
break
# otherwise, we need to divide the number by the magnitude but remove zeros
# equal to the number of 0's in the pattern minus 1
- number = cast(decimal.Decimal, number / (magnitude // (10 ** (pattern.count("0") - 1))))
+ number = cast(
+ decimal.Decimal,
+ number / (magnitude // (10 ** (pattern.count("0") - 1))),
+ )
# round to the number of fraction digits requested
rounded = round(number, fraction_digits)
# if the remaining number is singular, use the singular format
try:
pattern = locale.currency_formats[format_type]
except KeyError:
- raise UnknownCurrencyFormatError(f"{format_type!r} is not a known currency format type") from None
+ raise UnknownCurrencyFormatError(
+ f"{format_type!r} is not a known currency format type",
+ ) from None
return pattern.apply(
- number, locale, currency=currency, currency_digits=currency_digits,
- decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)
+ number,
+ locale,
+ currency=currency,
+ currency_digits=currency_digits,
+ decimal_quantization=decimal_quantization,
+ group_separator=group_separator,
+ numbering_system=numbering_system,
+ )
def _format_currency_long_name(
pattern = parse_pattern(format)
number_part = pattern.apply(
- number, locale, currency=currency, currency_digits=currency_digits,
- decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)
+ number,
+ locale,
+ currency=currency,
+ currency_digits=currency_digits,
+ decimal_quantization=decimal_quantization,
+ group_separator=group_separator,
+ numbering_system=numbering_system,
+ )
return unit_pattern.format(number_part, display_name)
try:
compact_format = locale.compact_currency_formats[format_type]
except KeyError as error:
- raise UnknownCurrencyFormatError(f"{format_type!r} is not a known compact currency format type") from error
+ raise UnknownCurrencyFormatError(
+ f"{format_type!r} is not a known compact currency format type",
+ ) from error
number, format = _get_compact_format(number, compact_format, locale, fraction_digits)
# Did not find a format, fall back.
if format is None or "¤" not in str(format):
if format is None:
raise ValueError('No compact currency format found for the given number and locale.')
pattern = parse_pattern(format)
- return pattern.apply(number, locale, currency=currency, currency_digits=False, decimal_quantization=False,
- numbering_system=numbering_system)
+ return pattern.apply(
+ number,
+ locale,
+ currency=currency,
+ currency_digits=False,
+ decimal_quantization=False,
+ numbering_system=numbering_system,
+ )
def format_percent(
format = locale.percent_formats[None]
pattern = parse_pattern(format)
return pattern.apply(
- number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator,
+ number,
+ locale,
+ decimal_quantization=decimal_quantization,
+ group_separator=group_separator,
numbering_system=numbering_system,
)
format = locale.scientific_formats[None]
pattern = parse_pattern(format)
return pattern.apply(
- number, locale, decimal_quantization=decimal_quantization, numbering_system=numbering_system)
+ number,
+ locale,
+ decimal_quantization=decimal_quantization,
+ numbering_system=numbering_system,
+ )
class NumberFormatError(ValueError):
string = SPACE_CHARS_RE.sub(group_symbol, string)
try:
- parsed = decimal.Decimal(string.replace(group_symbol, '')
- .replace(decimal_symbol, '.'))
+ parsed = decimal.Decimal(string.replace(group_symbol, '').replace(decimal_symbol, '.'))
except decimal.InvalidOperation as exc:
raise NumberFormatError(f"{string!r} is not a valid decimal number") from exc
if strict and group_symbol in string:
- proper = format_decimal(parsed, locale=locale, decimal_quantization=False, numbering_system=numbering_system)
- if string != proper and proper != _remove_trailing_zeros_after_decimal(string, decimal_symbol):
+ proper = format_decimal(
+ parsed,
+ locale=locale,
+ decimal_quantization=False,
+ numbering_system=numbering_system,
+ )
+ if string != proper and proper != _remove_trailing_zeros_after_decimal(string, decimal_symbol): # fmt: skip
try:
- parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '')
- .replace(group_symbol, '.'))
+ parsed_alt = decimal.Decimal(
+ string.replace(decimal_symbol, '').replace(group_symbol, '.'),
+ )
except decimal.InvalidOperation as exc:
raise NumberFormatError(
f"{string!r} is not a properly formatted decimal number. "
if g1 == -1:
return 1000, 1000
g1 = width - g1 - 1
- g2 = p[:-g1 - 1].rfind(',')
+ g2 = p[: -g1 - 1].rfind(',')
if g2 == -1:
return g1, g1
g2 = width - g1 - g2 - 2
exp_plus = None
exp_prec = None
grouping = parse_grouping(integer)
- return NumberPattern(pattern, (pos_prefix, neg_prefix),
- (pos_suffix, neg_suffix), grouping,
- int_prec, frac_prec,
- exp_prec, exp_plus, number)
+ return NumberPattern(
+ pattern,
+ (pos_prefix, neg_prefix),
+ (pos_suffix, neg_suffix),
+ grouping,
+ int_prec,
+ frac_prec,
+ exp_prec,
+ exp_plus,
+ number,
+ )
class NumberPattern:
-
def __init__(
self,
pattern: str,
*,
numbering_system: Literal["default"] | str = "latn",
) -> tuple[decimal.Decimal, int, str]:
- """ Returns normalized scientific notation components of a value.
- """
+ """Returns normalized scientific notation components of a value."""
# Normalize value to only have one lead digit.
exp = value.adjusted()
value = value * get_decimal_quantum(exp)
# Prepare scientific notation metadata.
if self.exp_prec:
- value, exp, exp_sign = self.scientific_notation_elements(value, locale, numbering_system=numbering_system)
+ value, exp, exp_sign = self.scientific_notation_elements(
+ value,
+ locale,
+ numbering_system=numbering_system,
+ )
# Adjust the precision of the fractional part and force it to the
# currency's if necessary.
)
frac_prec = force_frac
elif currency and currency_digits:
- frac_prec = (get_currency_precision(currency), ) * 2
+ frac_prec = (get_currency_precision(currency),) * 2
else:
frac_prec = self.frac_prec
get_exponential_symbol(locale, numbering_system=numbering_system),
exp_sign, # type: ignore # exp_sign is always defined here
self._format_int(str(exp), self.exp_prec[0], self.exp_prec[1], locale, numbering_system=numbering_system), # type: ignore # exp is always defined here
- ])
+ ]) # fmt: skip
# Is it a significant digits pattern?
elif '@' in self.pattern:
- text = self._format_significant(value,
- self.int_prec[0],
- self.int_prec[1])
+ text = self._format_significant(value, self.int_prec[0], self.int_prec[1])
a, sep, b = text.partition(".")
number = self._format_int(a, 0, 1000, locale, numbering_system=numbering_system)
if sep:
# A normal number pattern.
else:
- number = self._quantize_value(value, locale, frac_prec, group_separator, numbering_system=numbering_system)
+ number = self._quantize_value(
+ value,
+ locale,
+ frac_prec,
+ group_separator,
+ numbering_system=numbering_system,
+ )
- retval = ''.join([
- self.prefix[is_negative],
- number if self.number_pattern != '' else '',
- self.suffix[is_negative]])
+ retval = ''.join(
+ (
+ self.prefix[is_negative],
+ number if self.number_pattern != '' else '',
+ self.suffix[is_negative],
+ ),
+ )
if '¤' in retval and currency is not None:
retval = retval.replace('¤¤¤', get_currency_name(currency, value, locale))
a, sep, b = f"{rounded:f}".partition(".")
integer_part = a
if group_separator:
- integer_part = self._format_int(a, self.int_prec[0], self.int_prec[1], locale, numbering_system=numbering_system)
- number = integer_part + self._format_frac(b or '0', locale=locale, force_frac=frac_prec, numbering_system=numbering_system)
+ integer_part = self._format_int(
+ a,
+ self.int_prec[0],
+ self.int_prec[1],
+ locale,
+ numbering_system=numbering_system,
+ )
+ number = integer_part + self._format_frac(
+ b or '0',
+ locale=locale,
+ force_frac=frac_prec,
+ numbering_system=numbering_system,
+ )
return number
def _format_frac(
) -> str:
min, max = force_frac or self.frac_prec
if len(value) < min:
- value += ('0' * (min - len(value)))
+ value += '0' * (min - len(value))
if max == 0 or (min == 0 and int(value) == 0):
return ''
while len(value) > min and value[-1] == '0':
"""
- babel.numbers
- ~~~~~~~~~~~~~
+babel.numbers
+~~~~~~~~~~~~~
- CLDR Plural support. See UTS #35.
+CLDR Plural support. See UTS #35.
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import decimal
_fallback_tag = 'other'
-def extract_operands(source: float | decimal.Decimal) -> tuple[decimal.Decimal | int, int, int, int, int, int, Literal[0], Literal[0]]:
+def extract_operands(
+ source: float | decimal.Decimal,
+) -> tuple[decimal.Decimal | int, int, int, int, int, int, Literal[0], Literal[0]]:
"""Extract operands from a decimal, a float or an int, according to `CLDR rules`_.
The result is an 8-tuple (n, i, v, w, f, t, c, e), where those symbols are as follows:
def __repr__(self) -> str:
rules = self.rules
- args = ", ".join([f"{tag}: {rules[tag]}" for tag in _plural_tags if tag in rules])
+ args = ", ".join(f"{tag}: {rules[tag]}" for tag in _plural_tags if tag in rules)
return f"<{type(self).__name__} {args!r}>"
@classmethod
- def parse(cls, rules: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> PluralRule:
+ def parse(
+ cls,
+ rules: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule,
+ ) -> PluralRule:
"""Create a `PluralRule` instance for the given rules. If the rules
are a `PluralRule` object, that object is returned.
return ''.join(result)
-def to_python(rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> Callable[[float | decimal.Decimal], str]:
+def to_python(
+ rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule,
+) -> Callable[[float | decimal.Decimal], str]:
"""Convert a list/dict of rules or a `PluralRule` object into a regular
Python function. This is useful in situations where you need a real
function and don't are about the actual rule object:
return ''.join(result)
-def in_range_list(num: float | decimal.Decimal, range_list: Iterable[Iterable[float | decimal.Decimal]]) -> bool:
+def in_range_list(
+ num: float | decimal.Decimal,
+ range_list: Iterable[Iterable[float | decimal.Decimal]],
+) -> bool:
"""Integer range list test. This is the callback for the "in" operator
of the UTS #35 pluralization rule language:
return num == int(num) and within_range_list(num, range_list)
-def within_range_list(num: float | decimal.Decimal, range_list: Iterable[Iterable[float | decimal.Decimal]]) -> bool:
+def within_range_list(
+ num: float | decimal.Decimal,
+ range_list: Iterable[Iterable[float | decimal.Decimal]],
+) -> bool:
"""Float range test. This is the callback for the "within" operator
of the UTS #35 pluralization rule language:
_RULES: list[tuple[str | None, re.Pattern[str]]] = [
(None, re.compile(r'\s+', re.UNICODE)),
- ('word', re.compile(fr'\b(and|or|is|(?:with)?in|not|mod|[{"".join(_VARS)}])\b')),
+ ('word', re.compile(rf'\b(and|or|is|(?:with)?in|not|mod|[{"".join(_VARS)}])\b')),
('value', re.compile(r'\d+')),
('symbol', re.compile(r'%|,|!=|=')),
('ellipsis', re.compile(r'\.{2,3}|\u2026', re.UNICODE)), # U+2026: ELLIPSIS
type_: str,
value: str | None = None,
) -> list[tuple[str, str]] | bool:
- return tokens and tokens[-1][0] == type_ and \
- (value is None or tokens[-1][1] == value)
+ return tokens and tokens[-1][0] == type_ and (value is None or tokens[-1][1] == value)
def skip_token(tokens: list[tuple[str, str]], type_: str, value: str | None = None):
def value_node(value: int) -> tuple[Literal['value'], tuple[int]]:
- return 'value', (value, )
+ return 'value', (value,)
def ident_node(name: str) -> tuple[str, tuple[()]]:
compile_mod = _binary_compiler('MOD(%s, %s)')
def compile_relation(self, method, expr, range_list):
- ranges = ",".join([f"({self.compile(a)}, {self.compile(b)})" for (a, b) in range_list[1]])
+ ranges = ",".join(
+ f"({self.compile(a)}, {self.compile(b)})" for (a, b) in range_list[1]
+ )
return f"{method.upper()}({self.compile(expr)}, [{ranges}])"
compile_t = compile_zero
def compile_relation(self, method, expr, range_list):
- code = _GettextCompiler.compile_relation(
- self, method, expr, range_list)
+ code = _GettextCompiler.compile_relation(self, method, expr, range_list)
if method == 'in':
expr = self.compile(expr)
code = f"(parseInt({expr}, 10) == {expr} && {code})"
"""
- babel.support
- ~~~~~~~~~~~~~
+babel.support
+~~~~~~~~~~~~~
- Several classes and functions that help with integrating and using Babel
- in applications.
+Several classes and functions that help with integrating and using Babel
+in applications.
- .. note: the code in this module is not used by Babel itself
+.. note: the code in this module is not used by Babel itself
- :copyright: (c) 2013-2025 by the Babel Team.
- :license: BSD, see LICENSE for more details.
+:copyright: (c) 2013-2025 by the Babel Team.
+:license: BSD, see LICENSE for more details.
"""
+
from __future__ import annotations
import gettext
def timedelta(
self,
delta: _datetime.timedelta | int,
- granularity: Literal["year", "month", "week", "day", "hour", "minute", "second"] = "second",
+ granularity: Literal[
+ "year",
+ "month",
+ "week",
+ "day",
+ "hour",
+ "minute",
+ "second",
+ ] = "second",
threshold: float = 0.85,
format: Literal["narrow", "short", "medium", "long"] = "long",
add_direction: bool = False,
>>> fmt.timedelta(timedelta(weeks=11))
u'3 months'
"""
- return format_timedelta(delta, granularity=granularity,
- threshold=threshold,
- format=format, add_direction=add_direction,
- locale=self.locale)
+ return format_timedelta(
+ delta,
+ granularity=granularity,
+ threshold=threshold,
+ format=format,
+ add_direction=add_direction,
+ locale=self.locale,
+ )
def number(self, number: float | Decimal | str) -> str:
"""Return an integer number formatted for the locale.
>>> fmt.number(1099)
u'1,099'
"""
- return format_decimal(number, locale=self.locale, numbering_system=self.numbering_system)
+ return format_decimal(
+ number,
+ locale=self.locale,
+ numbering_system=self.numbering_system,
+ )
def decimal(self, number: float | Decimal | str, format: str | None = None) -> str:
"""Return a decimal number formatted for the locale.
>>> fmt.decimal(1.2345)
u'1.234'
"""
- return format_decimal(number, format, locale=self.locale, numbering_system=self.numbering_system)
+ return format_decimal(
+ number,
+ format,
+ locale=self.locale,
+ numbering_system=self.numbering_system,
+ )
def compact_decimal(
self,
)
def currency(self, number: float | Decimal | str, currency: str) -> str:
- """Return a number in the given currency formatted for the locale.
- """
- return format_currency(number, currency, locale=self.locale, numbering_system=self.numbering_system)
+ """Return a number in the given currency formatted for the locale."""
+ return format_currency(
+ number,
+ currency,
+ locale=self.locale,
+ numbering_system=self.numbering_system,
+ )
def compact_currency(
self,
>>> Format('en_US').compact_currency(1234567, "USD", format_type='short', fraction_digits=2)
'$1.23M'
"""
- return format_compact_currency(number, currency, format_type=format_type, fraction_digits=fraction_digits,
- locale=self.locale, numbering_system=self.numbering_system)
+ return format_compact_currency(
+ number,
+ currency,
+ format_type=format_type,
+ fraction_digits=fraction_digits,
+ locale=self.locale,
+ numbering_system=self.numbering_system,
+ )
def percent(self, number: float | Decimal | str, format: str | None = None) -> str:
"""Return a number formatted as percentage for the locale.
>>> fmt.percent(0.34)
u'34%'
"""
- return format_percent(number, format, locale=self.locale, numbering_system=self.numbering_system)
+ return format_percent(
+ number,
+ format,
+ locale=self.locale,
+ numbering_system=self.numbering_system,
+ )
def scientific(self, number: float | Decimal | str) -> str:
- """Return a number formatted using scientific notation for the locale.
- """
- return format_scientific(number, locale=self.locale, numbering_system=self.numbering_system)
+ """Return a number formatted using scientific notation for the locale."""
+ return format_scientific(
+ number,
+ locale=self.locale,
+ numbering_system=self.numbering_system,
+ )
class LazyProxy:
Hello, universe!
Hello, world!
"""
- __slots__ = ['_func', '_args', '_kwargs', '_value', '_is_cache_enabled', '_attribute_error']
+
+ __slots__ = [
+ '_func',
+ '_args',
+ '_kwargs',
+ '_value',
+ '_is_cache_enabled',
+ '_attribute_error',
+ ]
if TYPE_CHECKING:
_func: Callable[..., Any]
_value: Any
_attribute_error: AttributeError | None
- def __init__(self, func: Callable[..., Any], *args: Any, enable_cache: bool = True, **kwargs: Any) -> None:
+ def __init__(
+ self,
+ func: Callable[..., Any],
+ *args: Any,
+ enable_cache: bool = True,
+ **kwargs: Any,
+ ) -> None:
# Avoid triggering our own __setattr__ implementation
object.__setattr__(self, '_func', func)
object.__setattr__(self, '_args', args)
def __deepcopy__(self, memo: Any) -> LazyProxy:
from copy import deepcopy
+
return LazyProxy(
deepcopy(self._func, memo),
enable_cache=deepcopy(self._is_cache_enabled, memo),
class NullTranslations(gettext.NullTranslations):
-
if TYPE_CHECKING:
_info: dict[str, str]
_fallback: NullTranslations | None
domain.
"""
import warnings
+
warnings.warn(
'ldgettext() is deprecated, use dgettext() instead',
DeprecationWarning,
domain.
"""
return self._domains.get(domain, self).ugettext(message)
+
# backward compatibility with 0.9
dugettext = udgettext
domain.
"""
import warnings
+
warnings.warn(
'ldngettext() is deprecated, use dngettext() instead',
DeprecationWarning,
domain.
"""
return self._domains.get(domain, self).ungettext(singular, plural, num)
+
# backward compatibility with 0.9
dungettext = udngettext
``bind_textdomain_codeset()``.
"""
import warnings
+
warnings.warn(
'lpgettext() is deprecated, use pgettext() instead',
DeprecationWarning,
``bind_textdomain_codeset()``.
"""
import warnings
+
warnings.warn(
'lnpgettext() is deprecated, use npgettext() instead',
DeprecationWarning,
`domain`.
"""
return self._domains.get(domain, self).upgettext(context, message)
+
# backward compatibility with 0.9
dupgettext = udpgettext
# backward compatibility with 0.9
dunpgettext = udnpgettext
- def ldnpgettext(self, domain: str, context: str, singular: str, plural: str, num: int) -> str | bytes:
+ def ldnpgettext(
+ self,
+ domain: str,
+ context: str,
+ singular: str,
+ plural: str,
+ num: int,
+ ) -> str | bytes:
"""Equivalent to ``dnpgettext()``, but the translation is returned in
the preferred system encoding, if no other encoding was explicitly set
with ``bind_textdomain_codeset()``.
"""
- return self._domains.get(domain, self).lnpgettext(context, singular,
- plural, num)
+ return self._domains.get(domain, self).lnpgettext(context, singular, plural, num)
ugettext = gettext.NullTranslations.gettext
ungettext = gettext.NullTranslations.ngettext
DEFAULT_DOMAIN = 'messages'
- def __init__(self, fp: gettext._TranslationsReader | None = None, domain: str | None = None):
+ def __init__(
+ self,
+ fp: gettext._TranslationsReader | None = None,
+ domain: str | None = None,
+ ):
"""Initialize the translations catalog.
:param fp: the file-like object the translation should be read from
formatted_value = value
plural_form = "one"
else:
- formatted_value = format_decimal(value, format, locale, numbering_system=numbering_system)
+ formatted_value = format_decimal(
+ value,
+ format,
+ locale,
+ numbering_system=numbering_system,
+ )
plural_form = locale.plural_form(value)
if plural_form in unit_patterns:
# Fall back to a somewhat bad representation.
# nb: This is marked as no-cover, as the current CLDR seemingly has no way for this to happen.
- fallback_name = get_unit_name(measurement_unit, length=length, locale=locale) # pragma: no cover
+ fallback_name = get_unit_name( # pragma: no cover
+ measurement_unit,
+ length=length,
+ locale=locale,
+ )
return f"{formatted_value} {fallback_name or measurement_unit}" # pragma: no cover
# Now we can try and rebuild a compound unit specifier, then qualify it:
- return _find_unit_pattern(f"{bare_numerator_unit}-per-{bare_denominator_unit}", locale=locale)
+ return _find_unit_pattern(
+ f"{bare_numerator_unit}-per-{bare_denominator_unit}",
+ locale=locale,
+ )
def format_compound_unit(
elif denominator_unit: # Denominator has unit
if denominator_value == 1: # support perUnitPatterns when the denominator is 1
denominator_unit = _find_unit_pattern(denominator_unit, locale=locale)
- per_pattern = locale._data["unit_patterns"].get(denominator_unit, {}).get(length, {}).get("per")
+ per_pattern = (
+ locale._data["unit_patterns"]
+ .get(denominator_unit, {})
+ .get(length, {})
+ .get("per")
+ )
if per_pattern:
return per_pattern.format(formatted_numerator)
# See TR-35's per-unit pattern algorithm, point 3.2.
)
# TODO: this doesn't support "compound_variations" (or "prefix"), and will fall back to the "x/y" representation
- per_pattern = locale._data["compound_unit_patterns"].get("per", {}).get(length, {}).get("compound", "{0}/{1}")
+ per_pattern = (
+ locale._data["compound_unit_patterns"]
+ .get("per", {})
+ .get(length, {})
+ .get("compound", "{0}/{1}")
+ )
return per_pattern.format(formatted_numerator, formatted_denominator)
line1 = fp.readline()
has_bom = line1.startswith(codecs.BOM_UTF8)
if has_bom:
- line1 = line1[len(codecs.BOM_UTF8):]
+ line1 = line1[len(codecs.BOM_UTF8) :]
m = PYTHON_MAGIC_COMMENT_re.match(line1)
if not m:
try:
import ast
+
ast.parse(line1.decode('latin-1'))
except (ImportError, SyntaxError, UnicodeEncodeError):
# Either it's a real syntax error, in which case the source is
fp.seek(pos)
-PYTHON_FUTURE_IMPORT_re = re.compile(
- r'from\s+__future__\s+import\s+\(*(.+)\)*')
+PYTHON_FUTURE_IMPORT_re = re.compile(r'from\s+__future__\s+import\s+\(*(.+)\)*')
def parse_future_flags(fp: IO[bytes], encoding: str = 'latin-1') -> int:
code.
"""
import __future__
+
pos = fp.tell()
fp.seek(0)
flags = 0
class TextWrapper(textwrap.TextWrapper):
wordsep_re = re.compile(
- r'(\s+|' # any whitespace
- r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))', # em-dash
+ r'(\s+|' # any whitespace
+ r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))', # em-dash
)
# e.g. '\u2068foo bar.py\u2069:42'
return [c for c in chunks if c]
-def wraptext(text: str, width: int = 70, initial_indent: str = '', subsequent_indent: str = '') -> list[str]:
+def wraptext(
+ text: str,
+ width: int = 70,
+ initial_indent: str = '',
+ subsequent_indent: str = '',
+) -> list[str]:
"""Simple wrapper around the ``textwrap.wrap`` function in the standard
library. This version does not wrap lines on hyphens in words. It also
does not wrap PO file locations containing spaces.
DEPRECATED: Use the standard library `datetime.timezone` instead.
"""
+
# TODO (Babel 3.x): Remove this class
def __init__(self, offset: float, name: str | None = None) -> None: