Add support for API message localization
Add support for doing language resolution for a request, based on the Accept-Language HTTP header. Using the lazy gettext functionality from oslo gettextutils, it is now possible to use the resolved language to translate an exception message to the user requested language and return that translation from the API. Partially implements bp user-locale-api Change-Id: Ib2c8360372996d53b50542df54a52d92b07295ca
This commit is contained in:
@@ -44,7 +44,7 @@ if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from cinder.openstack.common import gettextutils
|
||||
gettextutils.install('cinder')
|
||||
gettextutils.install('cinder', lazy=True)
|
||||
|
||||
from cinder.common import config # Need to register global_opts
|
||||
from cinder.openstack.common import log as logging
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
# eventlet is updated/released to fix the root issue
|
||||
|
||||
import eventlet
|
||||
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import os
|
||||
@@ -39,7 +38,7 @@ if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
from cinder.openstack.common import gettextutils
|
||||
gettextutils.install('cinder')
|
||||
gettextutils.install('cinder', lazy=True)
|
||||
|
||||
from cinder.common import config # Need to register global_opts
|
||||
from cinder.openstack.common import log as logging
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright 2013 IBM Corp.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@@ -21,6 +22,7 @@ import time
|
||||
import webob
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import gettextutils
|
||||
from cinder.openstack.common import jsonutils
|
||||
from cinder.openstack.common import log as logging
|
||||
from cinder import utils
|
||||
@@ -101,6 +103,12 @@ class Request(webob.Request):
|
||||
|
||||
return content_type
|
||||
|
||||
def best_match_language(self):
|
||||
"""Determines best available locale from the Accept-Language header."""
|
||||
all_languages = gettextutils.get_available_languages('cinder')
|
||||
return self.accept_language.best_match(all_languages,
|
||||
default_match='en_US')
|
||||
|
||||
|
||||
class ActionDispatcher(object):
|
||||
"""Maps method name to local methods through action name."""
|
||||
@@ -1069,12 +1077,15 @@ class Fault(webob.exc.HTTPException):
|
||||
def __call__(self, req):
|
||||
"""Generate a WSGI response based on the exception passed to ctor."""
|
||||
# Replace the body with fault details.
|
||||
locale = req.best_match_language()
|
||||
code = self.wrapped_exc.status_int
|
||||
fault_name = self._fault_names.get(code, "computeFault")
|
||||
explanation = self.wrapped_exc.explanation
|
||||
fault_data = {
|
||||
fault_name: {
|
||||
'code': code,
|
||||
'message': self.wrapped_exc.explanation}}
|
||||
'message': gettextutils.get_localized_message(explanation,
|
||||
locale)}}
|
||||
if code == 413:
|
||||
retry = self.wrapped_exc.headers['Retry-After']
|
||||
fault_data[fault_name]['retryAfter'] = retry
|
||||
@@ -1134,13 +1145,19 @@ class OverLimitFault(webob.exc.HTTPException):
|
||||
|
||||
@webob.dec.wsgify(RequestClass=Request)
|
||||
def __call__(self, request):
|
||||
"""
|
||||
Return the wrapped exception with a serialized body conforming to our
|
||||
error format.
|
||||
"""
|
||||
"""Serializes the wrapped exception conforming to our error format."""
|
||||
content_type = request.best_match_content_type()
|
||||
metadata = {"attributes": {"overLimitFault": "code"}}
|
||||
|
||||
def translate(msg):
|
||||
locale = request.best_match_language()
|
||||
return gettextutils.get_localized_message(msg, locale)
|
||||
|
||||
self.content['overLimitFault']['message'] = \
|
||||
translate(self.content['overLimitFault']['message'])
|
||||
self.content['overLimitFault']['details'] = \
|
||||
translate(self.content['overLimitFault']['details'])
|
||||
|
||||
xml_serializer = XMLDictSerializer(metadata, XMLNS_V1)
|
||||
serializer = {
|
||||
'application/xml': xml_serializer,
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
import webob
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from cinder.api import common
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.openstack.common import gettextutils
|
||||
from cinder.openstack.common import jsonutils
|
||||
from cinder import test
|
||||
|
||||
@@ -109,6 +109,27 @@ class TestFaults(test.TestCase):
|
||||
self.assertTrue('resizeNotAllowed' not in resp.body)
|
||||
self.assertTrue('forbidden' in resp.body)
|
||||
|
||||
def test_raise_localized_explanation(self):
|
||||
params = ('blah', )
|
||||
expl = gettextutils.Message("String with params: %s" % params, 'test')
|
||||
|
||||
def _mock_translation(msg, locale):
|
||||
return "Mensaje traducido"
|
||||
|
||||
self.stubs.Set(gettextutils,
|
||||
"get_localized_message", _mock_translation)
|
||||
|
||||
@webob.dec.wsgify
|
||||
def raiser(req):
|
||||
raise wsgi.Fault(webob.exc.HTTPNotFound(explanation=expl))
|
||||
|
||||
req = webob.Request.blank('/.xml')
|
||||
resp = req.get_response(raiser)
|
||||
self.assertEqual(resp.content_type, "application/xml")
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
self.assertTrue(("Mensaje traducido") in resp.body)
|
||||
self.stubs.UnsetAll()
|
||||
|
||||
def test_fault_has_status_int(self):
|
||||
"""Ensure the status_int is set correctly on faults"""
|
||||
fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?'))
|
||||
|
||||
@@ -87,6 +87,21 @@ class RequestTest(test.TestCase):
|
||||
result = request.best_match_content_type()
|
||||
self.assertEqual(result, "application/json")
|
||||
|
||||
def test_best_match_language(self):
|
||||
# Here we test that we are actually invoking language negotiation
|
||||
# by webob and also that the default locale always available is en-US
|
||||
request = wsgi.Request.blank('/')
|
||||
accepted = 'unknown-lang'
|
||||
request.headers = {'Accept-Language': accepted}
|
||||
|
||||
def fake_best_match(self, offers, default_match=None):
|
||||
return default_match
|
||||
|
||||
self.stubs.SmartSet(request.accept_language,
|
||||
'best_match', fake_best_match)
|
||||
|
||||
self.assertEqual(request.best_match_language(), 'en_US')
|
||||
|
||||
|
||||
class ActionDispatcherTest(test.TestCase):
|
||||
def test_dispatch(self):
|
||||
|
||||
@@ -48,6 +48,7 @@ from oslo.config import cfg
|
||||
|
||||
from cinder import exception
|
||||
from cinder.openstack.common import excutils
|
||||
from cinder.openstack.common import gettextutils
|
||||
from cinder.openstack.common import importutils
|
||||
from cinder.openstack.common import lockutils
|
||||
from cinder.openstack.common import log as logging
|
||||
@@ -659,6 +660,8 @@ def utf8(value):
|
||||
"""
|
||||
if isinstance(value, unicode):
|
||||
return value.encode('utf-8')
|
||||
elif isinstance(value, gettextutils.Message):
|
||||
return unicode(value).encode('utf-8')
|
||||
elif isinstance(value, str):
|
||||
return value
|
||||
else:
|
||||
|
||||
@@ -204,7 +204,8 @@ class Server(object):
|
||||
backlog=backlog)
|
||||
self._server = eventlet.spawn(self._start)
|
||||
(self._host, self._port) = self._socket.getsockname()[0:2]
|
||||
LOG.info(_("Started %(name)s on %(_host)s:%(_port)s") % self.__dict__)
|
||||
LOG.info(_("Started %(name)s on %(host)s:%(port)s") %
|
||||
{'name': self.name, 'host': self.host, 'port': self.port})
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
|
||||
Reference in New Issue
Block a user