mgedmin: Meanwhile I've opened #9 for fixing the tests Right Now to unblock the other PRs.
diff --git a/.coveragerc b/.coveragerc
index f31f86e..f395c3b 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -6,3 +6,4 @@ exclude_lines =
     pragma: no cover
     if __name__ == '__main__':
     raise NotImplementedError
+    raise NotYetImplementedError
diff --git a/CHANGES.rst b/CHANGES.rst
index 7e4913d..399d9c5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -11,6 +11,10 @@ CHANGES
   their old locations in zope.component.interfaces instead of their current
   locations in zope.interface.interfaces.
 
+- Add compatibility with ``zope.i18n >= 4.7``. We fulfill the interface for
+  plural messages now, although we do not provide any implementation. This is
+  documented and raises NotImplementedError.
+
 
 4.0.0 (2017-05-25)
 ------------------
diff --git a/README.rst b/README.rst
index 09b357d..bc6d64b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,2 +1,30 @@
+=============
+zope.app.i18n
+=============
+
+.. image:: https://img.shields.io/pypi/v/zope.app.i18n.svg
+        :target: https://pypi.org/project/zope.app.i18n/
+        :alt: Latest release
+
+.. image:: https://img.shields.io/pypi/pyversions/zope.app.i18n.svg
+        :target: https://pypi.org/project/zope.app.i18n/
+        :alt: Supported Python versions
+
+.. image:: https://travis-ci.org/zopefoundation/zope.app.i18n.svg?branch=master
+        :target: https://travis-ci.org/zopefoundation/zope.app.i18n
+
+.. image:: https://coveralls.io/repos/github/zopefoundation/zope.app.i18n/badge.svg?branch=master
+        :target: https://coveralls.io/github/zopefoundation/zope.app.i18n?branch=master
+
+Summary
+-------
+
 This package provides placeful persistent translation domains and
-message catalogs along with ZMI views for managing them.
\ No newline at end of file
+message catalogs along with ZMI views for managing them.
+
+Caveats
+-------
+
+Currently this integration does not support the feature of plural messages
+which is supported by the underlying ``zope.i18n`` library. In case you need
+this feature, please discuss this in the issue tracker in the repository.
diff --git a/setup.py b/setup.py
index 15cd0d3..dc0ca0f 100644
--- a/setup.py
+++ b/setup.py
@@ -77,7 +77,7 @@ setup(name='zope.app.i18n',
         'zope.component >= 4.3.0',
         'zope.configuration',
         'zope.container >= 4.1.0',
-        'zope.i18n >= 4.2.0, < 4.5.0',
+        'zope.i18n >= 4.2.0, != 4.5.*, != 4.6.*',
         'zope.i18nmessageid >= 4.1.0',
         'zope.interface >= 3.8.0',
         'zope.publisher >= 4.3.2',
diff --git a/src/zope/app/i18n/interfaces.py b/src/zope/app/i18n/interfaces.py
index 6ceb1bb..c106353 100644
--- a/src/zope/app/i18n/interfaces.py
+++ b/src/zope/app/i18n/interfaces.py
@@ -143,3 +143,11 @@ class ILocalMessageCatalog(IMessageCatalog):
 
     def getMessages():
         """Get a list of all the messages."""
+
+
+# Plural messages are not supported yet. Please report an issue kn
+# github in case you want to implement this feature.
+NotYetImplementedError = NotImplementedError(
+    'Plural messages are not supported yet.'
+    ' See https://github.com/zopefoundation/zope.app.i18n/issues/7'
+)
diff --git a/src/zope/app/i18n/messagecatalog.py b/src/zope/app/i18n/messagecatalog.py
index 4cfbc28..9325725 100644
--- a/src/zope/app/i18n/messagecatalog.py
+++ b/src/zope/app/i18n/messagecatalog.py
@@ -23,6 +23,7 @@ from BTrees.OOBTree import OOBTree
 from persistent import Persistent
 from zope.component.interfaces import IFactory
 from zope.app.i18n.interfaces import ILocalMessageCatalog
+from zope.app.i18n.interfaces import NotYetImplementedError
 
 @implementer(ILocalMessageCatalog)
 @provider(IFactory)
@@ -38,11 +39,11 @@ class MessageCatalog(Persistent):
         self._messages = OOBTree()
 
     def getMessage(self, id):
-        'See `IReadMessageCatalog`'
+        'See `IMessageCatalog`'
         return self._messages[id][0]
 
     def queryMessage(self, id, default=None):
-        'See `IReadMessageCatalog`'
+        'See `IMessageCatalog`'
         result = self._messages.get(id)
         if result is not None:
             result = result[0]
@@ -50,6 +51,14 @@ class MessageCatalog(Persistent):
             result = default
         return result
 
+    def getPluralMessage(self, singular, plural, n):
+        'See `IMessageCatalog`'
+        raise NotYetImplementedError
+
+    def queryPluralMessage(self, singular, plural, n, dft1=None, dft2=None):
+        'See `IMessageCatalog`'
+        raise NotYetImplementedError
+
     def getIdentifier(self):
         'See `IReadMessageCatalog`'
         return (self.language, self.domain)
diff --git a/src/zope/app/i18n/translationdomain.py b/src/zope/app/i18n/translationdomain.py
index b9a074c..9de1cff 100644
--- a/src/zope/app/i18n/translationdomain.py
+++ b/src/zope/app/i18n/translationdomain.py
@@ -28,6 +28,7 @@ from zope.i18n.simpletranslationdomain import SimpleTranslationDomain
 from zope.container.btree import BTreeContainer
 from zope.container.contained import Contained
 from zope.app.i18n.interfaces import ILocalTranslationDomain
+from zope.app.i18n.interfaces import NotYetImplementedError
 
 try:
     unicode
@@ -64,8 +65,12 @@ class TranslationDomain(BTreeContainer, SimpleTranslationDomain, Contained):
         self._unregisterMessageCatalog(object.language, name)
 
     def translate(self, msgid, mapping=None, context=None,
-                  target_language=None, default=None):
+                  target_language=None, default=None, msgid_plural=None,
+                  default_plural=None, number=None):
         """See interface `ITranslationDomain`"""
+        if any((default_plural, msgid_plural, number)):
+            raise NotYetImplementedError
+
         if target_language is None and context is not None:
             avail_langs = self.getAvailableLanguages()
             # Let's negotiate the language to translate to. :)
@@ -80,6 +85,7 @@ class TranslationDomain(BTreeContainer, SimpleTranslationDomain, Contained):
 
         for name in catalog_names:
             catalog = super(TranslationDomain, self).__getitem__(name)
+            # TODO: handle msgid_plural
             text = catalog.queryMessage(msgid)
             if text is not None:
                 break
@@ -89,10 +95,12 @@ class TranslationDomain(BTreeContainer, SimpleTranslationDomain, Contained):
             domain = zope.component.queryNextUtility(self, ITranslationDomain,
                                                      self.domain)
             if domain is not None:
+                # TODO: handle msgid_plural
                 return domain.translate(msgid, mapping, context,
                                         target_language, default=default)
             if default is None:
                 default = unicode(msgid)
+            # TODO: handle msgid_plural/default_plural
             text = default
 
         # Now we need to do the interpolation

Add support for pluralization
zope.i18n 4.5 added support for pluralization, which introduced new methods and new arguments to IMessageCatalog and ITranslationDomain.

zope.app.i18n has local implementations of these interfaces that don't support the new methods/arguments, which breaks zope.app.i18n's test suite.
diff --git a/src/zope/app/i18n/tests/test_translationdomain.py b/src/zope/app/i18n/tests/test_translationdomain.py
index c6c9387..fd6b3d3 100644
--- a/src/zope/app/i18n/tests/test_translationdomain.py
+++ b/src/zope/app/i18n/tests/test_translationdomain.py
@@ -296,6 +296,25 @@ class TestTranslationDomainInAction(unittest.TestCase):
                                   target_language='de'),
             'Guten Tag!')
 
+    def test_translate__2(self):
+        """It raises a NotImplementedError in case of plural messages."""
+        with self.assertRaises(NotImplementedError) as err:
+            self.trans1.translate('some text', msgid_plural="some texts")
+        with self.assertRaises(NotImplementedError) as err2:
+            self.trans1.translate('some text', default_plural="some texts")
+        with self.assertRaises(NotImplementedError) as err3:
+            self.trans1.translate('some text', number="some texts")
+
+        expected = (
+            'Plural messages are not supported yet.'
+            ' See https://github.com/zopefoundation/zope.app.i18n/issues/7'
+        )
+
+        self.assertEqual(str(err.exception), expected)
+        self.assertEqual(str(err2.exception), expected)
+        self.assertEqual(str(err3.exception), expected)
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.defaultTestLoader.loadTestsFromName(__name__),

pip install -e .[test]
parse_log_pytest
pytest
pytest
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
BTrees==5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
multipart==1.1.0
packaging @ file:///croot/packaging_1671697413597/work
persistent==5.2
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycparser==2.21
pytest==7.1.2
python-gettext==5.0
pytz==2025.2
six==1.17.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
transaction==4.0
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
zipp @ file:///croot/zipp_1672387121353/work
zope.annotation==5.0
zope.app.component==5.0
zope.app.container==5.0
-e git+https://github.com/zopefoundation/zope.app.i18n.git@e924fa8930320f00a6b57a51295dc2d893e92032#egg=zope.app.i18n
zope.app.pagetemplate==5.0
zope.app.publisher==5.0
zope.browser==3.0
zope.browsermenu==5.0
zope.browserpage==5.0
zope.browserresource==5.1
zope.cachedescriptors==5.0
zope.component==6.0
zope.componentvocabulary==2.3.0
zope.configuration==5.0.1
zope.container==5.2
zope.contenttype==5.1
zope.copy==4.3
zope.copypastemove==5.0
zope.datetime==5.0.0
zope.deferredimport==5.0
zope.deprecation==5.0
zope.dottedname==6.0
zope.dublincore==5.0
zope.event==5.0
zope.exceptions==5.1
zope.filerepresentation==6.0
zope.formlib==6.0
zope.hookable==6.0
zope.i18n==4.4
zope.i18nmessageid==6.1.0
zope.interface==6.4.post2
zope.lifecycleevent==5.0
zope.location==5.0
zope.pagetemplate==5.1
zope.proxy==5.3
zope.ptresource==5.0
zope.publisher==7.0
zope.schema==7.0.1
zope.security==6.2
zope.site==5.0
zope.size==5.0
zope.tal==5.0.1
zope.tales==6.0
zope.testing==5.0.1
zope.testrunner==6.5
zope.traversing==5.0

name: zope.app.i18n
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - btrees==5.2
      - cffi==1.15.1
      - multipart==1.1.0
      - persistent==5.2
      - pycparser==2.21
      - python-gettext==5.0
      - pytz==2025.2
      - six==1.17.0
      - transaction==4.0
      - zope-annotation==5.0
      - zope-app-component==5.0
      - zope-app-container==5.0
      - zope-app-pagetemplate==5.0
      - zope-app-publisher==5.0
      - zope-browser==3.0
      - zope-browsermenu==5.0
      - zope-browserpage==5.0
      - zope-browserresource==5.1
      - zope-cachedescriptors==5.0
      - zope-component==6.0
      - zope-componentvocabulary==2.3.0
      - zope-configuration==5.0.1
      - zope-container==5.2
      - zope-contenttype==5.1
      - zope-copy==4.3
      - zope-copypastemove==5.0
      - zope-datetime==5.0.0
      - zope-deferredimport==5.0
      - zope-deprecation==5.0
      - zope-dottedname==6.0
      - zope-dublincore==5.0
      - zope-event==5.0
      - zope-exceptions==5.1
      - zope-filerepresentation==6.0
      - zope-formlib==6.0
      - zope-hookable==6.0
      - zope-i18n==4.4
      - zope-i18nmessageid==6.1.0
      - zope-interface==6.4.post2
      - zope-lifecycleevent==5.0
      - zope-location==5.0
      - zope-pagetemplate==5.1
      - zope-proxy==5.3
      - zope-ptresource==5.0
      - zope-publisher==7.0
      - zope-schema==7.0.1
      - zope-security==6.2
      - zope-site==5.0
      - zope-size==5.0
      - zope-tal==5.0.1
      - zope-tales==6.0
      - zope-testing==5.0.1
      - zope-testrunner==6.5
      - zope-traversing==5.0
prefix: /opt/conda/envs/zope.app.i18n

src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomainInAction::test_translate__2
src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testAddUpdateDeleteMessage src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testCatalogDomain src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testDynamicTranslate src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testGetAddDeleteLanguage src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testGetMessagesMapping src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testInterface src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testNoTargetLanguage src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testNoTranslation src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testParameterNames src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testSimpleTranslate src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testSynchronize src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::testUnicodeDefaultValue src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomain::test_getMessages src/zope/app/i18n/tests/test_translationdomain.py::TestTranslationDomainInAction::test_translate src/zope/app/i18n/tests/test_translationdomain.py::test_suite
README.rst src/zope/app/i18n/interfaces.py src/zope/app/i18n/translationdomain.py setup.py src/zope/app/i18n/messagecatalog.py .coveragerc CHANGES.rst
README.rst src/zope/app/i18n/interfaces.py src/zope/app/i18n/translationdomain.py setup.py src/zope/app/i18n/messagecatalog.py .coveragerc CHANGES.rst