``(message, errors)`` tuple, where ``message`` is the `Message` object
and ``errors`` is a sequence of `TranslationError` objects.
- :note: this feature requires ``setuptools``/``pkg_resources`` to be
- installed; if it is not, this method will simply return an empty
- iterator
:rtype: ``iterator``
"""
checkers = []
try:
from pkg_resources import working_set
except ImportError:
- return
+ from babel.messages.checkers import builtin_checkers
+ checkers.extend(builtin_checkers)
else:
for entry_point in working_set.iter_entry_points('babel.checkers'):
checkers.append(entry_point.load())
- for message in self._messages.values():
- errors = []
- for checker in checkers:
- try:
- checker(self, message)
- except TranslationError, e:
- errors.append(e)
- if errors:
- yield message, errors
+ for message in self._messages.values():
+ errors = []
+ for checker in checkers:
+ try:
+ checker(self, message)
+ except TranslationError, e:
+ errors.append(e)
+ if errors:
+ yield message, errors
def get(self, id, context=None):
"""Return the message with the specified ID and context.
:since: version 0.9
"""
+from itertools import izip
from babel.messages.catalog import TranslationError, PYTHON_FORMAT
+#: list of format chars that are compatible to each other
+_string_format_compatibilities = [
+ set(['i', 'd', 'u']),
+ set(['x', 'X']),
+ set(['f', 'F', 'g', 'G'])
+]
+
+
def num_plurals(catalog, message):
"""Verify the number of plurals in the translation."""
if not message.pluralizable:
raise TranslationError("Wrong number of plural forms (expected %d)" %
catalog.num_plurals)
+
def python_format(catalog, message):
- if 'python-format' in message.flags:
- msgids = message.id
- if not isinstance(msgids, (list, tuple)):
- msgids = (msgids,)
- msgstrs = message.string
- if not isinstance(msgstrs, (list, tuple)):
- msgstrs = (msgstrs,)
- for idx, msgid in enumerate(msgids):
- if not msgstrs[idx]:
- continue # no translation
- for match in PYTHON_FORMAT.finditer(msgid):
- param = match.group(0)
- if param not in msgstrs[idx]:
- raise TranslationError("Python parameter %s not found in "
- "translation" % param)
+ if 'python-format' not in message.flags:
+ return
+ msgids = message.id
+ if not isinstance(msgids, (list, tuple)):
+ msgids = (msgids,)
+ msgstrs = message.string
+ if not isinstance(msgstrs, (list, tuple)):
+ msgstrs = (msgstrs,)
+
+ for msgid, msgstr in izip(msgids, msgstrs):
+ if msgstr:
+ _validate_format(msgid, msgstr)
+
+
+def _validate_format(format, alternative):
+ """Test format string `alternative` against `format`. `format` can be the
+ msgid of a message and `alternative` one of the `msgstr`\s. The two
+ arguments are not interchangeable as `alternative` may contain less
+ placeholders if `format` uses named placeholders.
+
+ If `format` does not use string formatting a `ValueError` is raised.
+
+ If the string formatting of `alternative` is compatible to `format` the
+ function returns `None`, otherwise a `TranslationError` is raised.
+
+ Examples for compatible format strings:
+
+ >>> _validate_format('Hello %s!', 'Hallo %s!')
+ >>> _validate_format('Hello %i!', 'Hallo %d!')
+
+ Example for an incompatible format strings:
+
+ >>> _validate_format('Hello %(name)s!', 'Hallo %s!')
+ Traceback (most recent call last):
+ ...
+ TranslationError: the format strings are of different kinds
+
+ This function is used by the `python_format` checker.
+
+ :param format: The original format string
+ :param alternative: The alternative format string that should be checked
+ against format
+ :return: None on success
+ :raises TranslationError: on formatting errors
+ """
+
+ def _parse(string):
+ result = []
+ for match in PYTHON_FORMAT.finditer(string):
+ name, format, typechar = match.groups()
+ if typechar == '%' and name is not None:
+ continue
+ result.append((name, typechar))
+ return result
+
+ def _compatible(a, b):
+ if a == b:
+ return True
+ for set in _string_format_compatibilities:
+ if a in set and b in set:
+ return True
+ return False
+
+ def _check_positional(results):
+ positional = None
+ for name, char in results:
+ if positional is None:
+ positional = name is None
+ else:
+ if (name is None) != positional:
+ raise ValueError('format string mixes positional '
+ 'and named placeholders')
+ return bool(positional)
+
+ a, b = map(_parse, (format, alternative))
+
+ # if a does not use string formattings, we are dealing with invalid
+ # input data. This function only works if the first string provided
+ # does contain string format chars
+ if not a:
+ raise ValueError('original string provided does not use string '
+ 'formatting.')
+
+ # now check if both strings are positional or named
+ a_positional, b_positional = map(_check_positional, (a, b))
+ if a_positional and not b_positional and not b:
+ raise TranslationError('placeholders are incompatible')
+ elif a_positional != b_positional:
+ raise TranslationError('the format strings are of different kinds')
+
+ # if we are operating on positional strings both must have the
+ # same number of format chars and those must be compatible
+ if a_positional:
+ if len(a) != len(b):
+ raise TranslationError('positional format placeholders are '
+ 'unbalanced')
+ for idx, ((_, first), (_, second)) in enumerate(izip(a, b)):
+ if not _compatible(first, second):
+ raise TranslationError('incompatible format for placeholder '
+ '%d: %r and %r are not compatible' %
+ (idx + 1, first, second))
+
+ # otherwise the second string must not have names the first one
+ # doesn't have and the types of those included must be compatible
+ else:
+ type_map = dict(a)
+ for name, typechar in b:
+ if name not in type_map:
+ raise TranslationError('unknown named placeholder %r' % name)
+ elif not _compatible(typechar, type_map[name]):
+ raise TranslationErrorError('incompatible format for '
+ 'placeholder %r: '
+ '%r and %r are not compatible' %
+ (name, typechar, type_map[name]))
+
+
+#: list of builtin checkers for babel installations without setuptools.
+#: Keep this in sync with the mapping in the setup.py
+#: :see: babel.messages.catalog.Catalog.check
+builtin_checkers = [num_plurals, python_format]
from babel.dates import format_date, format_datetime, format_time, LC_TIME
from babel.numbers import format_number, format_decimal, format_currency, \
format_percent, format_scientific, LC_NUMERIC
-from babel.util import UTC, PYTHON_FORMAT
+from babel.util import UTC
-__all__ = ['Format', 'LazyProxy', 'Translations', 'validate_format']
+__all__ = ['Format', 'LazyProxy', 'Translations']
__docformat__ = 'restructuredtext en'
def __repr__(self):
return "<%s>" % (type(self).__name__)
-
-
-#: list of format chars that are compatible to each other
-_string_format_compatibilities = [
- set(['i', 'd', 'u']),
- set(['x', 'X']),
- set(['f', 'F', 'g', 'G'])
-]
-
-
-def validate_format(format, alternative):
- """Test format string `alternative` against `format`. `format` can be the
- msgid of a message and `alternative` one of the `msgstr`\s. The two
- arguments are not interchangeable as `alternative` may contain less
- placeholders if `format` uses named placeholders.
-
- If `format` does not use string formatting a `TypeError` is raised.
-
- If the string formatting of `alternative` is compatible to `format` the
- function returns `None`, otherwise a `ValueError` is raised.
-
- Examples for compatible format strings:
-
- >>> validate_format('Hello %s!', 'Hallo %s!')
- >>> validate_format('Hello %i!', 'Hallo %d!')
-
- Example for an incompatible format strings:
-
- >>> validate_format('Hello %(name)s!', 'Hallo %s!')
- Traceback (most recent call last):
- ...
- TypeError: the format strings are of different kinds
-
- :param format: The original format string
- :param alternative: The alternative format string that should be checked
- against format
- :return: None on success
- :raises ValueError: on an formatting error
- """
-
- def _parse(string):
- result = []
- for match in PYTHON_FORMAT.finditer(string):
- name, format, typechar = match.groups()
- if typechar == '%' and name is not None:
- continue
- result.append((name, typechar))
- return result
-
- def _compatible(a, b):
- if a == b:
- return True
- for set in _string_format_compatibilities:
- if a in set and b in set:
- return True
- return False
-
- def _check_positional(results):
- positional = None
- for name, char in results:
- if positional is None:
- positional = name is None
- else:
- if (name is None) != positional:
- raise ValueError('format string mixes positional '
- 'and named placeholders')
- return bool(positional)
-
- a, b = map(_parse, (format, alternative))
-
- # if a does not use string formattings, we are dealing with invalid
- # input data. This function only works if the first string provided
- # does contain string format chars
- if not a:
- raise TypeError('original string provided does not use string '
- 'formatting.')
-
- # now check if both strings are positional or named
- a_positional, b_positional = map(_check_positional, (a, b))
- if a_positional and not b_positional and not b:
- raise ValueError('placeholders are incompatible')
- elif a_positional != b_positional:
- raise TypeError('the format strings are of different kinds')
-
- # if we are operating on positional strings both must have the
- # same number of format chars and those must be compatible
- if a_positional:
- if len(a) != len(b):
- raise ValueError('positional format placeholders unbalanced')
- for idx, ((_, first), (_, second)) in enumerate(zip(a, b)):
- if not _compatible(first, second):
- raise ValueError('incompatible format for placeholder %d: '
- '%r and %r are not compatible' %
- (idx + 1, first, second))
-
- # otherwise the second string must not have names the first one
- # doesn't have and the types of those included must be compatible
- else:
- type_map = dict(a)
- for name, typechar in b:
- if name not in type_map:
- raise ValueError('unknown named placeholder %r' % name)
- elif not _compatible(typechar, type_map[name]):
- raise ValueError('incompatible format for placeholder %r: '
- '%r and %r are not compatible' %
- (name, typechar, type_map[name]))