summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAarni Koskela <akx@iki.fi>2022-11-23 13:33:10 +0200
committerGitHub <noreply@github.com>2022-11-23 13:33:10 +0200
commita45e25e3125f6ee0a9f32387545df318b0b3b2d0 (patch)
tree6228fbb39432f1584097a10742f0ec8ac7263d7a
parent896c2ea72818011bc0e8bd84151b0b10935fd4fa (diff)
downloadbabel-a45e25e3125f6ee0a9f32387545df318b0b3b2d0.tar.gz
Replace %/.format/concatenation with f-strings where feasible (#927)
Original conversion suggestions via flynt, edited by hand.
-rw-r--r--babel/core.py15
-rw-r--r--babel/dates.py27
-rw-r--r--babel/lists.py9
-rw-r--r--babel/localedata.py6
-rw-r--r--babel/localtime/_win32.py4
-rw-r--r--babel/messages/catalog.py32
-rw-r--r--babel/messages/checkers.py2
-rw-r--r--babel/messages/extract.py13
-rw-r--r--babel/messages/frontend.py36
-rw-r--r--babel/messages/pofile.py36
-rw-r--r--babel/numbers.py47
-rw-r--r--babel/plural.py62
-rw-r--r--babel/support.py4
-rw-r--r--babel/units.py10
-rw-r--r--babel/util.py8
-rw-r--r--scripts/generate_authors.py2
-rwxr-xr-xscripts/import_cldr.py17
-rwxr-xr-xsetup.py2
-rw-r--r--tests/messages/test_catalog.py4
-rw-r--r--tests/messages/test_frontend.py13
-rw-r--r--tests/messages/test_pofile.py2
-rw-r--r--tests/test_core.py3
-rw-r--r--tests/test_plural.py2
-rw-r--r--tests/test_support.py9
24 files changed, 161 insertions, 204 deletions
diff --git a/babel/core.py b/babel/core.py
index 2a01c30..825af81 100644
--- a/babel/core.py
+++ b/babel/core.py
@@ -98,7 +98,7 @@ class UnknownLocaleError(Exception):
:param identifier: the identifier string of the unsupported locale
"""
- Exception.__init__(self, 'unknown locale %r' % identifier)
+ Exception.__init__(self, f"unknown locale {identifier!r}")
#: The identifier of the locale that could not be found.
self.identifier = identifier
@@ -262,7 +262,7 @@ class Locale:
elif isinstance(identifier, Locale):
return identifier
elif not isinstance(identifier, str):
- raise TypeError('Unexpected value for identifier: %r' % (identifier,))
+ raise TypeError(f"Unexpected value for identifier: {identifier!r}")
parts = parse_locale(identifier, sep=sep)
input_id = get_locale_identifier(parts)
@@ -349,9 +349,8 @@ class Locale:
for key in ('territory', 'script', 'variant'):
value = getattr(self, key)
if value is not None:
- parameters.append('%s=%r' % (key, value))
- parameter_string = '%r' % self.language + ', '.join(parameters)
- return 'Locale(%s)' % parameter_string
+ parameters.append(f"{key}={value!r}")
+ return f"Locale({self.language!r}{', '.join(parameters)})"
def __str__(self):
return get_locale_identifier((self.language, self.territory,
@@ -388,7 +387,7 @@ class Locale:
details.append(locale.variants.get(self.variant))
details = filter(None, details)
if details:
- retval += ' (%s)' % u', '.join(details)
+ retval += f" ({', '.join(details)})"
return retval
display_name = property(get_display_name, doc="""\
@@ -1120,7 +1119,7 @@ def parse_locale(identifier, sep='_'):
parts = identifier.split(sep)
lang = parts.pop(0).lower()
if not lang.isalpha():
- raise ValueError('expected only letters, got %r' % lang)
+ raise ValueError(f"expected only letters, got {lang!r}")
script = territory = variant = None
if parts:
@@ -1139,7 +1138,7 @@ def parse_locale(identifier, sep='_'):
variant = parts.pop().upper()
if parts:
- raise ValueError('%r is not a valid locale identifier' % identifier)
+ raise ValueError(f"{identifier!r} is not a valid locale identifier")
return lang, territory, script, variant
diff --git a/babel/dates.py b/babel/dates.py
index 8228bef..e9f6f6d 100644
--- a/babel/dates.py
+++ b/babel/dates.py
@@ -203,7 +203,7 @@ def get_timezone(zone=None):
try:
return _pytz.timezone(zone)
except _pytz.UnknownTimeZoneError:
- raise LookupError('Unknown timezone %s' % zone)
+ raise LookupError(f"Unknown timezone {zone}")
def get_next_timezone_transition(zone=None, dt=None):
@@ -312,11 +312,7 @@ class TimezoneTransition:
return int(self.to_tzinfo._utcoffset.total_seconds())
def __repr__(self):
- return '<TimezoneTransition %s -> %s (%s)>' % (
- self.from_tz,
- self.to_tz,
- self.activates,
- )
+ return f"<TimezoneTransition {self.from_tz} -> {self.to_tz} ({self.activates})>"
def get_period_names(width='wide', context='stand-alone', locale=LC_TIME):
@@ -958,7 +954,7 @@ def format_timedelta(delta, granularity='second', threshold=.85,
yield unit_rel_patterns['future']
else:
yield unit_rel_patterns['past']
- a_unit = 'duration-' + a_unit
+ a_unit = f"duration-{a_unit}"
yield locale._data['unit_patterns'].get(a_unit, {}).get(format)
for unit, secs_per_unit in TIMEDELTA_UNITS:
@@ -1293,7 +1289,7 @@ class DateTimePattern:
self.format = format
def __repr__(self):
- return '<%s %r>' % (type(self).__name__, self.pattern)
+ return f"<{type(self).__name__} {self.pattern!r}>"
def __str__(self):
pat = self.pattern
@@ -1365,7 +1361,7 @@ class DateTimeFormat:
elif char in ('z', 'Z', 'v', 'V', 'x', 'X', 'O'):
return self.format_timezone(char, num)
else:
- raise KeyError('Unsupported date/time field %r' % char)
+ raise KeyError(f"Unsupported date/time field {char!r}")
def extract(self, char):
char = str(char)[0]
@@ -1384,7 +1380,7 @@ class DateTimeFormat:
elif char == 'a':
return int(self.value.hour >= 12) # 0 for am, 1 for pm
else:
- raise NotImplementedError("Not implemented: extracting %r from %r" % (char, self.value))
+ raise NotImplementedError(f"Not implemented: extracting {char!r} from {self.value!r}")
def format_era(self, char, num):
width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
@@ -1429,7 +1425,7 @@ class DateTimeFormat:
if week == 0:
date = self.value - timedelta(days=self.value.day)
week = self.get_week_number(date.day, date.weekday())
- return '%d' % week
+ return str(week)
def format_weekday(self, char='E', num=4):
"""
@@ -1475,7 +1471,7 @@ class DateTimeFormat:
return self.format(self.get_day_of_year(), num)
def format_day_of_week_in_month(self):
- return '%d' % ((self.value.day - 1) // 7 + 1)
+ return str((self.value.day - 1) // 7 + 1)
def format_period(self, char, num):
"""
@@ -1517,7 +1513,7 @@ class DateTimeFormat:
period_names = get_period_names(context=context, width=width, locale=self.locale)
if period in period_names:
return period_names[period]
- raise ValueError('Could not format period %s in %s' % (period, self.locale))
+ raise ValueError(f"Could not format period {period} in {self.locale}")
def format_frac_seconds(self, num):
""" Return fractional seconds.
@@ -1689,11 +1685,10 @@ def parse_pattern(pattern):
fieldchar, fieldnum = tok_value
limit = PATTERN_CHARS[fieldchar]
if limit and fieldnum not in limit:
- raise ValueError('Invalid length for field: %r'
- % (fieldchar * fieldnum))
+ raise ValueError(f"Invalid length for field: {fieldchar * fieldnum!r}")
result.append('%%(%s)s' % (fieldchar * fieldnum))
else:
- raise NotImplementedError("Unknown token type: %s" % tok_type)
+ raise NotImplementedError(f"Unknown token type: {tok_type}")
_pattern_cache[pattern] = pat = DateTimePattern(pattern, u''.join(result))
return pat
diff --git a/babel/lists.py b/babel/lists.py
index 11cc7d7..ea983ef 100644
--- a/babel/lists.py
+++ b/babel/lists.py
@@ -68,11 +68,10 @@ def format_list(lst, style='standard', locale=DEFAULT_LOCALE):
return lst[0]
if style not in locale.list_patterns:
- raise ValueError('Locale %s does not support list formatting style %r (supported are %s)' % (
- locale,
- style,
- list(sorted(locale.list_patterns)),
- ))
+ raise ValueError(
+ f'Locale {locale} does not support list formatting style {style!r} '
+ f'(supported are {sorted(locale.list_patterns)})'
+ )
patterns = locale.list_patterns[style]
if len(lst) == 2:
diff --git a/babel/localedata.py b/babel/localedata.py
index 14e6bcd..8ec8f4a 100644
--- a/babel/localedata.py
+++ b/babel/localedata.py
@@ -50,10 +50,10 @@ def resolve_locale_filename(name):
# Ensure we're not left with one of the Windows reserved names.
if sys.platform == "win32" and _windows_reserved_name_re.match(os.path.splitext(name)[0]):
- raise ValueError("Name %s is invalid on Windows" % name)
+ raise ValueError(f"Name {name} is invalid on Windows")
# Build the path.
- return os.path.join(_dirname, '%s.dat' % name)
+ return os.path.join(_dirname, f"{name}.dat")
def exists(name):
@@ -194,7 +194,7 @@ class Alias:
self.keys = tuple(keys)
def __repr__(self):
- return '<%s %r>' % (type(self).__name__, self.keys)
+ return f"<{type(self).__name__} {self.keys!r}>"
def resolve(self, data):
"""Resolve the alias based on the given data.
diff --git a/babel/localtime/_win32.py b/babel/localtime/_win32.py
index 09b87b1..a4f6d55 100644
--- a/babel/localtime/_win32.py
+++ b/babel/localtime/_win32.py
@@ -77,11 +77,11 @@ def get_localzone_name():
if timezone is None:
# Nope, that didn't work. Try adding 'Standard Time',
# it seems to work a lot of times:
- timezone = tz_names.get(tzkeyname + ' Standard Time')
+ timezone = tz_names.get(f"{tzkeyname} Standard Time")
# Return what we have.
if timezone is None:
- raise pytz.UnknownTimeZoneError('Can not find timezone ' + tzkeyname)
+ raise pytz.UnknownTimeZoneError(f"Can not find timezone {tzkeyname}")
return timezone
diff --git a/babel/messages/catalog.py b/babel/messages/catalog.py
index e43a28c..22ce660 100644
--- a/babel/messages/catalog.py
+++ b/babel/messages/catalog.py
@@ -49,7 +49,7 @@ def _parse_datetime_header(value):
hours_offset_s, mins_offset_s = rest[:2], rest[2:]
# Make them all integers
- plus_minus = int(plus_minus_s + '1')
+ plus_minus = int(f"{plus_minus_s}1")
hours_offset = int(hours_offset_s)
mins_offset = int(mins_offset_s)
@@ -108,8 +108,7 @@ class Message:
self.context = context
def __repr__(self):
- return '<%s %r (flags: %r)>' % (type(self).__name__, self.id,
- list(self.flags))
+ return f"<{type(self).__name__} {self.id!r} (flags: {list(self.flags)!r})>"
def __cmp__(self, other):
"""Compare Messages, taking into account plural ids"""
@@ -312,7 +311,7 @@ class Catalog:
self._locale = None
return
- raise TypeError('`locale` must be a Locale, a locale identifier string, or None; got %r' % locale)
+ raise TypeError(f"`locale` must be a Locale, a locale identifier string, or None; got {locale!r}")
def _get_locale(self):
return self._locale
@@ -334,7 +333,7 @@ class Catalog:
.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', '%s translations' % locale_name)
+ comment = comment.replace("Translations template", f"{locale_name} translations")
return comment
def _set_header_comment(self, string):
@@ -375,8 +374,7 @@ class Catalog:
def _get_mime_headers(self):
headers = []
- headers.append(('Project-Id-Version',
- '%s %s' % (self.project, self.version)))
+ 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',
@@ -399,10 +397,9 @@ class Catalog:
if self.locale is not None:
headers.append(('Plural-Forms', self.plural_forms))
headers.append(('MIME-Version', '1.0'))
- headers.append(('Content-Type',
- 'text/plain; charset=%s' % self.charset))
+ headers.append(("Content-Type", f"text/plain; charset={self.charset}"))
headers.append(('Content-Transfer-Encoding', '8bit'))
- headers.append(('Generated-By', 'Babel %s\n' % VERSION))
+ headers.append(("Generated-By", f"Babel {VERSION}\n"))
return headers
def _force_text(self, s, encoding='utf-8', errors='strict'):
@@ -434,7 +431,7 @@ class Catalog:
if 'charset' in params:
self.charset = params['charset'].lower()
elif name == 'plural-forms':
- params = parse_separated_header(' ;' + value)
+ params = parse_separated_header(f" ;{value}")
self._num_plurals = int(params.get('nplurals', 2))
self._plural_expr = params.get('plural', '(n != 1)')
elif name == 'pot-creation-date':
@@ -541,7 +538,7 @@ class Catalog:
'nplurals=2; plural=(n > 1);'
:type: `str`"""
- return 'nplurals=%s; plural=%s;' % (self.num_plurals, self.plural_expr)
+ return f"nplurals={self.num_plurals}; plural={self.plural_expr};"
def __contains__(self, id):
"""Return whether the catalog has a message with the specified ID."""
@@ -560,7 +557,7 @@ class Catalog:
:rtype: ``iterator``"""
buf = []
for name, value in self.mime_headers:
- buf.append('%s: %s' % (name, value))
+ buf.append(f"{name}: {value}")
flags = set()
if self.fuzzy:
flags |= {'fuzzy'}
@@ -571,8 +568,8 @@ class Catalog:
def __repr__(self):
locale = ''
if self.locale:
- locale = ' %s' % self.locale
- return '<%s %r%s>' % (type(self).__name__, self.domain, locale)
+ locale = f" {self.locale}"
+ return f"<{type(self).__name__} {self.domain!r}{locale}>"
def __delitem__(self, id):
"""Delete the message with the specified ID."""
@@ -626,13 +623,12 @@ class Catalog:
elif id == '':
# special treatment for the header message
self.mime_headers = message_from_string(message.string).items()
- self.header_comment = '\n'.join([('# %s' % c).rstrip() for c
- in message.user_comments])
+ self.header_comment = "\n".join([f"# {c}".rstrip() for c in message.user_comments])
self.fuzzy = message.fuzzy
else:
if isinstance(id, (list, tuple)):
assert isinstance(message.string, (list, tuple)), \
- 'Expected sequence but got %s' % type(message.string)
+ f"Expected sequence but got {type(message.string)}"
self._messages[key] = message
def add(self, id, string=None, locations=(), flags=(), auto_comments=(),
diff --git a/babel/messages/checkers.py b/babel/messages/checkers.py
index 4292c02..2706c5b 100644
--- a/babel/messages/checkers.py
+++ b/babel/messages/checkers.py
@@ -144,7 +144,7 @@ def _validate_format(format, alternative):
type_map = dict(a)
for name, typechar in b:
if name not in type_map:
- raise TranslationError('unknown named placeholder %r' % name)
+ raise TranslationError(f'unknown named placeholder {name!r}')
elif not _compatible(typechar, type_map[name]):
raise TranslationError('incompatible format for '
'placeholder %r: '
diff --git a/babel/messages/extract.py b/babel/messages/extract.py
index 74e57a1..4f0f649 100644
--- a/babel/messages/extract.py
+++ b/babel/messages/extract.py
@@ -42,9 +42,6 @@ DEFAULT_KEYWORDS = {
DEFAULT_MAPPING = [('**.py', 'python')]
-empty_msgid_warning = (
- '%s: warning: Empty msgid. It is reserved by GNU gettext: gettext("") '
- 'returns the header entry with meta information, not the empty string.')
def _strip_comment_tags(comments, tags):
@@ -332,7 +329,7 @@ def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(),
func = builtin.get(method)
if func is None:
- raise ValueError('Unknown extraction method %r' % method)
+ raise ValueError(f"Unknown extraction method {method!r}")
results = func(fileobj, keywords.keys(), comment_tags,
options=options or {})
@@ -377,9 +374,11 @@ def extract(method, fileobj, keywords=DEFAULT_KEYWORDS, comment_tags=(),
first_msg_index = spec[0] - 1
if not messages[first_msg_index]:
# An empty string msgid isn't valid, emit a warning
- where = '%s:%i' % (hasattr(fileobj, 'name') and
- fileobj.name or '(unknown)', lineno)
- sys.stderr.write((empty_msgid_warning % where) + '\n')
+ 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"
+ )
continue
messages = tuple(msgs)
diff --git a/babel/messages/frontend.py b/babel/messages/frontend.py
index 6e09d10..c42cdc2 100644
--- a/babel/messages/frontend.py
+++ b/babel/messages/frontend.py
@@ -191,7 +191,7 @@ class compile_catalog(Command):
for catalog, errors in self._run_domain(domain).items():
n_errors += len(errors)
if n_errors:
- self.log.error('%d errors encountered.' % n_errors)
+ self.log.error('%d errors encountered.', n_errors)
return (1 if n_errors else 0)
def _run_domain(self, domain):
@@ -203,19 +203,19 @@ class compile_catalog(Command):
po_files.append((self.locale,
os.path.join(self.directory, self.locale,
'LC_MESSAGES',
- domain + '.po')))
+ f"{domain}.po")))
mo_files.append(os.path.join(self.directory, self.locale,
'LC_MESSAGES',
- domain + '.mo'))
+ f"{domain}.mo"))
else:
for locale in os.listdir(self.directory):
po_file = os.path.join(self.directory, locale,
- 'LC_MESSAGES', domain + '.po')
+ 'LC_MESSAGES', f"{domain}.po")
if os.path.exists(po_file):
po_files.append((locale, po_file))
mo_files.append(os.path.join(self.directory, locale,
'LC_MESSAGES',
- domain + '.mo'))
+ f"{domain}.mo"))
else:
po_files.append((self.locale, self.input_file))
if self.output_file:
@@ -223,7 +223,7 @@ class compile_catalog(Command):
else:
mo_files.append(os.path.join(self.directory, self.locale,
'LC_MESSAGES',
- domain + '.mo'))
+ f"{domain}.mo"))
if not po_files:
raise OptionError('no message catalogs found')
@@ -451,7 +451,7 @@ class extract_messages(Command):
for path in self.input_paths:
if not os.path.exists(path):
- raise OptionError("Input path: %s does not exist" % path)
+ raise OptionError(f"Input path: {path} does not exist")
self.add_comments = listify_value(self.add_comments or (), ",")
@@ -498,8 +498,8 @@ class extract_messages(Command):
optstr = ''
if options:
- optstr = ' (%s)' % ', '.join(['%s="%s"' % (k, v) for
- k, v in options.items()])
+ opt_values = ", ".join(f'{k}="{v}"' for k, v in options.items())
+ optstr = f" ({opt_values})"
self.log.info('extracting messages from %s%s', filepath, optstr)
if os.path.isfile(path):
@@ -640,7 +640,7 @@ class init_catalog(Command):
raise OptionError('you must specify the output directory')
if not self.output_file:
self.output_file = os.path.join(self.output_dir, self.locale,
- 'LC_MESSAGES', self.domain + '.po')
+ 'LC_MESSAGES', f"{self.domain}.po")
if not os.path.exists(os.path.dirname(self.output_file)):
os.makedirs(os.path.dirname(self.output_file))
@@ -782,12 +782,12 @@ class update_catalog(Command):
po_files.append((self.locale,
os.path.join(self.output_dir, self.locale,
'LC_MESSAGES',
- self.domain + '.po')))
+ f"{self.domain}.po")))
else:
for locale in os.listdir(self.output_dir):
po_file = os.path.join(self.output_dir, locale,
'LC_MESSAGES',
- self.domain + '.po')
+ f"{self.domain}.po")
if os.path.exists(po_file):
po_files.append((locale, po_file))
else:
@@ -889,7 +889,7 @@ class CommandLineInterface:
"""
usage = '%%prog %s [options] %s'
- version = '%%prog %s' % VERSION
+ version = f'%prog {VERSION}'
commands = {
'compile': 'compile message catalogs to MO files',
'extract': 'extract messages from source files and generate a POT file',
@@ -949,7 +949,7 @@ class CommandLineInterface:
cmdname = args[0]
if cmdname not in self.commands:
- self.parser.error('unknown command "%s"' % cmdname)
+ self.parser.error(f'unknown command "{cmdname}"')
cmdinst = self._configure_command(cmdname, args[1:])
return cmdinst.run()
@@ -997,14 +997,14 @@ class CommandLineInterface:
as_args = getattr(cmdclass, "as_args", ())
for long, short, help in cmdclass.user_options:
name = long.strip("=")
- default = getattr(cmdinst, name.replace('-', '_'))
- strs = ["--%s" % name]
+ default = getattr(cmdinst, name.replace("-", "_"))
+ strs = [f"--{name}"]
if short:
- strs.append("-%s" % short)
+ strs.append(f"-{short}")
strs.extend(cmdclass.option_aliases.get(name, ()))
choices = cmdclass.option_choices.get(name, None)
if name == as_args:
- parser.usage += "<%s>" % name
+ parser.usage += f"<{name}>"
elif name in cmdclass.boolean_options:
parser.add_option(*strs, action="store_true", help=help)
elif name in cmdclass.multiple_value_options:
diff --git a/babel/messages/pofile.py b/babel/messages/pofile.py
index 00e0844..a213b22 100644
--- a/babel/messages/pofile.py
+++ b/babel/messages/pofile.py
@@ -318,10 +318,7 @@ class PoFileParser:
if self.abort_invalid:
raise PoFileError(msg, self.catalog, line, lineno)
print("WARNING:", msg)
- # `line` is guaranteed to be unicode so u"{}"-interpolating would always
- # succeed, but on Python < 2 if not in a TTY, `sys.stdout.encoding`
- # is `None`, unicode may not be printable so we `repr()` to ASCII.
- print(u"WARNING: Problem on line {0}: {1}".format(lineno + 1, repr(line)))
+ print(f"WARNING: Problem on line {lineno + 1}: {line!r}")
def read_po(fileobj, locale=None, domain=None, ignore_obsolete=False, charset=None, abort_invalid=False):
@@ -525,34 +522,26 @@ def write_po(fileobj, catalog, width=76, no_location=False, omit_header=False,
else:
_width = 76
for line in wraptext(comment, _width):
- _write('#%s %s\n' % (prefix, line.strip()))
+ _write(f"#{prefix} {line.strip()}\n")
def _write_message(message, prefix=''):
if isinstance(message.id, (list, tuple)):
if message.context:
- _write('%smsgctxt %s\n' % (prefix,
- _normalize(message.context, prefix)))
- _write('%smsgid %s\n' % (prefix, _normalize(message.id[0], prefix)))
- _write('%smsgid_plural %s\n' % (
- prefix, _normalize(message.id[1], prefix)
- ))
+ _write(f"{prefix}msgctxt {_normalize(message.context, prefix)}\n")
+ _write(f"{prefix}msgid {_normalize(message.id[0], prefix)}\n")
+ _write(f"{prefix}msgid_plural {_normalize(message.id[1], prefix)}\n")
for idx in range(catalog.num_plurals):
try:
string = message.string[idx]
except IndexError:
string = ''
- _write('%smsgstr[%d] %s\n' % (
- prefix, idx, _normalize(string, prefix)
- ))
+ _write(f"{prefix}msgstr[{idx:d}] {_normalize(string, prefix)}\n")
else:
if message.context:
- _write('%smsgctxt %s\n' % (prefix,
- _normalize(message.context, prefix)))
- _write('%smsgid %s\n' % (prefix, _normalize(message.id, prefix)))
- _write('%smsgstr %s\n' % (
- prefix, _normalize(message.string or '', prefix)
- ))
+ _write(f"{prefix}msgctxt {_normalize(message.context, prefix)}\n")
+ _write(f"{prefix}msgid {_normalize(message.id, prefix)}\n")
+ _write(f"{prefix}msgstr {_normalize(message.string or '', prefix)}\n")
sort_by = None
if sort_output:
@@ -571,7 +560,7 @@ def write_po(fileobj, catalog, width=76, no_location=False, omit_header=False,
lines += wraptext(line, width=width,
subsequent_indent='# ')
comment_header = u'\n'.join(lines)
- _write(comment_header + u'\n')
+ _write(f"{comment_header}\n")
for comment in message.user_comments:
_write_comment(comment)
@@ -592,10 +581,9 @@ def write_po(fileobj, catalog, width=76, no_location=False, omit_header=False,
locations = message.locations
for filename, lineno in locations:
+ location = filename.replace(os.sep, '/')
if lineno and include_lineno:
- location = u'%s:%d' % (filename.replace(os.sep, '/'), lineno)
- else:
- location = u'%s' % filename.replace(os.sep, '/')
+ location = f"{location}:{lineno:d}"
if location not in locs:
locs.append(location)
_write_comment(' '.join(locs), prefix=':')
diff --git a/babel/numbers.py b/babel/numbers.py
index 6c9ab24..2221e95 100644
--- a/babel/numbers.py
+++ b/babel/numbers.py
@@ -36,7 +36,7 @@ class UnknownCurrencyError(Exception):
"""Create the exception.
:param identifier: the identifier string of the unsupported currency
"""
- Exception.__init__(self, 'Unknown currency %r.' % identifier)
+ Exception.__init__(self, f"Unknown currency {identifier!r}.")
#: The identifier of the locale that could not be found.
self.identifier = identifier
@@ -583,8 +583,7 @@ def format_currency(
try:
pattern = locale.currency_formats[format_type]
except KeyError:
- raise UnknownCurrencyFormatError(
- "%r is not a known currency format type" % format_type)
+ raise UnknownCurrencyFormatError(f"{format_type!r} is not a known currency format type")
return pattern.apply(
number, locale, currency=currency, currency_digits=currency_digits,
@@ -779,7 +778,7 @@ def parse_number(string, locale=LC_NUMERIC):
try:
return int(string.replace(get_group_symbol(locale), ''))
except ValueError:
- raise NumberFormatError('%r is not a valid number' % string)
+ raise NumberFormatError(f"{string!r} is not a valid number")
def parse_decimal(string, locale=LC_NUMERIC, strict=False):
@@ -835,7 +834,7 @@ def parse_decimal(string, locale=LC_NUMERIC, strict=False):
parsed = decimal.Decimal(string.replace(group_symbol, '')
.replace(decimal_symbol, '.'))
except decimal.InvalidOperation:
- raise NumberFormatError('%r is not a valid decimal number' % string)
+ raise NumberFormatError(f"{string!r} is not a valid decimal number")
if strict and group_symbol in string:
proper = format_decimal(parsed, locale=locale, decimal_quantization=False)
if string != proper and string.rstrip('0') != (proper + decimal_symbol):
@@ -843,22 +842,25 @@ def parse_decimal(string, locale=LC_NUMERIC, strict=False):
parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '')
.replace(group_symbol, '.'))
except decimal.InvalidOperation:
- raise NumberFormatError((
- "%r is not a properly formatted decimal number. Did you mean %r?" %
- (string, proper)
- ), suggestions=[proper])
+ raise NumberFormatError(
+ f"{string!r} is not a properly formatted decimal number. "
+ f"Did you mean {proper!r}?",
+ suggestions=[proper],
+ )
else:
proper_alt = format_decimal(parsed_alt, locale=locale, decimal_quantization=False)
if proper_alt == proper:
- raise NumberFormatError((
- "%r is not a properly formatted decimal number. Did you mean %r?" %
- (string, proper)
- ), suggestions=[proper])
+ raise NumberFormatError(
+ f"{string!r} is not a properly formatted decimal number. "
+ f"Did you mean {proper!r}?",
+ suggestions=[proper],
+ )
else:
- raise NumberFormatError((
- "%r is not a properly formatted decimal number. Did you mean %r? Or maybe %r?" %
- (string, proper, proper_alt)
- ), suggestions=[proper, proper_alt])
+ raise NumberFormatError(
+ f"{string!r} is not a properly formatted decimal number. "
+ f"Did you mean {proper!r}? Or maybe {proper_alt!r}?",
+ suggestions=[proper, proper_alt],
+ )
return parsed
@@ -869,8 +871,7 @@ PREFIX_PATTERN = r"(?P<prefix>(?:'[^']*'|%s)*)" % PREFIX_END
NUMBER_PATTERN = r"(?P<number>%s*)" % NUMBER_TOKEN
SUFFIX_PATTERN = r"(?P<suffix>.*)"
-number_re = re.compile(r"%s%s%s" % (PREFIX_PATTERN, NUMBER_PATTERN,
- SUFFIX_PATTERN))
+number_re = re.compile(f"{PREFIX_PATTERN}{NUMBER_PATTERN}{SUFFIX_PATTERN}")
def parse_grouping(p):
@@ -903,7 +904,7 @@ def parse_pattern(pattern):
def _match_number(pattern):
rv = number_re.search(pattern)
if rv is None:
- raise ValueError('Invalid number pattern %r' % pattern)
+ raise ValueError(f"Invalid number pattern {pattern!r}")
return rv.groups()
pos_pattern = pattern
@@ -915,7 +916,7 @@ def parse_pattern(pattern):
neg_prefix, _, neg_suffix = _match_number(neg_pattern)
else:
pos_prefix, number, pos_suffix = _match_number(pos_pattern)
- neg_prefix = '-' + pos_prefix
+ neg_prefix = f"-{pos_prefix}"
neg_suffix = pos_suffix
if 'E' in number:
number, exp = number.split('E', 1)
@@ -978,7 +979,7 @@ class NumberPattern:
self.scale = self.compute_scale()
def __repr__(self):
- return '<%s %r>' % (type(self).__name__, self.pattern)
+ return f"<{type(self).__name__} {self.pattern!r}>"
def compute_scale(self):
"""Return the scaling factor to apply to the number before rendering.
@@ -1184,7 +1185,7 @@ class NumberPattern:
def _quantize_value(self, value, locale, frac_prec, group_separator):
quantum = get_decimal_quantum(frac_prec[1])
rounded = value.quantize(quantum)
- a, sep, b = "{:f}".format(rounded).partition(".")
+ 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)
diff --git a/babel/plural.py b/babel/plural.py
index d3dc22d..712acec 100644
--- a/babel/plural.py
+++ b/babel/plural.py
@@ -111,9 +111,9 @@ class PluralRule:
self.abstract = []
for key, expr in sorted(list(rules)):
if key not in _plural_tags:
- raise ValueError('unknown tag %r' % key)
+ raise ValueError(f"unknown tag {key!r}")
elif key in found:
- raise ValueError('tag %r defined twice' % key)
+ raise ValueError(f"tag {key!r} defined twice")
found.add(key)
ast = _Parser(expr).ast
if ast:
@@ -121,11 +121,8 @@ class PluralRule:
def __repr__(self):
rules = self.rules
- return '<%s %r>' % (
- type(self).__name__,
- ', '.join(['%s: %s' % (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):
@@ -185,7 +182,7 @@ def to_javascript(rule):
to_js = _JavaScriptCompiler().compile
result = ['(function(n) { return ']
for tag, ast in PluralRule.parse(rule).abstract:
- result.append('%s ? %r : ' % (to_js(ast), tag))
+ result.append(f"{to_js(ast)} ? {tag!r} : ")
result.append('%r; })' % _fallback_tag)
return ''.join(result)
@@ -223,8 +220,8 @@ def to_python(rule):
for tag, ast in PluralRule.parse(rule).abstract:
# the str() call is to coerce the tag to the native string. It's
# a limited ascii restricted set of tags anyways so that is fine.
- result.append(' if (%s): return %r' % (to_python_func(ast), str(tag)))
- result.append(' return %r' % _fallback_tag)
+ result.append(f" if ({to_python_func(ast)}): return {str(tag)!r}")
+ result.append(f" return {_fallback_tag!r}")
code = compile('\n'.join(result), '<rule>', 'exec')
eval(code, namespace)
return namespace['evaluate']
@@ -246,10 +243,10 @@ def to_gettext(rule):
_compile = _GettextCompiler().compile
_get_index = [tag for tag in _plural_tags if tag in used_tags].index
- result = ['nplurals=%d; plural=(' % len(used_tags)]
+ result = [f"nplurals={len(used_tags)}; plural=("]
for tag, ast in rule.abstract:
- result.append('%s ? %d : ' % (_compile(ast), _get_index(tag)))
- result.append('%d);' % _get_index(_fallback_tag))
+ result.append(f"{_compile(ast)} ? {_get_index(tag)} : ")
+ result.append(f"{_get_index(_fallback_tag)});")
return ''.join(result)
@@ -427,8 +424,7 @@ class _Parser:
return
self.ast = self.condition()
if self.tokens:
- raise RuleError('Expected end of rule, got %r' %
- self.tokens[-1][1])
+ raise RuleError(f"Expected end of rule, got {self.tokens[-1][1]!r}")
def expect(self, type_, value=None, term=None):
token = skip_token(self.tokens, type_, value)
@@ -437,8 +433,8 @@ class _Parser:
if term is None:
term = repr(value is None and type_ or value)
if not self.tokens:
- raise RuleError('expected %s but end of rule reached' % term)
- raise RuleError('expected %s but got %r' % (term, self.tokens[-1][1]))
+ raise RuleError(f"expected {term} but end of rule reached")
+ raise RuleError(f"expected {term} but got {self.tokens[-1][1]!r}")
def condition(self):
op = self.and_condition()
@@ -527,7 +523,7 @@ class _Compiler:
def compile(self, arg):
op, args = arg
- return getattr(self, 'compile_' + op)(*args)
+ return getattr(self, f"compile_{op}")(*args)
compile_n = lambda x: 'n'
compile_i = lambda x: 'i'
@@ -558,11 +554,8 @@ class _PythonCompiler(_Compiler):
compile_mod = _binary_compiler('MOD(%s, %s)')
def compile_relation(self, method, expr, range_list):
- compile_range_list = '[%s]' % ','.join(
- ['(%s, %s)' % tuple(map(self.compile, range_))
- for range_ in range_list[1]])
- return '%s(%s, %s)' % (method.upper(), self.compile(expr),
- compile_range_list)
+ ranges = ",".join([f"({self.compile(a)}, {self.compile(b)})" for (a, b) in range_list[1]])
+ return f"{method.upper()}({self.compile(expr)}, [{ranges}])"
class _GettextCompiler(_Compiler):
@@ -579,19 +572,11 @@ class _GettextCompiler(_Compiler):
expr = self.compile(expr)
for item in range_list[1]:
if item[0] == item[1]:
- rv.append('(%s == %s)' % (
- expr,
- self.compile(item[0])
- ))
+ rv.append(f"({expr} == {self.compile(item[0])})")
else:
min, max = map(self.compile, item)
- rv.append('(%s >= %s && %s <= %s)' % (
- expr,
- min,
- expr,
- max
- ))
- return '(%s)' % ' || '.join(rv)
+ rv.append(f"({expr} >= {min} && {expr} <= {max})")
+ return f"({' || '.join(rv)})"
class _JavaScriptCompiler(_GettextCompiler):
@@ -610,7 +595,7 @@ class _JavaScriptCompiler(_GettextCompiler):
self, method, expr, range_list)
if method == 'in':
expr = self.compile(expr)
- code = '(parseInt(%s, 10) == %s && %s)' % (expr, expr, code)
+ code = f"(parseInt({expr}, 10) == {expr} && {code})"
return code
@@ -636,8 +621,5 @@ class _UnicodeCompiler(_Compiler):
if item[0] == item[1]:
ranges.append(self.compile(item[0]))
else:
- ranges.append('%s..%s' % tuple(map(self.compile, item)))
- return '%s%s %s %s' % (
- self.compile(expr), negated and ' not' or '',
- method, ','.join(ranges)
- )
+ ranges.append(f"{self.compile(item[0])}..{self.compile(item[1])}")
+ return f"{self.compile(expr)}{' not' if negated else ''} {method} {','.join(ranges)}"
diff --git a/babel/support.py b/babel/support.py
index 50f2752..8cebd7d 100644
--- a/babel/support.py
+++ b/babel/support.py
@@ -577,8 +577,8 @@ class Translations(NullTranslations, gettext.GNUTranslations):
return cls(fp=fp, domain=domain)
def __repr__(self):
- return '<%s: "%s">' % (type(self).__name__,
- self._info.get('project-id-version'))
+ version = self._info.get('project-id-version')
+ return f'<{type(self).__name__}: "{version}">'
def add(self, translations, merge=True):
"""Add the given translations to the catalog.
diff --git a/babel/units.py b/babel/units.py
index 8a9ec7d..f8f2675 100644
--- a/babel/units.py
+++ b/babel/units.py
@@ -4,7 +4,7 @@ from babel.numbers import format_decimal, LC_NUMERIC
class UnknownUnitError(ValueError):
def __init__(self, unit, locale):
- ValueError.__init__(self, "%s is not a known unit in %s" % (unit, locale))
+ ValueError.__init__(self, f"{unit} is not a known unit in {locale}")
def get_unit_name(measurement_unit, length='long', locale=LC_NUMERIC):
@@ -128,10 +128,8 @@ def format_unit(value, measurement_unit, length='long', format=None, locale=LC_N
# 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.
- return '%s %s' % ( # pragma: no cover
- formatted_value,
- (get_unit_name(measurement_unit, length=length, locale=locale) or measurement_unit)
- )
+ fallback_name = get_unit_name(measurement_unit, length=length, locale=locale) # pragma: no cover
+ return f"{formatted_value} {fallback_name or measurement_unit}" # pragma: no cover
def _find_compound_unit(numerator_unit, denominator_unit, locale=LC_NUMERIC):
@@ -179,7 +177,7 @@ def _find_compound_unit(numerator_unit, denominator_unit, locale=LC_NUMERIC):
# Now we can try and rebuild a compound unit specifier, then qualify it:
- return _find_unit_pattern("%s-per-%s" % (bare_numerator_unit, bare_denominator_unit), locale=locale)
+ return _find_unit_pattern(f"{bare_numerator_unit}-per-{bare_denominator_unit}", locale=locale)
def format_compound_unit(
diff --git a/babel/util.py b/babel/util.py
index f628844..0436b9e 100644
--- a/babel/util.py
+++ b/babel/util.py
@@ -82,9 +82,7 @@ def parse_encoding(fp):
if m:
magic_comment_encoding = m.group(1).decode('latin-1')
if magic_comment_encoding != 'utf-8':
- raise SyntaxError(
- 'encoding problem: {0} with BOM'.format(
- magic_comment_encoding))
+ raise SyntaxError(f"encoding problem: {magic_comment_encoding} with BOM")
return 'utf-8'
elif m:
return m.group(1).decode('latin-1')
@@ -191,7 +189,7 @@ def pathmatch(pattern, filename):
buf.append(symbols[part])
elif part:
buf.append(re.escape(part))
- match = re.match(''.join(buf) + '$', filename.replace(os.sep, '/'))
+ match = re.match(f"{''.join(buf)}$", filename.replace(os.sep, "/"))
return match is not None
@@ -236,7 +234,7 @@ class FixedOffsetTimezone(tzinfo):
return self.zone
def __repr__(self):
- return '<FixedOffset "%s" %s>' % (self.zone, self._offset)
+ return f'<FixedOffset "{self.zone}" {self._offset}>'
def utcoffset(self, dt):
return self._offset
diff --git a/scripts/generate_authors.py b/scripts/generate_authors.py
index 409f24e..e2e3add 100644
--- a/scripts/generate_authors.py
+++ b/scripts/generate_authors.py
@@ -13,7 +13,7 @@ def get_sorted_authors_list():
def get_authors_file_content():
- author_list = '\n'.join('- %s' % a for a in get_sorted_authors_list())
+ author_list = "\n".join(f"- {a}" for a in get_sorted_authors_list())
return '''
Babel is written and maintained by the Babel team and various contributors:
diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py
index 097840c..5de707c 100755
--- a/scripts/import_cldr.py
+++ b/scripts/import_cldr.py
@@ -156,7 +156,8 @@ def write_datafile(path, data, dump_json=False):
pickle.dump(data, outfile, 2)
if dump_json:
import json
- with open(path + '.json', 'w') as outfile:
+
+ with open(f"{path}.json", "w") as outfile:
json.dump(data, outfile, indent=4, default=debug_repr)
@@ -358,8 +359,8 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False):
if ext != '.xml':
continue
- full_filename = os.path.join(srcdir, 'main', filename)
- data_filename = os.path.join(destdir, 'locale-data', stem + '.dat')
+ full_filename = os.path.join(srcdir, "main", filename)
+ data_filename = os.path.join(destdir, "locale-data", f"{stem}.dat")
data = {}
if not (force or need_conversion(data_filename, data, full_filename)):
@@ -434,10 +435,10 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False):
unsupported_number_systems_string = ', '.join(sorted(data.pop('unsupported_number_systems')))
if unsupported_number_systems_string:
- log.warning('%s: unsupported number systems were ignored: %s' % (
- locale_id,
- unsupported_number_systems_string,
- ))
+ log.warning(
+ f"{locale_id}: unsupported number systems were ignored: "
+ f"{unsupported_number_systems_string}"
+ )
write_datafile(data_filename, data, dump_json=dump_json)
@@ -902,7 +903,7 @@ def parse_interval_formats(data, tree):
if item_sub.tag == "greatestDifference":
skel_data[item_sub.attrib["id"]] = split_interval_pattern(item_sub.text)
else:
- raise NotImplementedError("Not implemented: %s(%r)" % (item_sub.tag, item_sub.attrib))
+ raise NotImplementedError(f"Not implemented: {item_sub.tag}({item_sub.attrib!r})")
def parse_currency_formats(data, tree):
diff --git a/setup.py b/setup.py
index 06caa6a..157f7c1 100755
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ from setuptools import setup, Command
try:
from babel import __version__
except SyntaxError as exc:
- sys.stderr.write("Unable to import Babel (%s). Are you running a supported version of Python?\n" % exc)
+ sys.stderr.write(f"Unable to import Babel ({exc}). Are you running a supported version of Python?\n")
sys.exit(1)
diff --git a/tests/messages/test_catalog.py b/tests/messages/test_catalog.py
index cc5fb49..e9b15a8 100644
--- a/tests/messages/test_catalog.py
+++ b/tests/messages/test_catalog.py
@@ -357,7 +357,7 @@ def test_catalog_mime_headers():
('MIME-Version', '1.0'),
('Content-Type', 'text/plain; charset=utf-8'),
('Content-Transfer-Encoding', '8bit'),
- ('Generated-By', 'Babel %s\n' % catalog.VERSION),
+ ('Generated-By', f'Babel {catalog.VERSION}\n'),
]
@@ -380,7 +380,7 @@ def test_catalog_mime_headers_set_locale():
('MIME-Version', '1.0'),
('Content-Type', 'text/plain; charset=utf-8'),
('Content-Transfer-Encoding', '8bit'),
- ('Generated-By', 'Babel %s\n' % catalog.VERSION),
+ ('Generated-By', f'Babel {catalog.VERSION}\n'),
]
diff --git a/tests/messages/test_frontend.py b/tests/messages/test_frontend.py
index 0eb7e8f..9e3ac9f 100644
--- a/tests/messages/test_frontend.py
+++ b/tests/messages/test_frontend.py
@@ -130,7 +130,7 @@ class ExtractMessagesTestCase(unittest.TestCase):
assert ('file1.py' in msg.locations[0][0])
def test_input_paths_handle_spaces_after_comma(self):
- self.cmd.input_paths = '%s, %s' % (this_dir, data_dir)
+ self.cmd.input_paths = f"{this_dir}, {data_dir}"
self.cmd.output_file = pot_file
self.cmd.finalize_options()
assert self.cmd.input_paths == [this_dir, data_dir]
@@ -1118,7 +1118,7 @@ msgstr[2] ""
self.cli.run(sys.argv + ['compile',
'--locale', 'de_DE',
'-d', i18n_dir])
- assert not os.path.isfile(mo_file), 'Expected no file at %r' % mo_file
+ assert not os.path.isfile(mo_file), f'Expected no file at {mo_file!r}'
assert sys.stderr.getvalue() == f'catalog {po_file} is marked as fuzzy, skipping\n'
def test_compile_fuzzy_catalog(self):
@@ -1397,14 +1397,15 @@ def test_extract_keyword_args_384(split, arg_name):
]
if split: # Generate a command line with multiple -ks
- kwarg_text = " ".join("%s %s" % (arg_name, kwarg_spec) for kwarg_spec in kwarg_specs)
+ kwarg_text = " ".join(f"{arg_name} {kwarg_spec}" for kwarg_spec in kwarg_specs)
else: # Generate a single space-separated -k
- kwarg_text = "%s \"%s\"" % (arg_name, " ".join(kwarg_specs))
+ specs = ' '.join(kwarg_specs)
+ kwarg_text = f'{arg_name} "{specs}"'
# (Both of those invocation styles should be equivalent, so there is no parametrization from here on out)
cmdinst = configure_cli_command(
- "extract -F babel-django.cfg --add-comments Translators: -o django232.pot %s ." % kwarg_text
+ f"extract -F babel-django.cfg --add-comments Translators: -o django232.pot {kwarg_text} ."
)
assert isinstance(cmdinst, extract_messages)
assert set(cmdinst.keywords.keys()) == {'_', 'dgettext', 'dngettext',
@@ -1489,7 +1490,7 @@ def test_extract_error_code(monkeypatch, capsys):
def test_extract_ignore_dirs(monkeypatch, capsys, tmp_path, with_underscore_ignore):
pot_file = tmp_path / 'temp.pot'
monkeypatch.chdir(project_dir)
- cmd = "extract . -o '{}' --ignore-dirs '*ignored*' ".format(pot_file)
+ cmd = f"extract . -o '{pot_file}' --ignore-dirs '*ignored*' "
if with_underscore_ignore:
# This also tests that multiple arguments are supported.
cmd += "--ignore-dirs '_*'"
diff --git a/tests/messages/test_pofile.py b/tests/messages/test_pofile.py
index 99e59ba..a72368b 100644
--- a/tests/messages/test_pofile.py
+++ b/tests/messages/test_pofile.py
@@ -859,7 +859,7 @@ class PofileFunctionsTestCase(unittest.TestCase):
expected_denormalized = u'multi-line\n translation'
assert expected_denormalized == pofile.denormalize(msgstr)
- assert expected_denormalized == pofile.denormalize('""\n' + msgstr)
+ assert expected_denormalized == pofile.denormalize(f'""\n{msgstr}')
def test_unknown_language_roundtrip():
diff --git a/tests/test_core.py b/tests/test_core.py
index 2de79e2..605bf5c 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -309,8 +309,7 @@ def test_compatible_classes_in_global_and_localedata(filename):
# *.dat files must have compatible classes between Python 2 and 3
if module.split('.')[0] == 'babel':
return pickle.Unpickler.find_class(self, module, name)
- raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
- (module, name))
+ raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")
with open(filename, 'rb') as f:
assert Unpickler(f).load()
diff --git a/tests/test_plural.py b/tests/test_plural.py
index 4210033..16601cf 100644
--- a/tests/test_plural.py
+++ b/tests/test_plural.py
@@ -273,7 +273,7 @@ def test_gettext_compilation(locale):
chars = 'ivwft'
# Test that these rules are valid for this test; i.e. that they contain at least one
# of the gettext-unsupported characters.
- assert any((" " + ch + " ") in rule for ch in chars for rule in ru_rules.values())
+ assert any(f" {ch} " in rule for ch in chars for rule in ru_rules.values())
# Then test that the generated value indeed does not contain these.
ru_rules_gettext = plural.to_gettext(ru_rules)
assert not any(ch in ru_rules_gettext for ch in chars)
diff --git a/tests/test_support.py b/tests/test_support.py
index 9447107..c73e53b 100644
--- a/tests/test_support.py
+++ b/tests/test_support.py
@@ -63,8 +63,7 @@ class TranslationsTestCase(unittest.TestCase):
def assertEqualTypeToo(self, expected, result):
assert expected == result
- assert type(expected) == type(result), "instance type's do not " + \
- "match: %r!=%r" % (type(expected), type(result))
+ assert type(expected) == type(result), f"instance types do not match: {type(expected)!r}!={type(result)!r}"
def test_pgettext(self):
self.assertEqualTypeToo('Voh', self.translations.gettext('foo'))
@@ -210,7 +209,7 @@ class NullTranslationsTestCase(unittest.TestCase):
def test_same_methods(self):
for name in self.method_names():
if not hasattr(self.null_translations, name):
- self.fail('NullTranslations does not provide method %r' % name)
+ self.fail(f"NullTranslations does not provide method {name!r}")
def test_method_signature_compatibility(self):
for name in self.method_names():
@@ -346,11 +345,13 @@ def test_format_percent():
def test_lazy_proxy():
def greeting(name='world'):
- return u'Hello, %s!' % name
+ return f"Hello, {name}!"
+
lazy_greeting = support.LazyProxy(greeting, name='Joe')
assert str(lazy_greeting) == u"Hello, Joe!"
assert u' ' + lazy_greeting == u' Hello, Joe!'
assert u'(%s)' % lazy_greeting == u'(Hello, Joe!)'
+ assert f"[{lazy_greeting}]" == "[Hello, Joe!]"
greetings = [
support.LazyProxy(greeting, 'world'),