From 98c945307794e0aeca0c63eb340d11bded8f39b6 Mon Sep 17 00:00:00 2001 From: David Glick Date: Tue, 3 Feb 2026 17:23:19 -0800 Subject: [PATCH 1/3] Translate exception messages --- news/200.bugfix | 1 + src/plone/rest/errors.py | 8 ++++++- src/plone/rest/testing.py | 11 ++------- src/plone/rest/tests/test_error_handling.py | 26 ++++++++++++++++----- 4 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 news/200.bugfix diff --git a/news/200.bugfix b/news/200.bugfix new file mode 100644 index 00000000..768ca006 --- /dev/null +++ b/news/200.bugfix @@ -0,0 +1 @@ +Translate exception message if it is an instance of zope.i18nmessageid.Message. @davisagli diff --git a/src/plone/rest/errors.py b/src/plone/rest/errors.py index 3d405483..960b0f38 100644 --- a/src/plone/rest/errors.py +++ b/src/plone/rest/errors.py @@ -14,6 +14,8 @@ from zope.component import queryMultiAdapter from zope.component import queryUtility from zope.component.hooks import getSite +from zope.i18n import Message +from zope.i18n import translate import json import sys @@ -60,7 +62,11 @@ def __call__(self): def render_exception(self, exception): name = type(exception).__name__ - message = str(exception) + msg = exception.args[0] + if isinstance(msg, Message): + message = translate(msg, context=self.request) + else: + message = str(msg) result = {"type": name, "message": message} policy = queryMultiAdapter((self.context, self.request), ICORSPolicy) diff --git a/src/plone/rest/testing.py b/src/plone/rest/testing.py index 95e4d12d..353632d5 100644 --- a/src/plone/rest/testing.py +++ b/src/plone/rest/testing.py @@ -5,6 +5,7 @@ from plone.rest.service import Service from plone.testing import zope from zope.configuration import xmlconfig +from zope.i18nmessageid import Message class PloneRestLayer(PloneSandboxLayer): @@ -29,12 +30,4 @@ def setUpZope(self, app, configurationContext): class InternalServerErrorService(Service): def __call__(self): - from urllib.error import HTTPError - - raise HTTPError( - "http://nohost/plone/500-internal-server-error", - 500, - "InternalServerError", - {}, - None, - ) + raise Exception(Message("Error", domain="plone")) diff --git a/src/plone/rest/tests/test_error_handling.py b/src/plone/rest/tests/test_error_handling.py index 03d433fc..c2e9ec6c 100644 --- a/src/plone/rest/tests/test_error_handling.py +++ b/src/plone/rest/tests/test_error_handling.py @@ -103,12 +103,8 @@ def test_500_internal_server_error(self): + "the server should respond with sending back application/json.", ) self.assertTrue(json.loads(response.content)) - self.assertEqual("HTTPError", response.json()["type"]) - - self.assertEqual("HTTPError", response.json()["type"]) - self.assertEqual( - "HTTP Error 500: InternalServerError", response.json()["message"] - ) + self.assertEqual("Exception", response.json()["type"]) + self.assertEqual("Error", response.json()["message"]) self.assertEqual(self.portal_url, response.json()["context"]) def test_500_traceback_only_for_manager_users(self): @@ -133,3 +129,21 @@ def test_500_traceback_only_for_manager_users(self): self.assertRegex( traceback[0], r'^File "[^"]*", line \d*, in (publish|transaction_pubevents)' ) + + def test_500_message_translated(self): + registry = self.portal.portal_registry + registry["plone.available_languages"] = ["de", "en"] + registry["plone.default_language"] = "de" + transaction.commit() + + response = requests.get( + self.portal_url + "/500-internal-server-error", + headers={"Accept": "application/json"}, + ) + + self.assertEqual(response.status_code, 500) + self.assertEqual( + response.headers.get("Content-Type"), + "application/json", + ) + self.assertEqual(response.json()["message"], "Fehler") From adfad9b49502dd1879c9861e6807b176eeda96ed Mon Sep 17 00:00:00 2001 From: David Glick Date: Wed, 4 Feb 2026 14:48:58 -0800 Subject: [PATCH 2/3] fix import --- src/plone/rest/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plone/rest/errors.py b/src/plone/rest/errors.py index 960b0f38..12e38aa6 100644 --- a/src/plone/rest/errors.py +++ b/src/plone/rest/errors.py @@ -14,8 +14,8 @@ from zope.component import queryMultiAdapter from zope.component import queryUtility from zope.component.hooks import getSite -from zope.i18n import Message from zope.i18n import translate +from zope.i18nmessageid import Message import json import sys From 5d66b89e5486f8e0597cde68fa93562c9b820035 Mon Sep 17 00:00:00 2001 From: David Glick Date: Wed, 4 Feb 2026 14:57:18 -0800 Subject: [PATCH 3/3] Fix handling of exceptions with no args --- src/plone/rest/errors.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/plone/rest/errors.py b/src/plone/rest/errors.py index 12e38aa6..be9c7575 100644 --- a/src/plone/rest/errors.py +++ b/src/plone/rest/errors.py @@ -62,11 +62,12 @@ def __call__(self): def render_exception(self, exception): name = type(exception).__name__ - msg = exception.args[0] - if isinstance(msg, Message): - message = translate(msg, context=self.request) - else: - message = str(msg) + try: + message = exception.args[0] + except IndexError: + message = str(exception) + if isinstance(message, Message): + message = translate(message, context=self.request) result = {"type": name, "message": message} policy = queryMultiAdapter((self.context, self.request), ICORSPolicy)