]> git.ipfire.org Git - thirdparty/babel.git/commitdiff
Replace %/.format/concatenation with f-strings where feasible (#927)
authorAarni Koskela <akx@iki.fi>
Wed, 23 Nov 2022 11:33:10 +0000 (13:33 +0200)
committerGitHub <noreply@github.com>
Wed, 23 Nov 2022 11:33:10 +0000 (13:33 +0200)
Original conversion suggestions via flynt, edited by hand.

24 files changed:
babel/core.py
babel/dates.py
babel/lists.py
babel/localedata.py
babel/localtime/_win32.py
babel/messages/catalog.py
babel/messages/checkers.py
babel/messages/extract.py
babel/messages/frontend.py
babel/messages/pofile.py
babel/numbers.py
babel/plural.py
babel/support.py
babel/units.py
babel/util.py
scripts/generate_authors.py
scripts/import_cldr.py
setup.py
tests/messages/test_catalog.py
tests/messages/test_frontend.py
tests/messages/test_pofile.py
tests/test_core.py
tests/test_plural.py
tests/test_support.py

index 2a01c309fc77f98c7e074d2a0f326f624e322744..825af813225d40a5e8240fabc10700f8930bb1a3 100644 (file)
@@ -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
 
index 8228bef888a7bc8745b07467db15dba741dac760..e9f6f6dd21270511f631f6ceb4ea175298f53248 100644 (file)
@@ -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
index 11cc7d72574144f5667407a6cae66071edd68f91..ea983efe0dca6447657959561f9060a25a55a311 100644 (file)
@@ -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:
index 14e6bcdf4bff1be4e148468834ae39b7434890f4..8ec8f4aaaa112aeca1d46845990d68ec49634dd7 100644 (file)
@@ -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.
index 09b87b14eeb80cda23fc8192e906472c03020a5a..a4f6d557a75a42aad2f4fed99ce008afa1da429d 100644 (file)
@@ -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
 
index e43a28c02d8b531dc98349fb491f489aac73f733..22ce660676a37445caee913952e477e76e52eca7 100644 (file)
@@ -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=(),
index 4292c02d3cecf366949e70010efcde652668ef9e..2706c5bfea1af8b4ec763d91c78b03b6bb132882 100644 (file)
@@ -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: '
index 74e57a1817bcac6e313e87e180768746fb1b4cd2..4f0f649b35bd13136778b094e2221b6e51959b2b 100644 (file)
@@ -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)
index 6e09d1095fa4e210fa022f24acf5857551809f9c..c42cdc2e27b22027cfc9f0039d1205c847435484 100644 (file)
@@ -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:
index 00e0844473dcce62fa85e49f02365783e2ba0255..a213b2237920904c4ff3395744d3d604f42f022b 100644 (file)
@@ -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=':')
index 6c9ab24b0e54e29f92859060e640be1f582aaf36..2221e95a124c14f12ae03ff3a658d01747ceff4a 100644 (file)
@@ -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)
index d3dc22d3265efc69b0ff2dc230648c830af02999..712acec6b5adfd4fee057395215e1cee466c1eca 100644 (file)
@@ -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)}"
index 50f275274828587891ebb4498fa2104538b0deeb..8cebd7d9758cbf8fcf4312efa4db445d75d1bfe3 100644 (file)
@@ -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.
index 8a9ec7d38f30d823d067bd187576ca40d3fbfc48..f8f2675790211f39d6a229b8cc161b04a92765e9 100644 (file)
@@ -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(
index f628844ab7e3fea30a37de65dc3633b0c7959b21..0436b9ee4938ed0b3515bc8f528d249deb5e0317 100644 (file)
@@ -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
index 409f24e367797d2770c03de911248cdcef828049..e2e3addcd22dc6ed6157dc235310060476eb2fcc 100644 (file)
@@ -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:
index 097840cecc9522f0446bd8ab60620cc76f2d3a22..5de707c89cbca69cc042cc2deba4dce9e6b455ae 100755 (executable)
@@ -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):
index 06caa6a64a6c55c062d2fec35d44449b1202fd99..157f7c160e61ee574a5393b09e48156a201164fa 100755 (executable)
--- 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)
 
 
index cc5fb49fc359180d961c1493beb5c552c70cc5ad..e9b15a88aaf2a3adbf3ea8e14fa71164c687e617 100644 (file)
@@ -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'),
     ]
 
 
index 0eb7e8f3ab90788b93b6dc71cca969764713a0d8..9e3ac9f4068748c2c3198be1fc6f2d80c3e4896b 100644 (file)
@@ -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 '_*'"
index 99e59babca7b5287d93132e96e9a9a6b96103dcd..a72368bcce3190a952e63329a8339166d2e3eb29 100644 (file)
@@ -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():
index 2de79e2df0af6a7e79a93a39c154b27bf2977d1f..605bf5c02f147495a1054c2f5d3bce3d2b7fc72b 100644 (file)
@@ -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()
index 42100332410358ea13f1464cbd69369b024194dd..16601cfec06a91f9803d6b8162b5f00e96322dad 100644 (file)
@@ -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)
index 944710751053d5f5ac803a122da7901f29b24ae6..c73e53ba7b84438eea79a4b1b3cc7304ba09961e 100644 (file)
@@ -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'),