diff options
45 files changed, 667 insertions, 616 deletions
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d31f648..2ca497a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,32 +17,30 @@ jobs: fail-fast: false matrix: os: - - ubuntu + - ["ubuntu", "ubuntu-20.04"] config: # [Python version, tox env] - ["3.9", "lint"] - - ["2.7", "py27"] - - ["3.5", "py35"] - - ["3.6", "py36"] - ["3.7", "py37"] - ["3.8", "py38"] - ["3.9", "py39"] - ["3.10", "py310"] - - ["pypy2", "pypy"] - - ["pypy3", "pypy3"] + - ["3.11", "py311"] + - ["pypy-3.7", "pypy3"] - ["3.9", "docs"] - ["3.9", "coverage"] - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os[1] }} + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.config[0] }} - name: Pip cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} @@ -58,7 +56,7 @@ jobs: - name: Coverage if: matrix.config[1] == 'coverage' run: | - pip install coveralls coverage-python-version + pip install coveralls coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -28,4 +28,5 @@ lib64 log/ parts/ pyvenv.cfg +testing.log var/ @@ -2,15 +2,15 @@ # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" -commit-id = "fba6d957ba447b6fa369d872e803756bd5176391" +commit-id = "d03ad585" [python] with-windows = false with-pypy = true with-future-python = false -with-legacy-python = true with-docs = true with-sphinx-doctests = true +with-macos = false [tox] use-flake8 = true @@ -40,6 +40,7 @@ ignore-bad-ideas = [ "src/zope/i18n/tests/locale/en/LC_MESSAGES/zope-i18n.mo", "src/zope/i18n/tests/locale2/en/LC_MESSAGES/zope-i18n.mo", "src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo", + "src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n2.mo", "src/zope/i18n/tests/pl-default.mo", "src/zope/i18n/tests/sr-default.mo", "src/zope/i18n/tests/sr@Cyrl-default.mo", diff --git a/CHANGES.rst b/CHANGES.rst index f6ff6ea..681b65c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,14 @@ CHANGES ========= -4.9.1 (unreleased) -================== +5.0 (unreleased) +================ + +- Add support for Python 3.11. + +- Drop support for Python 2.7, 3.5, 3.6. -- Nothing changed yet. +- Drop deprecated support for running the tests using ``python setup.py test``. 4.9.0 (2021-12-09) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..31d95f0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +<!-- +Generated from: +https://github.com/zopefoundation/meta/tree/master/config/pure-python +--> +# Contributing to zopefoundation projects + +The projects under the zopefoundation GitHub organization are open source and +welcome contributions in different forms: + +* bug reports +* code improvements and bug fixes +* documentation improvements +* pull request reviews + +For any changes in the repository besides trivial typo fixes you are required +to sign the contributor agreement. See +https://www.zope.dev/developer/becoming-a-committer.html for details. + +Please visit our [Developer +Guidelines](https://www.zope.dev/developer/guidelines.html) if you'd like to +contribute code changes and our [guidelines for reporting +bugs](https://www.zope.dev/developer/reporting-bugs.html) if you want to file a +bug report. diff --git a/MANIFEST.in b/MANIFEST.in index b2b9c96..4a04ff5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python +include *.md include *.rst include *.txt include buildout.cfg @@ -1,7 +1,7 @@ # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [bdist_wheel] -universal = 1 +universal = 0 [flake8] doctests = 1 @@ -20,7 +20,19 @@ ignore-bad-ideas = src/zope/i18n/tests/locale/en/LC_MESSAGES/zope-i18n.mo src/zope/i18n/tests/locale2/en/LC_MESSAGES/zope-i18n.mo src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n.mo + src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n2.mo src/zope/i18n/tests/pl-default.mo src/zope/i18n/tests/sr-default.mo src/zope/i18n/tests/sr@Cyrl-default.mo src/zope/i18n/tests/sr@Latn-default.mo + +[isort] +force_single_line = True +combine_as_imports = True +sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER +known_third_party = six, docutils, pkg_resources +known_zope = +known_first_party = +default_section = ZOPE +line_length = 79 +lines_after_imports = 2 @@ -19,7 +19,9 @@ """Setup for zope.i18n package """ import os -from setuptools import setup, find_packages + +from setuptools import find_packages +from setuptools import setup def read(*rnames): @@ -27,21 +29,6 @@ def read(*rnames): return f.read() -def alltests(): - import sys - import unittest - # use the zope.testrunner machinery to find all the - # test suites we've put under ourselves - import zope.testrunner.find - import zope.testrunner.options - here = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')) - args = sys.argv[:] - defaults = ["--test-path", here] - options = zope.testrunner.options.get_options(args, defaults) - suites = list(zope.testrunner.find.find_suites(options)) - return unittest.TestSuite(suites) - - COMPILE_REQUIRES = [ # python-gettext used to be here, but it's now # a fixed requirement. Keep the extra to avoid @@ -62,7 +49,7 @@ TESTS_REQUIRE = COMPILE_REQUIRES + ZCML_REQUIRES + [ setup( name='zope.i18n', - version='4.9.1.dev0', + version='5.0.dev0', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Zope Internationalization Support', @@ -80,15 +67,12 @@ setup( 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Natural Language :: English', @@ -118,8 +102,6 @@ setup( 'repoze.sphinx.autointerface', ], }, - tests_require=TESTS_REQUIRE, - test_suite='__main__.alltests', include_package_data=True, zip_safe=False, ) diff --git a/src/zope/i18n/__init__.py b/src/zope/i18n/__init__.py index b737d00..36b6953 100644 --- a/src/zope/i18n/__init__.py +++ b/src/zope/i18n/__init__.py @@ -16,16 +16,15 @@ import re from zope.component import queryUtility -from zope.i18nmessageid import Message # MessageFactory is not used, but it might be here for BBB reasons, # as it could be imported by other packages. +from zope.i18nmessageid import Message from zope.i18nmessageid import MessageFactory # noqa -from zope.i18n._compat import text_type from zope.i18n.config import ALLOWED_LANGUAGES +from zope.i18n.interfaces import IFallbackTranslationDomainFactory from zope.i18n.interfaces import INegotiator from zope.i18n.interfaces import ITranslationDomain -from zope.i18n.interfaces import IFallbackTranslationDomainFactory # Set up regular expressions for finding interpolation variables in text. @@ -37,7 +36,7 @@ _interp_regex = re.compile(r'(?<!\$)(\$(?:(%(n)s)|{(%(n)s)}))' % ({'n': NAME_RE})) -class _FallbackNegotiator(object): +class _FallbackNegotiator: def getLanguage(self, _allowed, _context): return None @@ -171,9 +170,9 @@ def translate(msgid, domain=None, mapping=None, context=None, number = msgid.number if default is None: - default = text_type(msgid) + default = str(msgid) if msgid_plural is not None and default_plural is None: - default_plural = text_type(msgid_plural) + default_plural = str(msgid_plural) if domain: util = queryUtility(ITranslationDomain, domain) @@ -239,7 +238,7 @@ def interpolate(text, mapping=None): value = mapping.get(param1 or param2, whole) if isinstance(value, Message): value = interpolate(value, value.mapping) - return text_type(value) + return str(value) if not text or not mapping: return text diff --git a/src/zope/i18n/_compat.py b/src/zope/i18n/_compat.py deleted file mode 100644 index f6bf93b..0000000 --- a/src/zope/i18n/_compat.py +++ /dev/null @@ -1,8 +0,0 @@ -# This gives a linting error because unicode is not defined on Python 3: -# text_type = str if bytes is not str else unicode -try: - # Python 2 - text_type = unicode -except NameError: - # Python 3 - text_type = str diff --git a/src/zope/i18n/compile.py b/src/zope/i18n/compile.py index 81a6a17..5a5564f 100644 --- a/src/zope/i18n/compile.py +++ b/src/zope/i18n/compile.py @@ -1,11 +1,12 @@ -from contextlib import closing +import logging import os.path +from contextlib import closing from os.path import join -import logging from pythongettext.msgfmt import Msgfmt from pythongettext.msgfmt import PoSyntaxError + logger = logging.getLogger('zope.i18n') @@ -15,7 +16,7 @@ HAS_PYTHON_GETTEXT = True def _safe_mtime(path): try: return os.path.getmtime(path) - except (IOError, OSError): + except OSError: return None @@ -36,10 +37,7 @@ def compile_mo_file(domain, lc_messages_path): if po_mtime > mo_mtime: try: - # Msgfmt.getAsFile returns io.BytesIO on Python 3, - # and cStringIO.StringIO on Python 2; - # sadly StringIO isn't a proper context manager, so we have to - # wrap it with `closing`. Also, Msgfmt doesn't properly close a + # Msgfmt doesn't properly close a # file it opens for reading if you pass the path, # but it does if you pass the file. with open(pofile, 'rb') as pofd: @@ -49,5 +47,5 @@ def compile_mo_file(domain, lc_messages_path): except PoSyntaxError as err: logger.warning( 'Syntax error while compiling %s (%s).', pofile, err.msg) - except (IOError, OSError) as err: + except OSError as err: logger.warning('Error while compiling %s (%s).', pofile, err) diff --git a/src/zope/i18n/config.py b/src/zope/i18n/config.py index ce94ded..1979d26 100644 --- a/src/zope/i18n/config.py +++ b/src/zope/i18n/config.py @@ -1,5 +1,6 @@ import os + #: The environment variable that is consulted when this module #: is imported to determine the value of `COMPILE_MO_FILES`. #: Simply set this to a non-empty string to make it True. diff --git a/src/zope/i18n/format.py b/src/zope/i18n/format.py index 2a2421c..8162978 100644 --- a/src/zope/i18n/format.py +++ b/src/zope/i18n/format.py @@ -16,23 +16,17 @@ This module implements basic object formatting functionality, such as date/time, number and money formatting. """ -import sys -import re -import math import datetime +import math +import re +import sys + import pytz import pytz.reference - -from zope.i18n._compat import text_type -from zope.i18n.interfaces import IDateTimeFormat, INumberFormat from zope.interface import implementer - -NATIVE_NUMBER_TYPES = (int, float) -try: - NATIVE_NUMBER_TYPES += (long,) -except NameError: - pass # Py3 +from zope.i18n.interfaces import IDateTimeFormat +from zope.i18n.interfaces import INumberFormat def roundHalfUp(n): @@ -55,7 +49,7 @@ class DateTimeParseError(Exception): @implementer(IDateTimeFormat) -class DateTimeFormat(object): +class DateTimeFormat: __doc__ = IDateTimeFormat.__doc__ _DATETIMECHARS = "aGyMdEDFwWhHmsSkKz" @@ -212,7 +206,7 @@ class DateTimeFormat(object): else: bin_pattern = self._bin_pattern - text = u"" + text = "" info = buildDateTimeInfo(obj, self.calendar, bin_pattern) for elem in bin_pattern: text += info.get(elem, elem) @@ -226,7 +220,7 @@ class NumberParseError(Exception): @implementer(INumberFormat) -class NumberFormat(object): +class NumberFormat: __doc__ = INumberFormat.__doc__ type = None @@ -236,18 +230,18 @@ class NumberFormat(object): def __init__(self, pattern=None, symbols=()): # setup default symbols self.symbols = { - u"decimal": u".", - u"group": u",", - u"list": u";", - u"percentSign": u"%", - u"nativeZeroDigit": u"0", - u"patternDigit": u"#", - u"plusSign": u"+", - u"minusSign": u"-", - u"exponential": u"E", - u"perMille": u"\xe2\x88\x9e", - u"infinity": u"\xef\xbf\xbd", - u"nan": u'' + "decimal": ".", + "group": ",", + "list": ";", + "percentSign": "%", + "nativeZeroDigit": "0", + "patternDigit": "#", + "plusSign": "+", + "minusSign": "-", + "exponential": "E", + "perMille": "\xe2\x88\x9e", + "infinity": "\xef\xbf\xbd", + "nan": '' } self.symbols.update(symbols) if pattern is not None: @@ -411,12 +405,7 @@ class NumberFormat(object): else: bin_pattern = bin_pattern[1] - if isinstance(obj, NATIVE_NUMBER_TYPES): - # repr() handles high-precision numbers correctly in - # Python 2 and 3. str() is only correct in Python 3. - strobj = repr(obj) - else: - strobj = str(obj) + strobj = str(obj) if 'e' in strobj: # Str(obj) # returned scientific representation of a number (e.g. # 1e-7). We can't rely on str() to format fraction. @@ -432,7 +421,7 @@ class NumberFormat(object): # The exponential might have a mandatory sign; remove it from the # bin_pattern and remember the setting exp_bin_pattern = bin_pattern[EXPONENTIAL] - plus_sign = u"" + plus_sign = "" if exp_bin_pattern.startswith('+'): plus_sign = self.symbols['plusSign'] exp_bin_pattern = exp_bin_pattern[1:] @@ -510,8 +499,8 @@ class NumberFormat(object): if bin_pattern[PADDING4] is not None and post_padding > 0: text += bin_pattern[PADDING4] * post_padding - # TODO: Need to make sure unicode is everywhere - return text_type(text) + # TODO: Need to make sure str is everywhere + return str(text) DEFAULT = 0 @@ -622,12 +611,13 @@ def buildDateTimeParseInfo(calendar, pattern): # am/pm marker (Text) for entry in _findFormattingCharacterInPattern('a', pattern): - info[entry] = r'(%s|%s)' % (calendar.am, calendar.pm) + info[entry] = r'({}|{})'.format(calendar.am, calendar.pm) # era designator (Text) # TODO: works for gregorian only right now for entry in _findFormattingCharacterInPattern('G', pattern): - info[entry] = r'(%s|%s)' % (calendar.eras[1][1], calendar.eras[2][1]) + info[entry] = r'({}|{})'.format( + calendar.eras[1][1], calendar.eras[2][1]) # time zone (Text) for entry in _findFormattingCharacterInPattern('z', pattern): @@ -703,8 +693,8 @@ def buildDateTimeInfo(dt, calendar, pattern): tz_fullname = getattr(tzinfo, 'zone', None) or tz_name info = { - ('y', 2): text_type(dt.year)[2:], - ('y', 4): text_type(dt.year), + ('y', 2): str(dt.year)[2:], + ('y', 4): str(dt.year), } # Generic Numbers @@ -715,7 +705,7 @@ def buildDateTimeInfo(dt, calendar, pattern): ('S', dt.microsecond), ('w', int(dt.strftime('%W'))), ('W', week_in_month)): for entry in _findFormattingCharacterInPattern(field, pattern): - info[entry] = (u"%%.%ii" % entry[1]) % value + info[entry] = ("%%.%ii" % entry[1]) % value # am/pm marker (Text) for entry in _findFormattingCharacterInPattern('a', pattern): @@ -729,9 +719,9 @@ def buildDateTimeInfo(dt, calendar, pattern): # time zone (Text) for entry in _findFormattingCharacterInPattern('z', pattern): if entry[1] == 1: - info[entry] = u"%s%i%.2i" % (tz_sign, tz_hours, tz_mins) + info[entry] = "%s%i%.2i" % (tz_sign, tz_hours, tz_mins) elif entry[1] == 2: - info[entry] = u"%s%.2i:%.2i" % (tz_sign, tz_hours, tz_mins) + info[entry] = "%s%.2i:%.2i" % (tz_sign, tz_hours, tz_mins) elif entry[1] == 3: info[entry] = tz_name else: @@ -740,9 +730,9 @@ def buildDateTimeInfo(dt, calendar, pattern): # month in year (Text and Number) for entry in _findFormattingCharacterInPattern('M', pattern): if entry[1] == 1: - info[entry] = u"%i" % dt.month + info[entry] = "%i" % dt.month elif entry[1] == 2: - info[entry] = u"%.2i" % dt.month + info[entry] = "%.2i" % dt.month elif entry[1] == 3: info[entry] = calendar.months[dt.month][1] else: @@ -751,9 +741,9 @@ def buildDateTimeInfo(dt, calendar, pattern): # day in week (Text and Number) for entry in _findFormattingCharacterInPattern('E', pattern): if entry[1] == 1: - info[entry] = u"%i" % weekday + info[entry] = "%i" % weekday elif entry[1] == 2: - info[entry] = u"%.2i" % weekday + info[entry] = "%.2i" % weekday elif entry[1] == 3: info[entry] = calendar.days[dt.weekday() + 1][1] else: diff --git a/src/zope/i18n/gettextmessagecatalog.py b/src/zope/i18n/gettextmessagecatalog.py index 8e1272b..85b0687 100644 --- a/src/zope/i18n/gettextmessagecatalog.py +++ b/src/zope/i18n/gettextmessagecatalog.py @@ -16,11 +16,13 @@ from functools import wraps from gettext import GNUTranslations -from zope.i18n.interfaces import IGlobalMessageCatalog + from zope.interface import implementer +from zope.i18n.interfaces import IGlobalMessageCatalog + -class _KeyErrorRaisingFallback(object): +class _KeyErrorRaisingFallback: def ugettext(self, message): raise KeyError(message) @@ -51,7 +53,7 @@ def plural_formatting(func): @implementer(IGlobalMessageCatalog) -class GettextMessageCatalog(object): +class GettextMessageCatalog: """A message catalog based on GNU gettext and Python's gettext module.""" _catalog = None diff --git a/src/zope/i18n/interfaces/__init__.py b/src/zope/i18n/interfaces/__init__.py index 025c477..78e5239 100644 --- a/src/zope/i18n/interfaces/__init__.py +++ b/src/zope/i18n/interfaces/__init__.py @@ -13,8 +13,12 @@ ############################################################################## """Internationalization of content objects. """ -from zope.interface import Interface, Attribute -from zope.schema import TextLine, Dict, Choice, Field +from zope.interface import Attribute +from zope.interface import Interface +from zope.schema import Choice +from zope.schema import Dict +from zope.schema import Field +from zope.schema import TextLine class II18nAware(Interface): @@ -85,13 +89,13 @@ class IMessageCatalog(Interface): """ language = TextLine( - title=u"Language", - description=u"The language the catalog translates to.", + title="Language", + description="The language the catalog translates to.", required=True) domain = TextLine( - title=u"Domain", - description=u"The domain the catalog is registered for.", + title="Domain", + description="The domain the catalog is registered for.", required=True) def getIdentifier(): @@ -149,8 +153,8 @@ class ITranslationDomain(Interface): """ domain = TextLine( - title=u"Domain Name", - description=u"The name of the domain this object represents.", + title="Domain Name", + description="The name of the domain this object represents.", required=True) def translate(msgid, mapping=None, context=None, target_language=None, @@ -177,7 +181,7 @@ class IFallbackTranslationDomainFactory(Interface): debugging i18n. """ - def __call__(domain_id=u""): + def __call__(domain_id=""): """Return a fallback translation domain for the given domain id. """ @@ -379,22 +383,22 @@ class INumberFormat(IFormat): """ type = Field( - title=u"Type", - description=((u"The type into which a string is parsed. If ``None``, " - u"then ``int`` will be used for whole numbers and " - u"``float`` for decimals.")), + title="Type", + description=("The type into which a string is parsed. If ``None``, " + "then ``int`` will be used for whole numbers and " + "``float`` for decimals."), default=None, required=False) symbols = Dict( - title=u"Number Symbols", + title="Number Symbols", key_type=Choice( - title=u"Dictionary Class", - values=(u"decimal", u"group", u"list", u"percentSign", - u"nativeZeroDigit", u"patternDigit", u"plusSign", - u"minusSign", u"exponential", u"perMille", - u"infinity", u"nan")), - value_type=TextLine(title=u"Symbol")) + title="Dictionary Class", + values=("decimal", "group", "list", "percentSign", + "nativeZeroDigit", "patternDigit", "plusSign", + "minusSign", "exponential", "perMille", + "infinity", "nan")), + value_type=TextLine(title="Symbol")) class IDateTimeFormat(IFormat): diff --git a/src/zope/i18n/interfaces/locales.py b/src/zope/i18n/interfaces/locales.py index 01c9cbe..83e40a3 100644 --- a/src/zope/i18n/interfaces/locales.py +++ b/src/zope/i18n/interfaces/locales.py @@ -15,10 +15,19 @@ """ import datetime import re -from zope.interface import Interface, Attribute -from zope.schema import \ - Field, Text, TextLine, Int, Bool, Tuple, List, Dict, Date + +from zope.interface import Attribute +from zope.interface import Interface +from zope.schema import Bool from zope.schema import Choice +from zope.schema import Date +from zope.schema import Dict +from zope.schema import Field +from zope.schema import Int +from zope.schema import List +from zope.schema import Text +from zope.schema import TextLine +from zope.schema import Tuple class ILocaleProvider(Interface): @@ -66,35 +75,35 @@ class ILocaleIdentity(Interface): """ language = TextLine( - title=u"Language Type", - description=u"The language for which a locale is applicable.", + title="Language Type", + description="The language for which a locale is applicable.", constraint=re.compile(r'[a-z]{2}').match, required=True, readonly=True) script = TextLine( - title=u"Script Type", - description=(u"""The script for which the language/locale is + title="Script Type", + description=("""The script for which the language/locale is applicable."""), constraint=re.compile(r'[a-z]*').match) territory = TextLine( - title=u"Territory Type", - description=u"The territory for which a locale is applicable.", + title="Territory Type", + description="The territory for which a locale is applicable.", constraint=re.compile(r'[A-Z]{2}').match, required=True, readonly=True) variant = TextLine( - title=u"Variant Type", - description=u"The variant for which a locale is applicable.", + title="Variant Type", + description="The variant for which a locale is applicable.", constraint=re.compile(r'[a-zA-Z]*').match, required=True, readonly=True) version = Field( - title=u"Locale Version", - description=u"The value of this field is an ILocaleVersion object.", + title="Locale Version", + description="The value of this field is an ILocaleVersion object.", readonly=True) def __repr__(self): @@ -109,21 +118,21 @@ class ILocaleVersion(Interface): """ number = TextLine( - title=u"Version Number", - description=u"The version number of the locale.", + title="Version Number", + description="The version number of the locale.", constraint=re.compile(r'^([0-9].)*[0-9]$').match, required=True, readonly=True) generationDate = Date( - title=u"Generation Date", - description=u"Specifies the creation date of the locale.", + title="Generation Date", + description="Specifies the creation date of the locale.", constraint=lambda date: date < datetime.datetime.now(), readonly=True) notes = Text( - title=u"Notes", - description=u"Some release notes for the version of this locale.", + title="Notes", + description="Some release notes for the version of this locale.", readonly=True) @@ -136,34 +145,34 @@ class ILocaleDisplayNames(Interface): """ languages = Dict( - title=u"Language type to translated name", - key_type=TextLine(title=u"Language Type"), - value_type=TextLine(title=u"Language Name")) + title="Language type to translated name", + key_type=TextLine(title="Language Type"), + value_type=TextLine(title="Language Name")) scripts = Dict( - title=u"Script type to script name", - key_type=TextLine(title=u"Script Type"), - value_type=TextLine(title=u"Script Name")) + title="Script type to script name", + key_type=TextLine(title="Script Type"), + value_type=TextLine(title="Script Name")) territories = Dict( - title=u"Territory type to translated territory name", - key_type=TextLine(title=u"Territory Type"), - value_type=TextLine(title=u"Territory Name")) + title="Territory type to translated territory name", + key_type=TextLine(title="Territory Type"), + value_type=TextLine(title="Territory Name")) variants = Dict( - title=u"Variant type to name", - key_type=TextLine(title=u"Variant Type"), - value_type=TextLine(title=u"Variant Name")) + title="Variant type to name", + key_type=TextLine(title="Variant Type"), + value_type=TextLine(title="Variant Name")) keys = Dict( - title=u"Key type to name", - key_type=TextLine(title=u"Key Type"), - value_type=TextLine(title=u"Key Name")) + title="Key type to name", + key_type=TextLine(title="Key Type"), + value_type=TextLine(title="Key Name")) types = Dict( - title=u"Type type and key to localized name", - key_type=Tuple(title=u"Type Type and Key"), - value_type=TextLine(title=u"Type Name")) + title="Type type and key to localized name", + key_type=Tuple(title="Type Type and Key"), + value_type=TextLine(title="Type Name")) class ILocaleTimeZone(Interface): @@ -176,25 +185,25 @@ class ILocaleTimeZone(Interface): """ type = TextLine( - title=u"Time Zone Type", - description=u"Standard name of the timezone for unique referencing.", + title="Time Zone Type", + description="Standard name of the timezone for unique referencing.", required=True, readonly=True) cities = List( - title=u"Cities", - description=u"Cities in Timezone", - value_type=TextLine(title=u"City Name"), + title="Cities", + description="Cities in Timezone", + value_type=TextLine(title="City Name"), required=True, readonly=True) names = Dict( - title=u"Time Zone Names", - description=u"Various names of the timezone.", + title="Time Zone Names", + description="Various names of the timezone.", key_type=Choice( - title=u"Time Zone Name Type", - values=(u"generic", u"standard", u"daylight")), - value_type=Tuple(title=u"Time Zone Name and Abbreviation", + title="Time Zone Name Type", + values=("generic", "standard", "daylight")), + value_type=Tuple(title="Time Zone Name and Abbreviation", min_length=2, max_length=2), required=True, readonly=True) @@ -204,20 +213,20 @@ class ILocaleFormat(Interface): """Specifies a format for a particular type of data.""" type = TextLine( - title=u"Format Type", - description=u"The name of the format", + title="Format Type", + description="The name of the format", required=False, readonly=True) displayName = TextLine( - title=u"Display Name", - description=u"Name of the calendar, for example 'gregorian'.", + title="Display Name", + description="Name of the calendar, for example 'gregorian'.", required=False, readonly=True) pattern = TextLine( - title=u"Format Pattern", - description=u"The pattern that is used to format the object.", + title="Format Pattern", + description="The pattern that is used to format the object.", required=True, readonly=True) @@ -226,22 +235,22 @@ class ILocaleFormatLength(Interface): """The format length describes a class of formats.""" type = Choice( - title=u"Format Length Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short") + title="Format Length Type", + description="Name of the format length", + values=("full", "long", "medium", "short") ) default = TextLine( - title=u"Default Format", - description=u"The name of the defaulkt format.") + title="Default Format", + description="The name of the defaulkt format.") formats = Dict( - title=u"Formats", - description=u"Maps format types to format objects", - key_type=TextLine(title=u"Format Type"), + title="Formats", + description="Maps format types to format objects", + key_type=TextLine(title="Format Type"), value_type=Field( - title=u"Format Object", - description=u"Values are ILocaleFormat objects."), + title="Format Object", + description="Values are ILocaleFormat objects."), required=True, readonly=True) @@ -250,24 +259,24 @@ class ILocaleMonthContext(Interface): """Specifices a usage context for month names""" type = TextLine( - title=u"Month context type", - description=u"Name of the month context, format or stand-alone.") + title="Month context type", + description="Name of the month context, format or stand-alone.") defaultWidth = TextLine( - title=u"Default month name width", - default=u"wide") + title="Default month name width", + default="wide") months = Dict( - title=u"Month Names", - description=(u"A mapping of month name widths to a mapping of" - u"corresponding month names."), + title="Month Names", + description=("A mapping of month name widths to a mapping of" + "corresponding month names."), key_type=Choice( - title=u"Width type", - values=(u"wide", u"abbreviated", u"narrow")), + title="Width type", + values=("wide", "abbreviated", "narrow")), value_type=Dict( - title=u"Month name", - key_type=Int(title=u"Type", min=1, max=12), - value_type=TextLine(title=u"Month Name")) + title="Month name", + key_type=Int(title="Type", min=1, max=12), + value_type=TextLine(title="Month Name")) ) @@ -275,27 +284,27 @@ class ILocaleDayContext(Interface): """Specifices a usage context for days names""" type = TextLine( - title=u"Day context type", - description=u"Name of the day context, format or stand-alone.") + title="Day context type", + description="Name of the day context, format or stand-alone.") defaultWidth = TextLine( - title=u"Default day name width", - default=u"wide") + title="Default day name width", + default="wide") days = Dict( - title=u"Day Names", - description=(u"A mapping of day name widths to a mapping of" - u"corresponding day names."), + title="Day Names", + description=("A mapping of day name widths to a mapping of" + "corresponding day names."), key_type=Choice( - title=u"Width type", - values=(u"wide", u"abbreviated", u"narrow")), + title="Width type", + values=("wide", "abbreviated", "narrow")), value_type=Dict( - title=u"Day name", + title="Day name", key_type=Choice( - title=u"Type", - values=(u"sun", u"mon", u"tue", u"wed", - u"thu", u"fri", u"sat")), - value_type=TextLine(title=u"Day Name")) + title="Type", + values=("sun", "mon", "tue", "wed", + "thu", "fri", "sat")), + value_type=TextLine(title="Day Name")) ) @@ -304,57 +313,57 @@ class ILocaleCalendar(Interface): which made it attractive to be added.""" type = TextLine( - title=u"Calendar Type", - description=u"Name of the calendar, for example 'gregorian'.") + title="Calendar Type", + description="Name of the calendar, for example 'gregorian'.") defaultMonthContext = TextLine( - title=u"Default month context", - default=u"format") + title="Default month context", + default="format") monthContexts = Dict( - title=u"Month Contexts", - description=(u"A mapping of month context types to " - u"ILocaleMonthContext objects"), - key_type=Choice(title=u"Type", - values=(u"format", u"stand-alone")), - value_type=Field(title=u"ILocaleMonthContext object")) + title="Month Contexts", + description=("A mapping of month context types to " + "ILocaleMonthContext objects"), + key_type=Choice(title="Type", + values=("format", "stand-alone")), + value_type=Field(title="ILocaleMonthContext object")) # BBB: leftover from CLDR 1.0 months = Dict( - title=u"Month Names", - description=u"A mapping of all month names and abbreviations", - key_type=Int(title=u"Type", min=1, max=12), - value_type=Tuple(title=u"Month Name and Abbreviation", + title="Month Names", + description="A mapping of all month names and abbreviations", + key_type=Int(title="Type", min=1, max=12), + value_type=Tuple(title="Month Name and Abbreviation", min_length=2, max_length=2)) defaultDayContext = TextLine( - title=u"Default day context", - default=u"format") + title="Default day context", + default="format") dayContexts = Dict( - title=u"Day Contexts", - description=(u"A mapping of day context types to " - u"ILocaleDayContext objects"), - key_type=Choice(title=u"Type", - values=(u"format", u"stand-alone")), - value_type=Field(title=u"ILocaleDayContext object")) + title="Day Contexts", + description=("A mapping of day context types to " + "ILocaleDayContext objects"), + key_type=Choice(title="Type", + values=("format", "stand-alone")), + value_type=Field(title="ILocaleDayContext object")) # BBB: leftover from CLDR 1.0 days = Dict( - title=u"Weekdays Names", - description=u"A mapping of all month names and abbreviations", - key_type=Choice(title=u"Type", - values=(u"sun", u"mon", u"tue", u"wed", - u"thu", u"fri", u"sat")), - value_type=Tuple(title=u"Weekdays Name and Abbreviation", + title="Weekdays Names", + description="A mapping of all month names and abbreviations", + key_type=Choice(title="Type", + values=("sun", "mon", "tue", "wed", + "thu", "fri", "sat")), + value_type=Tuple(title="Weekdays Name and Abbreviation", min_length=2, max_length=2)) week = Dict( - title=u"Week Information", - description=u"Contains various week information", + title="Week Information", + description="Contains various week information", key_type=Choice( - title=u"Type", - description=(u""" + title="Type", + description=(""" Varies Week information: - 'minDays' is just an integer between 1 and 7. @@ -364,51 +373,51 @@ class ILocaleCalendar(Interface): - The 'weekendStart' and 'weekendEnd' are tuples of the form (weekDayNumber, datetime.time) """), - values=(u"minDays", u"firstDay", - u"weekendStart", u"weekendEnd"))) + values=("minDays", "firstDay", + "weekendStart", "weekendEnd"))) - am = TextLine(title=u"AM String") + am = TextLine(title="AM String") - pm = TextLine(title=u"PM String") + pm = TextLine(title="PM String") eras = Dict( - title=u"Era Names", - key_type=Int(title=u"Type", min=0), - value_type=Tuple(title=u"Era Name and Abbreviation", + title="Era Names", + key_type=Int(title="Type", min=0), + value_type=Tuple(title="Era Name and Abbreviation", min_length=2, max_length=2)) - defaultDateFormat = TextLine(title=u"Default Date Format Type") + defaultDateFormat = TextLine(title="Default Date Format Type") dateFormats = Dict( - title=u"Date Formats", - description=u"Contains various Date Formats.", + title="Date Formats", + description="Contains various Date Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) - defaultTimeFormat = TextLine(title=u"Default Time Format Type") + defaultTimeFormat = TextLine(title="Default Time Format Type") timeFormats = Dict( - title=u"Time Formats", - description=u"Contains various Time Formats.", + title="Time Formats", + description="Contains various Time Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) - defaultDateTimeFormat = TextLine(title=u"Default Date-Time Format Type") + defaultDateTimeFormat = TextLine(title="Default Date-Time Format Type") dateTimeFormats = Dict( - title=u"Date-Time Formats", - description=u"Contains various Date-Time Formats.", + title="Date-Time Formats", + description="Contains various Date-Time Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) def getMonthNames(): """Return a list of month names.""" @@ -445,30 +454,30 @@ class ILocaleDates(Interface): """This object contains various data about dates, times and time zones.""" localizedPatternChars = TextLine( - title=u"Localized Pattern Characters", - description=u"Localized pattern characters used in dates and times") + title="Localized Pattern Characters", + description="Localized pattern characters used in dates and times") calendars = Dict( - title=u"Calendar type to ILocaleCalendar", + title="Calendar type to ILocaleCalendar", key_type=Choice( - title=u"Calendar Type", - values=(u"gregorian", - u"arabic", - u"chinese", - u"civil-arabic", - u"hebrew", - u"japanese", - u"thai-buddhist")), - value_type=Field(title=u"Calendar", - description=u"This is a ILocaleCalendar object.")) + title="Calendar Type", + values=("gregorian", + "arabic", + "chinese", + "civil-arabic", + "hebrew", + "japanese", + "thai-buddhist")), + value_type=Field(title="Calendar", + description="This is a ILocaleCalendar object.")) timezones = Dict( - title=u"Time zone type to ILocaleTimezone", - key_type=TextLine(title=u"Time Zone type"), - value_type=Field(title=u"Time Zone", - description=u"This is a ILocaleTimeZone object.")) + title="Time zone type to ILocaleTimezone", + key_type=TextLine(title="Time Zone type"), + value_type=Field(title="Time Zone", + description="This is a ILocaleTimeZone object.")) - def getFormatter(category, length=None, name=None, calendar=u"gregorian"): + def getFormatter(category, length=None, name=None, calendar="gregorian"): """Get a date/time formatter. `category` must be one of 'date', 'dateTime', 'time'. @@ -482,81 +491,81 @@ class ILocaleDates(Interface): class ILocaleCurrency(Interface): """Defines a particular currency.""" - type = TextLine(title=u"Type") + type = TextLine(title="Type") - symbol = TextLine(title=u"Symbol") + symbol = TextLine(title="Symbol") - displayName = TextLine(title=u"Official Name") + displayName = TextLine(title="Official Name") - symbolChoice = Bool(title=u"Symbol Choice") + symbolChoice = Bool(title="Symbol Choice") class ILocaleNumbers(Interface): """This object contains various data about numbers and currencies.""" symbols = Dict( - title=u"Number Symbols", + title="Number Symbols", key_type=Choice( - title=u"Format Name", - values=(u"decimal", u"group", u"list", u"percentSign", - u"nativeZeroDigit", u"patternDigit", u"plusSign", - u"minusSign", u"exponential", u"perMille", - u"infinity", u"nan")), - value_type=TextLine(title=u"Symbol")) + title="Format Name", + values=("decimal", "group", "list", "percentSign", + "nativeZeroDigit", "patternDigit", "plusSign", + "minusSign", "exponential", "perMille", + "infinity", "nan")), + value_type=TextLine(title="Symbol")) - defaultDecimalFormat = TextLine(title=u"Default Decimal Format Type") + defaultDecimalFormat = TextLine(title="Default Decimal Format Type") decimalFormats = Dict( - title=u"Decimal Formats", - description=u"Contains various Decimal Formats.", + title="Decimal Formats", + description="Contains various Decimal Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) - defaultScientificFormat = TextLine(title=u"Default Scientific Format Type") + defaultScientificFormat = TextLine(title="Default Scientific Format Type") scientificFormats = Dict( - title=u"Scientific Formats", - description=u"Contains various Scientific Formats.", + title="Scientific Formats", + description="Contains various Scientific Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) - defaultPercentFormat = TextLine(title=u"Default Percent Format Type") + defaultPercentFormat = TextLine(title="Default Percent Format Type") percentFormats = Dict( - title=u"Percent Formats", - description=u"Contains various Percent Formats.", + title="Percent Formats", + description="Contains various Percent Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) - defaultCurrencyFormat = TextLine(title=u"Default Currency Format Type") + defaultCurrencyFormat = TextLine(title="Default Currency Format Type") currencyFormats = Dict( - title=u"Currency Formats", - description=u"Contains various Currency Formats.", + title="Currency Formats", + description="Contains various Currency Formats.", key_type=Choice( - title=u"Type", - description=u"Name of the format length", - values=(u"full", u"long", u"medium", u"short")), - value_type=Field(title=u"ILocaleFormatLength object")) + title="Type", + description="Name of the format length", + values=("full", "long", "medium", "short")), + value_type=Field(title="ILocaleFormatLength object")) currencies = Dict( - title=u"Currencies", - description=u"Contains various Currency data.", + title="Currencies", + description="Contains various Currency data.", key_type=TextLine( - title=u"Type", - description=u"Name of the format length"), - value_type=Field(title=u"ILocaleCurrency object")) + title="Type", + description="Name of the format length"), + value_type=Field(title="ILocaleCurrency object")) - def getFormatter(category, length=None, name=u""): + def getFormatter(category, length=None, name=""): """Get the NumberFormat based on the category, length and name of the format. @@ -577,23 +586,23 @@ class ILocaleNumbers(Interface): """Get the default currency.""" -_orientations = [u"left-to-right", u"right-to-left", - u"top-to-bottom", u"bottom-to-top"] +_orientations = ["left-to-right", "right-to-left", + "top-to-bottom", "bottom-to-top"] class ILocaleOrientation(Interface): """Information about the orientation of text.""" characters = Choice( - title=u"Orientation of characters", + title="Orientation of characters", values=_orientations, - default=u"left-to-right" + default="left-to-right" ) lines = Choice( - title=u"Orientation of characters", + title="Orientation of characters", values=_orientations, - default=u"top-to-bottom" + default="top-to-bottom" ) @@ -610,39 +619,39 @@ class ILocale(Interface): """ id = Field( - title=u"Locale identity", - description=u"ILocaleIdentity object identifying the locale.", + title="Locale identity", + description="ILocaleIdentity object identifying the locale.", required=True, readonly=True) displayNames = Field( - title=u"Display Names", - description=(u"""ILocaleDisplayNames object that contains localized + title="Display Names", + description=("""ILocaleDisplayNames object that contains localized names.""")) dates = Field( - title=u"Dates", - description=u"ILocaleDates object that contains date/time data.") + title="Dates", + description="ILocaleDates object that contains date/time data.") numbers = Field( - title=u"Numbers", - description=u"ILocaleNumbers object that contains number data.") + title="Numbers", + description="ILocaleNumbers object that contains number data.") orientation = Field( - title=u"Orientation", - description=u"ILocaleOrientation with text orientation info.") + title="Orientation", + description="ILocaleOrientation with text orientation info.") delimiters = Dict( - title=u"Delimiters", - description=u"Contains various Currency data.", + title="Delimiters", + description="Contains various Currency data.", key_type=Choice( - title=u"Delimiter Type", - description=u"Delimiter name.", - values=(u"quotationStart", - u"quotationEnd", - u"alternateQuotationStart", - u"alternateQuotationEnd")), - value_type=Field(title=u"Delimiter symbol")) + title="Delimiter Type", + description="Delimiter name.", + values=("quotationStart", + "quotationEnd", + "alternateQuotationStart", + "alternateQuotationEnd")), + value_type=Field(title="Delimiter symbol")) def getLocaleID(): """Return a locale id as specified in the LDML specification""" @@ -659,8 +668,8 @@ class ILocaleInheritance(Interface): __parent__ = Attribute("The parent in the location hierarchy") __name__ = TextLine( - title=u"The name within the parent", - description=(u"""The parent can be traversed with this name to get + title="The name within the parent", + description=("""The parent can be traversed with this name to get the object.""")) def getInheritedSelf(): diff --git a/src/zope/i18n/locales/__init__.py b/src/zope/i18n/locales/__init__.py index 12009a6..8ecb911 100644 --- a/src/zope/i18n/locales/__init__.py +++ b/src/zope/i18n/locales/__init__.py @@ -19,24 +19,33 @@ import os from datetime import date from zope.interface import implementer + +# Setup the locale directory +from zope import i18n +from zope.i18n.format import DateTimeFormat +from zope.i18n.format import NumberFormat from zope.i18n.interfaces.locales import ILocale -from zope.i18n.interfaces.locales import ILocaleDisplayNames, ILocaleDates -from zope.i18n.interfaces.locales import ILocaleVersion, ILocaleIdentity -from zope.i18n.interfaces.locales import ILocaleTimeZone, ILocaleCalendar -from zope.i18n.interfaces.locales import ILocaleCurrency, ILocaleNumbers -from zope.i18n.interfaces.locales import ILocaleFormat, ILocaleFormatLength +from zope.i18n.interfaces.locales import ILocaleCalendar +from zope.i18n.interfaces.locales import ILocaleCurrency +from zope.i18n.interfaces.locales import ILocaleDates +from zope.i18n.interfaces.locales import ILocaleDayContext +from zope.i18n.interfaces.locales import ILocaleDisplayNames +from zope.i18n.interfaces.locales import ILocaleFormat +from zope.i18n.interfaces.locales import ILocaleFormatLength +from zope.i18n.interfaces.locales import ILocaleIdentity +from zope.i18n.interfaces.locales import ILocaleMonthContext +from zope.i18n.interfaces.locales import ILocaleNumbers from zope.i18n.interfaces.locales import ILocaleOrientation -from zope.i18n.interfaces.locales import ILocaleDayContext, ILocaleMonthContext -from zope.i18n.format import NumberFormat, DateTimeFormat -from zope.i18n.locales.inheritance import \ - AttributeInheritance, InheritingDictionary, NoParentException +from zope.i18n.interfaces.locales import ILocaleTimeZone +from zope.i18n.interfaces.locales import ILocaleVersion +from zope.i18n.locales.inheritance import AttributeInheritance +from zope.i18n.locales.inheritance import InheritingDictionary +from zope.i18n.locales.inheritance import NoParentException # LoadLocaleError is not used, but might be imported from here by others. from zope.i18n.locales.provider import LoadLocaleError # noqa from zope.i18n.locales.provider import LocaleProvider -# Setup the locale directory -from zope import i18n LOCALEDIR = os.path.join(os.path.dirname(i18n.__file__), "locales", "data") # Global LocaleProvider. We really just need this single one. @@ -77,7 +86,7 @@ calendarAliases = {'islamic': ('arabic',), @implementer(ILocaleIdentity) -class LocaleIdentity(object): +class LocaleIdentity: """Represents a unique identification of the locale This class does not have to deal with inheritance. @@ -116,12 +125,12 @@ class LocaleIdentity(object): def __repr__(self): """See zope.i18n.interfaces.ILocaleIdentity """ - return "<LocaleIdentity (%s, %s, %s, %s)>" % ( + return "<LocaleIdentity ({}, {}, {}, {})>".format( self.language, self.script, self.territory, self.variant) @implementer(ILocaleVersion) -class LocaleVersion(object): +class LocaleVersion: """Represents a particular version of a locale This class does not have to deal with inheritance. @@ -190,7 +199,7 @@ class LocaleDisplayNames(AttributeInheritance): @implementer(ILocaleTimeZone) -class LocaleTimeZone(object): +class LocaleTimeZone: """Specifies one of the timezones of a specific locale. The attributes of this class are not inherited, since all timezone @@ -217,7 +226,7 @@ class LocaleTimeZone(object): @implementer(ILocaleFormat) -class LocaleFormat(object): +class LocaleFormat: """Specifies one of the format of a specific format length. The attributes of this class are not inherited, since all format @@ -229,8 +238,8 @@ class LocaleFormat(object): def __init__(self, type=None): """Initialize the object.""" self.type = type - self.displayName = u"" - self.pattern = u"" + self.displayName = "" + self.pattern = "" @implementer(ILocaleFormatLength) @@ -250,7 +259,7 @@ class LocaleMonthContext(AttributeInheritance): def __init__(self, type=None): """Initialize the object.""" self.type = type - self.default = u"wide" + self.default = "wide" @implementer(ILocaleDayContext) @@ -259,7 +268,7 @@ class LocaleDayContext(AttributeInheritance): def __init__(self, type=None): """Initialize the object.""" self.type = type - self.default = u"wide" + self.default = "wide" @implementer(ILocaleCalendar) @@ -498,15 +507,15 @@ class LocaleDates(AttributeInheritance): """ def getFormatter(self, category, length=None, name=None, - calendar=u"gregorian"): + calendar="gregorian"): """See zope.i18n.interfaces.locales.ILocaleDates""" - if category not in (u"date", u"time", u"dateTime"): + if category not in ("date", "time", "dateTime"): raise ValueError('Invalid category: %s' % category) - if calendar not in (u"gregorian", u"arabic", u"chinese", - u"civil-arabic", u"hebrew", u"japanese", - u"thai-buddhist"): + if calendar not in ("gregorian", "arabic", "chinese", + "civil-arabic", "hebrew", "japanese", + "thai-buddhist"): raise ValueError('Invalid calendar: %s' % calendar) - if length not in (u"short", u"medium", u"long", u"full", None): + if length not in ("short", "medium", "long", "full", None): raise ValueError('Invalid format length: %s' % length) cal = self.calendars[calendar] @@ -544,7 +553,7 @@ class LocaleDates(AttributeInheritance): @implementer(ILocaleCurrency) -class LocaleCurrency(object): +class LocaleCurrency: """Simple implementation of ILocaleCurrency without inheritance support, since it is not needed for a single currency.""" @@ -634,8 +643,8 @@ class LocaleNumbers(AttributeInheritance): def getFormatter(self, category, length=None, name=None): """See zope.i18n.interfaces.locales.ILocaleNumbers""" - assert category in (u"decimal", u"percent", u"scientific", u"currency") - assert length in (u"short", u"medium", u"long", u"full", None) + assert category in ("decimal", "percent", "scientific", "currency") + assert length in ("short", "medium", "long", "full", None) formats = getattr(self, category + 'Formats') if length is None: diff --git a/src/zope/i18n/locales/inheritance.py b/src/zope/i18n/locales/inheritance.py index e597b78..809b6fe 100644 --- a/src/zope/i18n/locales/inheritance.py +++ b/src/zope/i18n/locales/inheritance.py @@ -21,10 +21,11 @@ locale inheritance is not inheritance in the programming sense. __docformat__ = 'restructuredtext' from zope.deprecation import deprecate - from zope.interface import implementer -from zope.i18n.interfaces.locales import \ - ILocaleInheritance, IAttributeInheritance, IDictionaryInheritance + +from zope.i18n.interfaces.locales import IAttributeInheritance +from zope.i18n.interfaces.locales import IDictionaryInheritance +from zope.i18n.interfaces.locales import ILocaleInheritance class NoParentException(AttributeError): @@ -32,7 +33,7 @@ class NoParentException(AttributeError): @implementer(ILocaleInheritance) -class Inheritance(object): +class Inheritance: """A simple base version of locale inheritance. This object contains some shared code amongst the various @@ -109,7 +110,7 @@ class AttributeInheritance(Inheritance): not name.startswith('__')): value.__parent__ = self value.__name__ = name - super(AttributeInheritance, self).__setattr__(name, value) + super().__setattr__(name, value) def __getattr__(self, name): """See zope.i18n.interfaces.locales.ILocaleInheritance""" @@ -129,7 +130,7 @@ class AttributeInheritance(Inheritance): # Note that we cannot use the normal setattr function, since # __setattr__ of this class tries to assign a parent and name, # which we do not want to override. - super(AttributeInheritance, self).__setattr__(name, value) + super().__setattr__(name, value) return value @@ -200,7 +201,7 @@ class InheritingDictionary(Inheritance, dict): if ILocaleInheritance.providedBy(value): value.__parent__ = self value.__name__ = name - super(InheritingDictionary, self).__setitem__(name, value) + super().__setitem__(name, value) def __getitem__(self, name): """See zope.i18n.interfaces.locales.ILocaleInheritance""" @@ -211,7 +212,7 @@ class InheritingDictionary(Inheritance, dict): pass else: return selfUp.__getitem__(name) - return super(InheritingDictionary, self).__getitem__(name) + return super().__getitem__(name) def get(self, name, default=None): """See zope.i18n.interfaces.locales.ILocaleInheritance""" diff --git a/src/zope/i18n/locales/provider.py b/src/zope/i18n/locales/provider.py index 9e7f66b..6396898 100644 --- a/src/zope/i18n/locales/provider.py +++ b/src/zope/i18n/locales/provider.py @@ -17,7 +17,9 @@ The Locale Provider looks up locales and loads them from the XML data, if necessary. """ import os + from zope.interface import implementer + from zope.i18n.interfaces.locales import ILocaleProvider @@ -26,7 +28,7 @@ class LoadLocaleError(Exception): @implementer(ILocaleProvider) -class LocaleProvider(object): +class LocaleProvider: """A locale provider that gets its data from the XML data.""" def __init__(self, locale_dir): diff --git a/src/zope/i18n/locales/tests/test_docstrings.py b/src/zope/i18n/locales/tests/test_docstrings.py index faa4e97..59a8f52 100644 --- a/src/zope/i18n/locales/tests/test_docstrings.py +++ b/src/zope/i18n/locales/tests/test_docstrings.py @@ -15,9 +15,9 @@ """ import unittest from doctest import DocTestSuite + from zope.i18n.locales.inheritance import AttributeInheritance from zope.i18n.locales.inheritance import NoParentException - from zope.i18n.testing import unicode_checker diff --git a/src/zope/i18n/locales/tests/test_fallbackcollator.py b/src/zope/i18n/locales/tests/test_fallbackcollator.py index ad78b72..f3a2761 100644 --- a/src/zope/i18n/locales/tests/test_fallbackcollator.py +++ b/src/zope/i18n/locales/tests/test_fallbackcollator.py @@ -12,8 +12,8 @@ # ############################################################################## -import unittest import doctest +import unittest from zope.i18n.testing import unicode_checker diff --git a/src/zope/i18n/locales/tests/test_locales.py b/src/zope/i18n/locales/tests/test_locales.py index 523b1d2..42968cc 100644 --- a/src/zope/i18n/locales/tests/test_locales.py +++ b/src/zope/i18n/locales/tests/test_locales.py @@ -13,19 +13,21 @@ ############################################################################## """This module tests the LocaleProvider and everything that goes with it. """ -import os import datetime +import os from unittest import TestCase +import zope.i18n from zope.i18n.interfaces.locales import ILocaleProvider from zope.i18n.locales import locales -from zope.i18n.locales.provider import LocaleProvider, LoadLocaleError +from zope.i18n.locales.provider import LoadLocaleError +from zope.i18n.locales.provider import LocaleProvider + -import zope.i18n datadir = os.path.join(os.path.dirname(zope.i18n.__file__), 'locales', 'data') -class AbstractTestILocaleProviderMixin(object): +class AbstractTestILocaleProviderMixin: """Test the functionality of an implmentation of the ILocaleProvider interface.""" diff --git a/src/zope/i18n/locales/tests/test_xmlfactory.py b/src/zope/i18n/locales/tests/test_xmlfactory.py index e11097c..e3e82f9 100644 --- a/src/zope/i18n/locales/tests/test_xmlfactory.py +++ b/src/zope/i18n/locales/tests/test_xmlfactory.py @@ -14,10 +14,11 @@ """Testing all XML Locale functionality. """ import os -from unittest import TestCase, TestSuite +from unittest import TestCase +from unittest import TestSuite -from zope.i18n.locales.xmlfactory import LocaleFactory import zope.i18n +from zope.i18n.locales.xmlfactory import LocaleFactory class LocaleXMLFileTestCase(TestCase): diff --git a/src/zope/i18n/locales/xmlfactory.py b/src/zope/i18n/locales/xmlfactory.py index f1ed219..c6ac415 100644 --- a/src/zope/i18n/locales/xmlfactory.py +++ b/src/zope/i18n/locales/xmlfactory.py @@ -13,18 +13,30 @@ ############################################################################## """XML Locale-related objects and functions """ -from datetime import date, time +from datetime import date +from datetime import time from xml.dom.minidom import parse as parseXML -from zope.i18n.locales import Locale, LocaleDisplayNames, LocaleDates -from zope.i18n.locales import LocaleVersion, LocaleIdentity, LocaleTimeZone -from zope.i18n.locales import LocaleCalendar, LocaleCurrency, LocaleNumbers -from zope.i18n.locales import LocaleFormat, LocaleFormatLength, dayMapping -from zope.i18n.locales import LocaleOrientation, LocaleDayContext -from zope.i18n.locales import LocaleMonthContext, calendarAliases + +from zope.i18n.locales import Locale +from zope.i18n.locales import LocaleCalendar +from zope.i18n.locales import LocaleCurrency +from zope.i18n.locales import LocaleDates +from zope.i18n.locales import LocaleDayContext +from zope.i18n.locales import LocaleDisplayNames +from zope.i18n.locales import LocaleFormat +from zope.i18n.locales import LocaleFormatLength +from zope.i18n.locales import LocaleIdentity +from zope.i18n.locales import LocaleMonthContext +from zope.i18n.locales import LocaleNumbers +from zope.i18n.locales import LocaleOrientation +from zope.i18n.locales import LocaleTimeZone +from zope.i18n.locales import LocaleVersion +from zope.i18n.locales import calendarAliases +from zope.i18n.locales import dayMapping from zope.i18n.locales.inheritance import InheritingDictionary -class LocaleFactory(object): +class LocaleFactory: """This class creates a Locale object from an ICU XML file.""" def __init__(self, path): @@ -35,7 +47,7 @@ class LocaleFactory(object): self._data = parseXML(path).documentElement def _getText(self, nodelist): - rc = u'' + rc = '' for node in nodelist: if node.nodeType == node.TEXT_NODE: rc = rc + node.data @@ -983,7 +995,7 @@ class LocaleFactory(object): # get the short and long name node long = node.getElementsByTagName('long') short = node.getElementsByTagName('short') - for type in (u"generic", u"standard", u"daylight"): + for type in ("generic", "standard", "daylight"): # get long name long_desc = None if long: @@ -1066,10 +1078,10 @@ class LocaleFactory(object): return symbols = InheritingDictionary() - for name in (u"decimal", u"group", u"list", u"percentSign", - u"nativeZeroDigit", u"patternDigit", u"plusSign", - u"minusSign", u"exponential", u"perMille", - u"infinity", u"nan"): + for name in ("decimal", "group", "list", "percentSign", + "nativeZeroDigit", "patternDigit", "plusSign", + "minusSign", "exponential", "perMille", + "infinity", "nan"): nodes = symbols_nodes[0].getElementsByTagName(name) if nodes: symbols[name] = self._getText(nodes[0].childNodes) @@ -1225,7 +1237,7 @@ class LocaleFactory(object): if nodes: currency.symbol = self._getText(nodes[0].childNodes) currency.symbolChoice = \ - nodes[0].getAttribute('choice') == u"true" + nodes[0].getAttribute('choice') == "true" nodes = curr_node.getElementsByTagName('displayName') if nodes: @@ -1298,8 +1310,8 @@ class LocaleFactory(object): return delimiters = InheritingDictionary() - for name in (u'quotationStart', u"quotationEnd", - u"alternateQuotationStart", u"alternateQuotationEnd"): + for name in ('quotationStart', "quotationEnd", + "alternateQuotationStart", "alternateQuotationEnd"): nodes = delimiters_nodes[0].getElementsByTagName(name) if nodes: delimiters[name] = self._getText(nodes[0].childNodes) @@ -1330,7 +1342,7 @@ class LocaleFactory(object): if not orientation_nodes: return orientation = LocaleOrientation() - for name in (u"characters", u"lines"): + for name in ("characters", "lines"): value = orientation_nodes[0].getAttribute(name) if value: setattr(orientation, name, value) diff --git a/src/zope/i18n/negotiator.py b/src/zope/i18n/negotiator.py index 3ece221..b790a96 100644 --- a/src/zope/i18n/negotiator.py +++ b/src/zope/i18n/negotiator.py @@ -14,6 +14,7 @@ """Language Negotiator """ from zope.interface import implementer + from zope.i18n.interfaces import INegotiator from zope.i18n.interfaces import IUserPreferredLanguages @@ -35,7 +36,7 @@ def normalize_langs(langs): @implementer(INegotiator) -class Negotiator(object): +class Negotiator: def getLanguage(self, langs, env): envadapter = IUserPreferredLanguages(env) diff --git a/src/zope/i18n/simpletranslationdomain.py b/src/zope/i18n/simpletranslationdomain.py index b20385a..46e0020 100644 --- a/src/zope/i18n/simpletranslationdomain.py +++ b/src/zope/i18n/simpletranslationdomain.py @@ -13,15 +13,16 @@ ############################################################################## """This is a simple implementation of the ITranslationDomain interface. """ -from zope.interface import implementer from zope.component import getUtility -from zope.i18n._compat import text_type -from zope.i18n.interfaces import ITranslationDomain, INegotiator +from zope.interface import implementer + from zope.i18n import interpolate +from zope.i18n.interfaces import INegotiator +from zope.i18n.interfaces import ITranslationDomain @implementer(ITranslationDomain) -class SimpleTranslationDomain(object): +class SimpleTranslationDomain: """This is the simplest implementation of the ITranslationDomain I could come up with. @@ -58,7 +59,7 @@ class SimpleTranslationDomain(object): # Find a translation; if nothing is found, use the default # value if default is None: - default = text_type(msgid) + default = str(msgid) text = self.messages.get((target_language, msgid)) if text is None: text = default diff --git a/src/zope/i18n/testing.py b/src/zope/i18n/testing.py index 035827c..37b9b51 100644 --- a/src/zope/i18n/testing.py +++ b/src/zope/i18n/testing.py @@ -21,6 +21,7 @@ import re from zope.testing import renormalizing + rules = [] if bytes is not str: rules = [ @@ -38,7 +39,7 @@ def setUp(test=None): zope.component.provideAdapter(BrowserLanguages) -class PlacelessSetup(object): +class PlacelessSetup: def setUp(self): """ diff --git a/src/zope/i18n/testmessagecatalog.py b/src/zope/i18n/testmessagecatalog.py index b88b3d7..17a0925 100644 --- a/src/zope/i18n/testmessagecatalog.py +++ b/src/zope/i18n/testmessagecatalog.py @@ -14,13 +14,13 @@ """Test message catalog """ -from zope import interface import zope.i18n.interfaces +from zope import interface from zope.i18n.translationdomain import TranslationDomain @interface.implementer(zope.i18n.interfaces.IGlobalMessageCatalog) -class TestMessageCatalog(object): +class TestMessageCatalog: language = 'test' @@ -30,11 +30,11 @@ class TestMessageCatalog(object): def queryMessage(self, msgid, default=None): default = getattr(msgid, 'default', default) if default is not None and default != msgid: - msg = u"%s (%s)" % (msgid, default) + msg = "{} ({})".format(msgid, default) else: msg = msgid - return u"[[%s][%s]]" % (self.domain, msg) + return "[[{}][{}]]".format(self.domain, msg) getMessage = queryMessage @@ -46,7 +46,7 @@ class TestMessageCatalog(object): @interface.implementer(zope.i18n.interfaces.ITranslationDomain) -def TestMessageFallbackDomain(domain_id=u""): +def TestMessageFallbackDomain(domain_id=""): domain = TranslationDomain(domain_id) domain.addCatalog(TestMessageCatalog(domain_id)) return domain diff --git a/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n2.mo b/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n2.mo Binary files differnew file mode 100644 index 0000000..ecfe91c --- /dev/null +++ b/src/zope/i18n/tests/locale3/en/LC_MESSAGES/zope-i18n2.mo diff --git a/src/zope/i18n/tests/test.py b/src/zope/i18n/tests/test.py index 62082c3..07897be 100644 --- a/src/zope/i18n/tests/test.py +++ b/src/zope/i18n/tests/test.py @@ -13,10 +13,12 @@ ############################################################################## """Misc tests """ +import doctest import unittest -import doctest -from zope.component.testing import setUp, tearDown +from zope.component.testing import setUp +from zope.component.testing import tearDown + from zope.i18n.testing import unicode_checker diff --git a/src/zope/i18n/tests/test_compile.py b/src/zope/i18n/tests/test_compile.py index d82c3e7..0a20cf9 100644 --- a/src/zope/i18n/tests/test_compile.py +++ b/src/zope/i18n/tests/test_compile.py @@ -31,9 +31,9 @@ class TestCompile(unittest.TestCase): self.assertIsNone(compile.compile_mo_file('no_such_domain', '')) def test_po_exists_but_invalid(self): - import tempfile - import shutil import os.path + import shutil + import tempfile td = tempfile.mkdtemp(suffix=".zopei18n_test_compile") self.addCleanup(shutil.rmtree, td) @@ -47,10 +47,10 @@ class TestCompile(unittest.TestCase): str(self.handler)) def test_po_exists_cannot_write_mo(self): - import tempfile - import shutil import os import os.path + import shutil + import tempfile td = tempfile.mkdtemp(suffix=".zopei18n_test_compile") self.addCleanup(shutil.rmtree, td) diff --git a/src/zope/i18n/tests/test_formats.py b/src/zope/i18n/tests/test_formats.py index a266de6..2f993fd 100644 --- a/src/zope/i18n/tests/test_formats.py +++ b/src/zope/i18n/tests/test_formats.py @@ -13,31 +13,33 @@ ############################################################################## """This module tests the Formats and everything that goes with it. """ -import decimal import datetime +import decimal import pickle from unittest import TestCase import pytz -from zope.i18n.interfaces import IDateTimeFormat from zope.i18n.format import DateTimeFormat -from zope.i18n.format import parseDateTimePattern, buildDateTimeParseInfo -from zope.i18n.format import DateTimePatternParseError, DateTimeParseError - -from zope.i18n.interfaces import INumberFormat -from zope.i18n.format import NumberFormat, NumberParseError -from zope.i18n.format import parseNumberPattern +from zope.i18n.format import DateTimeParseError +from zope.i18n.format import DateTimePatternParseError +from zope.i18n.format import NumberFormat +from zope.i18n.format import NumberParseError from zope.i18n.format import NumberPatternParseError +from zope.i18n.format import buildDateTimeParseInfo +from zope.i18n.format import parseDateTimePattern +from zope.i18n.format import parseNumberPattern +from zope.i18n.interfaces import IDateTimeFormat +from zope.i18n.interfaces import INumberFormat -class LocaleStub(object): +class LocaleStub: pass -class LocaleCalendarStub(object): +class LocaleCalendarStub: - type = u"gregorian" + type = "gregorian" months = { 1: ('Januar', 'Jan'), @@ -102,14 +104,7 @@ class LocaleCalendarStub(object): raise NotImplementedError() -class _TestCase(TestCase): - # Avoid deprecation warnings in Python 3 by making the preferred - # method name available for Python 2. - assertRaisesRegex = getattr( - TestCase, 'assertRaisesRegex', TestCase.assertRaisesRegexp) - - -class TestDateTimePatternParser(_TestCase): +class TestDateTimePatternParser(TestCase): """Extensive tests for the ICU-based-syntax datetime pattern parser.""" def testParseSimpleTimePattern(self): @@ -207,7 +202,7 @@ class TestDateTimePatternParser(_TestCase): ) -class TestBuildDateTimeParseInfo(_TestCase): +class TestBuildDateTimeParseInfo(TestCase): """This class tests the functionality of the buildDateTimeParseInfo() method with the German locale. """ @@ -250,9 +245,9 @@ class TestBuildDateTimeParseInfo(_TestCase): self.assertEqual(self.info(('M', 2)), '([0-9]{2})') def testMonthNames(self): - names = [u"Januar", u"Februar", u"Maerz", u"April", - u"Mai", u"Juni", u"Juli", u"August", u"September", u"Oktober", - u"November", u"Dezember"] + names = ["Januar", "Februar", "Maerz", "April", + "Mai", "Juni", "Juli", "August", "September", "Oktober", + "November", "Dezember"] self.assertEqual(self.info(('M', 4)), '(' + '|'.join(names) + ')') def testMonthAbbr(self): @@ -276,7 +271,7 @@ class TestBuildDateTimeParseInfo(_TestCase): self.assertEqual(self.info(('E', 3)), '(' + '|'.join(names) + ')') -class TestDateTimeFormat(_TestCase): +class TestDateTimeFormat(TestCase): """Test the functionality of an implmentation of the ILocaleProvider interface.""" @@ -630,69 +625,69 @@ class TestDateTimeFormat(_TestCase): def testFormatDayInYear(self): self.assertEqual( self.format.format(datetime.date(2003, 1, 3), 'D'), - u"3") + "3") self.assertEqual( self.format.format(datetime.date(2003, 1, 3), 'DD'), - u"03") + "03") self.assertEqual( self.format.format(datetime.date(2003, 1, 3), 'DDD'), - u"003") + "003") self.assertEqual( self.format.format(datetime.date(2003, 12, 31), 'D'), - u"365") + "365") self.assertEqual( self.format.format(datetime.date(2003, 12, 31), 'DD'), - u"365") + "365") self.assertEqual( self.format.format(datetime.date(2003, 12, 31), 'DDD'), - u"365") + "365") self.assertEqual( self.format.format(datetime.date(2004, 12, 31), 'DDD'), - u"366") + "366") def testFormatDayOfWeekInMOnth(self): self.assertEqual( self.format.format(datetime.date(2003, 1, 3), 'F'), - u"1") + "1") self.assertEqual( self.format.format(datetime.date(2003, 1, 10), 'F'), - u"2") + "2") self.assertEqual( self.format.format(datetime.date(2003, 1, 17), 'F'), - u"3") + "3") self.assertEqual( self.format.format(datetime.date(2003, 1, 24), 'F'), - u"4") + "4") self.assertEqual( self.format.format(datetime.date(2003, 1, 31), 'F'), - u"5") + "5") self.assertEqual( self.format.format(datetime.date(2003, 1, 6), 'F'), - u"1") + "1") def testFormatUnusualFormats(self): self.assertEqual( self.format.format(datetime.date(2003, 1, 3), 'DDD-yyyy'), - u"003-2003") + "003-2003") self.assertEqual( self.format.format(datetime.date(2003, 1, 10), "F. EEEE 'im' MMMM, yyyy"), - u"2. Freitag im Januar, 2003") + "2. Freitag im Januar, 2003") def testFormatGregorianEra(self): self.assertEqual( self.format.format(datetime.date(2017, 12, 17), 'G'), - u'n. Chr.' + 'n. Chr.' ) def testFormateMonthLengthOne(self): self.assertEqual( self.format.format(datetime.date(2017, 12, 17), 'M'), - u'12' + '12' ) -class TestNumberPatternParser(_TestCase): +class TestNumberPatternParser(TestCase): """Extensive tests for the ICU-based-syntax number pattern parser.""" def testParseSimpleIntegerPattern(self): @@ -1047,7 +1042,7 @@ class TestNumberPatternParser(_TestCase): neg_pattern) -class TestNumberFormat(_TestCase): +class TestNumberFormat(TestCase): """Test the functionality of an implmentation of the NumberFormat.""" format = NumberFormat(symbols={ diff --git a/src/zope/i18n/tests/test_gettextmessagecatalog.py b/src/zope/i18n/tests/test_gettextmessagecatalog.py index 5bd93df..0272898 100644 --- a/src/zope/i18n/tests/test_gettextmessagecatalog.py +++ b/src/zope/i18n/tests/test_gettextmessagecatalog.py @@ -14,6 +14,7 @@ """Test a gettext implementation of a Message Catalog. """ import os + from zope.i18n.gettextmessagecatalog import GettextMessageCatalog from zope.i18n.tests import test_imessagecatalog diff --git a/src/zope/i18n/tests/test_imessagecatalog.py b/src/zope/i18n/tests/test_imessagecatalog.py index 36fb43c..95295cd 100644 --- a/src/zope/i18n/tests/test_imessagecatalog.py +++ b/src/zope/i18n/tests/test_imessagecatalog.py @@ -14,10 +14,12 @@ """This is an 'abstract' test for the IMessageCatalog interface. """ import unittest + from zope.interface.verify import verifyObject -from zope.i18n.interfaces import IMessageCatalog from zope.schema import getValidationErrors +from zope.i18n.interfaces import IMessageCatalog + class TestIMessageCatalog(unittest.TestCase): diff --git a/src/zope/i18n/tests/test_itranslationdomain.py b/src/zope/i18n/tests/test_itranslationdomain.py index bea5360..81bec8a 100644 --- a/src/zope/i18n/tests/test_itranslationdomain.py +++ b/src/zope/i18n/tests/test_itranslationdomain.py @@ -14,22 +14,21 @@ """This is an 'abstract' test for the ITranslationDomain interface. """ import unittest -from zope.interface.verify import verifyObject -from zope.interface import implementer import zope.component from zope.component.testing import PlacelessSetup - +from zope.interface import implementer +from zope.interface.verify import verifyObject from zope.schema import getValidationErrors -from zope.i18n._compat import text_type -from zope.i18n.negotiator import negotiator -from zope.i18n.interfaces import INegotiator, IUserPreferredLanguages +from zope.i18n.interfaces import INegotiator from zope.i18n.interfaces import ITranslationDomain +from zope.i18n.interfaces import IUserPreferredLanguages +from zope.i18n.negotiator import negotiator @implementer(IUserPreferredLanguages) -class Environment(object): +class Environment: def __init__(self, langs=()): self.langs = langs @@ -45,7 +44,7 @@ class TestITranslationDomain(PlacelessSetup): raise NotImplementedError() def setUp(self): - super(TestITranslationDomain, self).setUp() + super().setUp() self._domain = self._getTranslationDomain() # Setup the negotiator utility @@ -94,7 +93,7 @@ class TestITranslationDomain(PlacelessSetup): translate = self._domain.translate translated = translate('no way', target_language='en') self.assertEqual(translated, "no way") - self.assertIsInstance(translated, text_type) + self.assertIsInstance(translated, str) def testNoTargetLanguage(self): translate = self._domain.translate diff --git a/src/zope/i18n/tests/test_negotiator.py b/src/zope/i18n/tests/test_negotiator.py index 9267332..bf5ba4d 100644 --- a/src/zope/i18n/tests/test_negotiator.py +++ b/src/zope/i18n/tests/test_negotiator.py @@ -15,14 +15,15 @@ """ import unittest -from zope.i18n.negotiator import Negotiator -from zope.i18n.interfaces import IUserPreferredLanguages from zope.component.testing import PlacelessSetup from zope.interface import implementer +from zope.i18n.interfaces import IUserPreferredLanguages +from zope.i18n.negotiator import Negotiator + @implementer(IUserPreferredLanguages) -class Env(object): +class Env: def __init__(self, langs=()): self.langs = langs @@ -34,7 +35,7 @@ class Env(object): class NegotiatorTest(PlacelessSetup, unittest.TestCase): def setUp(self): - super(NegotiatorTest, self).setUp() + super().setUp() self.negotiator = Negotiator() def test_findLanguages(self): @@ -55,5 +56,5 @@ class NegotiatorTest(PlacelessSetup, unittest.TestCase): def test_suite(): return unittest.TestSuite(( - unittest.makeSuite(NegotiatorTest), + unittest.defaultTestLoader.loadTestsFromTestCase(NegotiatorTest), )) diff --git a/src/zope/i18n/tests/test_plurals.py b/src/zope/i18n/tests/test_plurals.py index bc97bf3..2577bed 100644 --- a/src/zope/i18n/tests/test_plurals.py +++ b/src/zope/i18n/tests/test_plurals.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. @@ -18,24 +17,26 @@ import os import unittest import zope.component -from zope.i18n import tests, translate -from zope.i18n.translationdomain import TranslationDomain -from zope.i18n.gettextmessagecatalog import GettextMessageCatalog from zope.i18nmessageid import MessageFactory + +from zope.i18n import tests +from zope.i18n import translate +from zope.i18n.gettextmessagecatalog import GettextMessageCatalog from zope.i18n.interfaces import ITranslationDomain +from zope.i18n.translationdomain import TranslationDomain class TestPlurals(unittest.TestCase): def _getMessageCatalog(self, locale, variant="default"): path = os.path.dirname(tests.__file__) - self._path = os.path.join(path, '%s-%s.mo' % (locale, variant)) + self._path = os.path.join(path, '{}-{}.mo'.format(locale, variant)) catalog = GettextMessageCatalog(locale, variant, self._path) return catalog def _getTranslationDomain(self, locale, variant="default"): path = os.path.dirname(tests.__file__) - self._path = os.path.join(path, '%s-%s.mo' % (locale, variant)) + self._path = os.path.join(path, '{}-{}.mo'.format(locale, variant)) catalog = GettextMessageCatalog(locale, variant, self._path) domain = TranslationDomain('default') domain.addCatalog(catalog) @@ -124,22 +125,22 @@ class TestPlurals(unittest.TestCase): self.assertEqual(catalog.getPluralMessage( 'There is one file.', 'There are %d files.', 0), - u"Istnieją 0 plików.") + "Istnieją 0 plików.") self.assertEqual(catalog.getPluralMessage( 'There is one file.', 'There are %d files.', 1), - u"Istnieje 1 plik.") + "Istnieje 1 plik.") self.assertEqual(catalog.getPluralMessage( 'There is one file.', 'There are %d files.', 3), - u"Istnieją 3 pliki.") + "Istnieją 3 pliki.") self.assertEqual(catalog.getPluralMessage( 'There is one file.', 'There are %d files.', 17), - u"Istnieją 17 plików.") + "Istnieją 17 plików.") self.assertEqual(catalog.getPluralMessage( 'There is one file.', 'There are %d files.', 23), - u"Istnieją 23 pliki.") + "Istnieją 23 pliki.") self.assertEqual(catalog.getPluralMessage( 'There is one file.', 'There are %d files.', 28), - u"Istnieją 28 plików.") + "Istnieją 28 plików.") def test_floater(self): """Test with the number being a float. diff --git a/src/zope/i18n/tests/test_simpletranslationdomain.py b/src/zope/i18n/tests/test_simpletranslationdomain.py index 2f233d6..4c88d8e 100644 --- a/src/zope/i18n/tests/test_simpletranslationdomain.py +++ b/src/zope/i18n/tests/test_simpletranslationdomain.py @@ -14,6 +14,7 @@ """This module tests the regular persistent Translation Domain. """ import unittest + from zope.i18n.simpletranslationdomain import SimpleTranslationDomain from zope.i18n.tests.test_itranslationdomain import TestITranslationDomain @@ -22,7 +23,8 @@ data = { ('en', 'short_greeting'): 'Hello!', ('de', 'short_greeting'): 'Hallo!', ('en', 'greeting'): 'Hello $name, how are you?', - ('de', 'greeting'): 'Hallo $name, wie geht es Dir?'} + ('de', 'greeting'): 'Hallo $name, wie geht es Dir?', +} class TestSimpleTranslationDomain(unittest.TestCase, TestITranslationDomain): @@ -36,9 +38,3 @@ class TestSimpleTranslationDomain(unittest.TestCase, TestITranslationDomain): def _getTranslationDomain(self): domain = SimpleTranslationDomain('default', data) return domain - - -def test_suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestSimpleTranslationDomain)) - return suite diff --git a/src/zope/i18n/tests/test_testmessagecatalog.py b/src/zope/i18n/tests/test_testmessagecatalog.py index e5c8e57..a28148a 100644 --- a/src/zope/i18n/tests/test_testmessagecatalog.py +++ b/src/zope/i18n/tests/test_testmessagecatalog.py @@ -12,11 +12,11 @@ # ############################################################################## -import unittest import doctest +import unittest def test_suite(): - return unittest.TestSuite(( + return unittest.TestSuite( doctest.DocFileSuite('../testmessagecatalog.rst') - )) + ) diff --git a/src/zope/i18n/tests/test_translationdomain.py b/src/zope/i18n/tests/test_translationdomain.py index 5d55156..b681dc4 100644 --- a/src/zope/i18n/tests/test_translationdomain.py +++ b/src/zope/i18n/tests/test_translationdomain.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2001-2008 Zope Foundation and Contributors. @@ -14,16 +13,18 @@ ############################################################################## """This module tests the regular persistent Translation Domain. """ -import unittest import os -from zope.i18n.translationdomain import TranslationDomain -from zope.i18n.gettextmessagecatalog import GettextMessageCatalog -from zope.i18n.tests.test_itranslationdomain import \ - TestITranslationDomain, Environment +import unittest + +import zope.component from zope.i18nmessageid import MessageFactory + +from zope.i18n.gettextmessagecatalog import GettextMessageCatalog from zope.i18n.interfaces import ITranslationDomain +from zope.i18n.tests.test_itranslationdomain import Environment +from zope.i18n.tests.test_itranslationdomain import TestITranslationDomain +from zope.i18n.translationdomain import TranslationDomain -import zope.component testdir = os.path.dirname(__file__) @@ -68,19 +69,19 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): def testEmptyStringTranslate(self): translate = self._domain.translate - self.assertEqual(translate(u"", target_language='en'), u"") - self.assertEqual(translate(u"", target_language='foo'), u"") + self.assertEqual(translate("", target_language='en'), "") + self.assertEqual(translate("", target_language='foo'), "") def testStringTranslate(self): self.assertEqual( - self._domain.translate(u"short_greeting", target_language='en'), - u"Hello!") + self._domain.translate("short_greeting", target_language='en'), + "Hello!") def testMessageIDTranslate(self): factory = MessageFactory('default') translate = self._domain.translate - msgid = factory(u"short_greeting", 'default') - self.assertEqual(translate(msgid, target_language='en'), u"Hello!") + msgid = factory("short_greeting", 'default') + self.assertEqual(translate(msgid, target_language='en'), "Hello!") # MessageID attributes override arguments msgid = factory('43-not-there', 'this ${that} the other', mapping={'that': 'THAT'}) @@ -91,13 +92,13 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): def testMessageIDRecursiveTranslate(self): factory = MessageFactory('default') translate = self._domain.translate - msgid_sub1 = factory(u"44-not-there", '${blue}', + msgid_sub1 = factory("44-not-there", '${blue}', mapping={'blue': 'BLUE'}) - msgid_sub2 = factory(u"45-not-there", '${yellow}', + msgid_sub2 = factory("45-not-there", '${yellow}', mapping={'yellow': 'YELLOW'}) mapping = {'color1': msgid_sub1, 'color2': msgid_sub2} - msgid = factory(u"46-not-there", 'Color: ${color1}/${color2}', + msgid = factory("46-not-there", 'Color: ${color1}/${color2}', mapping=mapping) self.assertEqual( translate(msgid, target_language='en', default="default"), @@ -106,9 +107,9 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): self.assertEqual(msgid.mapping, {'color1': msgid_sub1, 'color2': msgid_sub2}) # A circular reference should not lead to crashes - msgid1 = factory(u"47-not-there", 'Message 1 and $msg2', + msgid1 = factory("47-not-there", 'Message 1 and $msg2', mapping={}) - msgid2 = factory(u"48-not-there", 'Message 2 and $msg1', + msgid2 = factory("48-not-there", 'Message 2 and $msg1', mapping={}) msgid1.mapping['msg2'] = msgid2 msgid2.mapping['msg1'] = msgid1 @@ -118,7 +119,7 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): # message id but a Unicode with a directly passed mapping self.assertEqual( "Color: BLUE/YELLOW", - translate(u"Color: ${color1}/${color2}", mapping=mapping, + translate("Color: ${color1}/${color2}", mapping=mapping, target_language='en')) # If we have mapping with a message id from a different @@ -126,9 +127,9 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): # message domain is not registered yet, we should return a # default translation. alt_factory = MessageFactory('alt') - msgid_sub = alt_factory(u"special", default=u"oohhh") + msgid_sub = alt_factory("special", default="oohhh") mapping = {'message': msgid_sub} - msgid = factory(u"46-not-there", 'Message: ${message}', + msgid = factory("46-not-there", 'Message: ${message}', mapping=mapping) # test we get a default with no domain registered self.assertEqual( @@ -156,9 +157,9 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): zope.component.provideUtility(domain, ITranslationDomain, 'alt') factory = MessageFactory('alt') - msgid = factory(u"special", 'default') + msgid = factory("special", 'default') self.assertEqual( - self._domain.translate(msgid, target_language='en'), u"Wow") + self._domain.translate(msgid, target_language='en'), "Wow") def testSimpleFallbackTranslation(self): translate = self._domain.translate @@ -166,11 +167,11 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): # Test that a translation in an unsupported language returns a # translation in the fallback language (by default, English) eq(translate('short_greeting', target_language='es'), - u"Hello!") + "Hello!") # Same test, but use the context argument instead of target_language context = Environment() eq(translate('short_greeting', context=context), - u"Hello!") + "Hello!") def testInterpolationWithoutTranslation(self): translate = self._domain.translate @@ -233,7 +234,7 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): domain.addCatalog(standard_catalog) self.assertEqual( domain.translate('short_greeting', target_language='sr'), - u"Hello in Serbian Standard!", + "Hello in Serbian Standard!", ) # Test the Latin file. @@ -241,12 +242,12 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): domain.addCatalog(latin_catalog) self.assertEqual( domain.translate('short_greeting', target_language='sr'), - u"Hello in Serbian Latin!", + "Hello in Serbian Latin!", ) # Note that sr@Latn is not recognizes as language id. self.assertEqual( domain.translate('short_greeting', target_language='sr@Latn'), - u"short_greeting", + "short_greeting", ) # Test the Cyrillic file. @@ -254,7 +255,7 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): domain.addCatalog(cyrillic_catalog) self.assertEqual( domain.translate('short_greeting', target_language='sr'), - u"Hello in српски!", + "Hello in српски!", ) # When I have all three locales, this is the order that @@ -266,5 +267,5 @@ class TestGlobalTranslationDomain(TestITranslationDomain, unittest.TestCase): # The Latin one is first, so it wins. self.assertEqual( domain.translate('short_greeting', target_language='sr'), - u"Hello in Serbian Latin!", + "Hello in Serbian Latin!", ) diff --git a/src/zope/i18n/tests/test_zcml.py b/src/zope/i18n/tests/test_zcml.py index cd3d2b4..5540beb 100644 --- a/src/zope/i18n/tests/test_zcml.py +++ b/src/zope/i18n/tests/test_zcml.py @@ -25,9 +25,8 @@ from zope.component.testing import PlacelessSetup from zope.configuration import xmlconfig import zope.i18n.tests -from zope.i18n._compat import text_type -from zope.i18n.interfaces import ITranslationDomain from zope.i18n import config +from zope.i18n.interfaces import ITranslationDomain template = """\ @@ -43,13 +42,13 @@ class DirectivesTest(PlacelessSetup, unittest.TestCase): # This test suite needs the [zcml] and [compile] extra dependencies def setUp(self): - super(DirectivesTest, self).setUp() + super().setUp() self.context = xmlconfig.file('meta.zcml', zope.i18n) self.allowed = config.ALLOWED_LANGUAGES config.ALLOWED_LANGUAGES = None def tearDown(self): - super(DirectivesTest, self).tearDown() + super().tearDown() config.ALLOWED_LANGUAGES = self.allowed def testRegisterTranslations(self): @@ -64,7 +63,7 @@ class DirectivesTest(PlacelessSetup, unittest.TestCase): 'locale', 'en', 'LC_MESSAGES', 'zope-i18n.mo') util = getUtility(ITranslationDomain, 'zope-i18n') self.assertEqual(util._catalogs.get('test'), ['test']) - self.assertEqual(util._catalogs.get('en'), [text_type(path)]) + self.assertEqual(util._catalogs.get('en'), [str(path)]) def testAllowedTranslations(self): self.assertTrue(queryUtility(ITranslationDomain) is None) @@ -79,7 +78,7 @@ class DirectivesTest(PlacelessSetup, unittest.TestCase): 'locale', 'de', 'LC_MESSAGES', 'zope-i18n.mo') util = getUtility(ITranslationDomain, 'zope-i18n') self.assertEqual(util._catalogs, - {'test': ['test'], 'de': [text_type(path)]}) + {'test': ['test'], 'de': [str(path)]}) def testRegisterDistributedTranslations(self): self.assertTrue(queryUtility(ITranslationDomain, 'zope-i18n') is None) @@ -102,16 +101,16 @@ class DirectivesTest(PlacelessSetup, unittest.TestCase): util = getUtility(ITranslationDomain, 'zope-i18n') self.assertEqual(util._catalogs.get('test'), ['test', 'test']) self.assertEqual(util._catalogs.get('en'), - [text_type(path1), text_type(path2)]) + [str(path1), str(path2)]) - msg = util.translate(u"Additional message", target_language='en') - self.assertEqual(msg, u"Additional message translated") + msg = util.translate("Additional message", target_language='en') + self.assertEqual(msg, "Additional message translated") - msg = util.translate(u"New Domain", target_language='en') - self.assertEqual(msg, u"New Domain translated") + msg = util.translate("New Domain", target_language='en') + self.assertEqual(msg, "New Domain translated") - msg = util.translate(u"New Language", target_language='en') - self.assertEqual(msg, u"New Language translated") + msg = util.translate("New Language", target_language='en') + self.assertEqual(msg, "New Language translated") def testRegisterAndCompileTranslations(self): config.COMPILE_MO_FILES = True @@ -139,14 +138,14 @@ class DirectivesTest(PlacelessSetup, unittest.TestCase): ''', self.context) util = getUtility(ITranslationDomain, 'zope-i18n') self.assertEqual(util._catalogs, - {'test': ['test'], 'en': [text_type(path)]}) + {'test': ['test'], 'en': [str(path)]}) - msg = util.translate(u"I'm a newer file", target_language='en') - self.assertEqual(msg, u"I'm a newer file translated") + msg = util.translate("I'm a newer file", target_language='en') + self.assertEqual(msg, "I'm a newer file translated") util = getUtility(ITranslationDomain, 'zope-i18n2') - msg = util.translate(u"I'm a new file", target_language='en') - self.assertEqual(msg, u"I'm a new file translated") + msg = util.translate("I'm a new file", target_language='en') + self.assertEqual(msg, "I'm a new file translated") # Reset the mtime of the mo file os.utime(path, (path_atime, path_mtime)) @@ -165,7 +164,7 @@ class DirectivesTest(PlacelessSetup, unittest.TestCase): 'locale3', 'en', 'LC_MESSAGES', 'zope-i18n.mo') util = getUtility(ITranslationDomain, 'zope-i18n') self.assertEqual(util._catalogs, - {'test': ['test'], 'en': [text_type(path)]}) + {'test': ['test'], 'en': [str(path)]}) self.assertTrue(queryUtility(ITranslationDomain, 'zope-i18n2') is None) diff --git a/src/zope/i18n/tests/testi18nawareobject.py b/src/zope/i18n/tests/testi18nawareobject.py index 996416b..bc71044 100644 --- a/src/zope/i18n/tests/testi18nawareobject.py +++ b/src/zope/i18n/tests/testi18nawareobject.py @@ -15,12 +15,13 @@ """ import unittest -from zope.i18n.interfaces import II18nAware from zope.interface import implementer +from zope.i18n.interfaces import II18nAware + @implementer(II18nAware) -class I18nAwareContentObject(object): +class I18nAwareContentObject: def __init__(self): self.content = {} @@ -55,7 +56,7 @@ class I18nAwareContentObject(object): ############################################################ -class AbstractTestII18nAwareMixin(object): +class AbstractTestII18nAwareMixin: def setUp(self): self.object = self._createObject() diff --git a/src/zope/i18n/translationdomain.py b/src/zope/i18n/translationdomain.py index e56d75c..6c587b3 100644 --- a/src/zope/i18n/translationdomain.py +++ b/src/zope/i18n/translationdomain.py @@ -16,11 +16,12 @@ import zope.component import zope.interface - from zope.i18nmessageid import Message -from zope.i18n import translate, interpolate -from zope.i18n._compat import text_type -from zope.i18n.interfaces import ITranslationDomain, INegotiator + +from zope.i18n import interpolate +from zope.i18n import translate +from zope.i18n.interfaces import INegotiator +from zope.i18n.interfaces import ITranslationDomain # The configuration should specify a list of fallback languages for the @@ -35,7 +36,7 @@ LANGUAGE_FALLBACKS = ['en'] @zope.interface.implementer(ITranslationDomain) -class TranslationDomain(object): +class TranslationDomain: def __init__(self, domain, fallbacks=None): self.domain = ( @@ -74,8 +75,8 @@ class TranslationDomain(object): """See zope.i18n.interfaces.ITranslationDomain""" # if the msgid is empty, let's save a lot of calculations and return # an empty string. - if msgid == u'': - return u'' + if msgid == '': + return '' if target_language is None and context is not None: langs = self._catalogs.keys() @@ -124,9 +125,9 @@ class TranslationDomain(object): msgid_plural, default_plural, number, seen) if default is None: - default = text_type(msgid) + default = str(msgid) if msgid_plural is not None and default_plural is None: - default_plural = text_type(msgid_plural) + default_plural = str(msgid_plural) # Get the translation. Use the specified fallbacks if this fails catalog_names = self._catalogs.get(target_language) diff --git a/src/zope/i18n/zcml.py b/src/zope/i18n/zcml.py index caeff9b..acce5fa 100644 --- a/src/zope/i18n/zcml.py +++ b/src/zope/i18n/zcml.py @@ -1,4 +1,3 @@ - # ############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. @@ -16,8 +15,8 @@ """ __docformat__ = 'restructuredtext' -import os import logging +import os from glob import glob from zope.component import getSiteManager @@ -30,9 +29,9 @@ from zope.schema import TextLine from zope.i18n import config from zope.i18n.compile import compile_mo_file from zope.i18n.gettextmessagecatalog import GettextMessageCatalog +from zope.i18n.interfaces import ITranslationDomain from zope.i18n.testmessagecatalog import TestMessageCatalog from zope.i18n.translationdomain import TranslationDomain -from zope.i18n.interfaces import ITranslationDomain logger = logging.getLogger("zope.i18n") @@ -42,15 +41,15 @@ class IRegisterTranslationsDirective(Interface): """Register translations with the global site manager.""" directory = Path( - title=u"Directory", - description=u"Directory containing the translations", + title="Directory", + description="Directory containing the translations", required=True ) domain = TextLine( - title=u"Domain", - description=(u"Translation domain to register. If not specified, " - u"all domains found in the directory are registered"), + title="Domain", + description=("Translation domain to register. If not specified, " + "all domains found in the directory are registered"), required=False ) @@ -4,14 +4,11 @@ minversion = 3.18 envlist = lint - py27 - py35 - py36 py37 py38 py39 py310 - pypy + py311 pypy3 docs coverage @@ -21,7 +18,7 @@ usedevelop = true deps = commands = zope-testrunner --test-path=src {posargs:-vc} - !py27-!pypy: sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest + sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest extras = test docs @@ -29,15 +26,26 @@ extras = [testenv:lint] basepython = python3 skip_install = true +commands = + isort --check-only --diff {toxinidir}/src {toxinidir}/setup.py + flake8 src setup.py + check-manifest + check-python-versions deps = - flake8 check-manifest check-python-versions >= 0.19.1 wheel + flake8 + isort + +[testenv:isort-apply] +basepython = python3 +skip_install = true +commands_pre = +deps = + isort commands = - flake8 src setup.py - check-manifest - check-python-versions + isort {toxinidir}/src {toxinidir}/setup.py [] [testenv:docs] basepython = python3 @@ -53,17 +61,15 @@ allowlist_externals = mkdir deps = coverage - coverage-python-version commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest - coverage html - coverage report -m --fail-under=99 + coverage html --ignore-errors + coverage report --ignore-errors --show-missing --fail-under=99 [coverage:run] branch = True -plugins = coverage_python_version source = zope.i18n [coverage:report] |