Skip to content

Commit

Permalink
Allow i18n of colander error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
amarandon committed Dec 13, 2013
1 parent 1503210 commit 5ff3611
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 5 deletions.
32 changes: 31 additions & 1 deletion cornice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import logging
from functools import partial

from cornice import util
from cornice.errors import Errors # NOQA
Expand All @@ -13,9 +14,10 @@
add_deserializer,
)
from cornice.util import ContentTypePredicate

from pyramid.i18n import get_localizer
from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid.threadlocal import get_current_registry

logger = logging.getLogger('cornice')
__version__ = "0.17"
Expand All @@ -32,6 +34,25 @@ def add_apidoc(config, pattern, func, service, **kwargs):
info['func'] = func


def get_available_locales():
settings = get_current_registry().settings
if 'available_languages' in settings:
return settings['available_languages'].split()
else:
return []


def set_localizer_for_languages(event, available_languages,
default_locale_name):
request = event.request
if request.accept_language:
accepted = request.accept_language
locale = accepted.best_match(available_languages, default_locale_name)
request._LOCALE_ = locale
localizer = get_localizer(request)
request.localizer = localizer


def includeme(config):
"""Include the Cornice definitions
"""
Expand All @@ -56,3 +77,12 @@ def includeme(config):
permission=NO_PERMISSION_REQUIRED)
config.add_view(handle_exceptions, context=HTTPForbidden,
permission=NO_PERMISSION_REQUIRED)

if settings.get('available_languages'):
available_languages = settings['available_languages'].split()
default_locale_name = settings.get('pyramid.default_locale_name', 'en')
set_localizer = partial(set_localizer_for_languages,
available_languages=available_languages,
default_locale_name=default_locale_name)
config.add_subscriber(set_localizer, NewRequest)
config.add_translation_dirs('colander:locale/')
6 changes: 4 additions & 2 deletions cornice/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@ def _validate_fields(location, data):
deserialized = attr.deserialize(serialized)
except Invalid as e:
# the struct is invalid
translate = request.localizer.translate
error_dict = e.asdict(translate=translate)
try:
request.errors.add(location, attr.name,
e.asdict()[attr.name])
error_dict[attr.name])
except KeyError:
for k, v in e.asdict().items():
for k, v in error_dict.items():
if k.startswith(attr.name):
request.errors.add(location, k, v)
else:
Expand Down
2 changes: 1 addition & 1 deletion cornice/tests/test_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def test_colander_inheritance(self):
inherited_schema = CorniceSchema.from_colander(InheritedSchema)

self.assertEqual(len(base_schema.get_attributes()),
len(inherited_schema.get_attributes()))
len(inherited_schema.get_attributes()))

foo_filter = lambda x: x.name == "foo"
base_foo = list(filter(foo_filter,
Expand Down
47 changes: 47 additions & 0 deletions cornice/tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- encoding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
Expand Down Expand Up @@ -362,3 +363,49 @@ def test_view_config_has_priority_over_global_config(self):
"hello,open,yeah",
headers={'content-type': 'text/dummy'})
self.assertEqual(response.json['test'], 'succeeded')


class TestErrorMessageTranslation(TestCase):

def post(self, settings={}, headers={}):
app = TestApp(main({}, **settings))
return app.post_json('/foobar?yeah=test', {
'foo': 'hello',
'bar': 'open',
'yeah': 'man',
'ipsum': 10,
}, status=400, headers=headers)

def assertErrorDescription(self, response, message):
error_description = response.json['errors'][0]['description']
self.assertEqual(error_description, message)

def test_accept_language_header(self):
response = self.post(
settings={'available_languages': 'fr en'},
headers={'Accept-Language': 'fr'})
self.assertErrorDescription(
response,
u'10 est plus grand que la valeur maximum autorisée (3)')

def test_default_language(self):
response = self.post(settings={
'available_languages': 'fr ja',
'pyramid.default_locale_name': 'ja',
})
self.assertErrorDescription(
response,
u'10 は最大値 3 を超過しています')

def test_default_language_fallback(self):
"""Should fallback to default language if requested language is not
available"""
response = self.post(
settings={
'available_languages': 'ja en',
'pyramid.default_locale_name': 'ja',
},
headers={'Accept-Language': 'ru'})
self.assertErrorDescription(
response,
u'10 は最大値 3 を超過しています')
2 changes: 1 addition & 1 deletion cornice/tests/validationapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,6 @@ def includeme(config):


def main(global_config, **settings):
config = Configurator(settings={})
config = Configurator(settings=settings)
config.include(includeme)
return CatchErrors(config.make_wsgi_app())

0 comments on commit 5ff3611

Please sign in to comment.