From c7677b94fdc018db77e3ae1832b7ee2aeebae77f Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 24 Aug 2016 16:26:41 +0100 Subject: [PATCH 1/3] [#3196] Add Flask-babel requirement, hook into Flask middleware --- ckan/config/middleware/flask_app.py | 19 +++++++++++++++++++ requirements.in | 1 + requirements.txt | 1 + 3 files changed, 21 insertions(+) diff --git a/ckan/config/middleware/flask_app.py b/ckan/config/middleware/flask_app.py index 2bff977c324..73eddba4a1c 100644 --- a/ckan/config/middleware/flask_app.py +++ b/ckan/config/middleware/flask_app.py @@ -12,6 +12,7 @@ from werkzeug.exceptions import HTTPException from werkzeug.routing import Rule +from flask_babel import Babel from flask_debugtoolbar import DebugToolbarExtension from paste.deploy.converters import asbool @@ -133,6 +134,24 @@ def c_object(): ''' return dict(c=g) + # Babel + app.config[u'BABEL_TRANSLATION_DIRECTORIES'] = os.path.join(root, u'i18n') + # TODO: this relies on unpublished changes in flask-babel + app.config[u'BABEL_DOMAIN'] = 'ckan' + + babel = Babel(app) + + @babel.localeselector + def get_locale(): + u''' + Return the value of the `CKAN_LANG` key of the WSGI environ, + set by the I18nMiddleware based on the URL. + If no value is defined, it defaults to `ckan.locale_default` or `en`. + ''' + return request.environ.get( + u'CKAN_LANG', + config.get(u'ckan.locale_default', u'en')) + @app.route('/hello', methods=['GET']) def hello_world(): return 'Hello World, this is served by Flask' diff --git a/requirements.in b/requirements.in index b3ff4ca7fce..a8887a1d3db 100644 --- a/requirements.in +++ b/requirements.in @@ -5,6 +5,7 @@ Beaker==1.8.0 # Needs to be pinned to a more up to date version than the Pylons bleach==1.4.3 fanstatic==0.12 Flask==0.10.1 +Flask-Babel==0.11.1 Jinja2==2.8 Markdown==2.6.6 ofs==0.4.2 diff --git a/requirements.txt b/requirements.txt index 7e12c29998a..856eab25441 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ bleach==1.4.3 decorator==4.0.6 # via pylons, sqlalchemy-migrate fanstatic==0.12 Flask==0.10.1 +Flask-Babel==0.11.1 FormEncode==1.3.0 html5lib==0.9999999 # via bleach itsdangerous==0.24 # via flask From b1e5681f28ce3f74700998a72befc99b84c044a2 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 24 Aug 2016 16:27:26 +0100 Subject: [PATCH 2/3] [#3196] Common translation functions for Flask and Pylons Wrappers in ckan.common for `_` and `ungettext`. Flask-babel provides the `gettext` and `ngettext` functions but they really use the unicode versions internally so they are consitent with the current behaviour. We ketp the `u...` names. --- ckan/common.py | 21 ++++++++++++++++++++- ckan/plugins/toolkit.py | 10 ++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ckan/common.py b/ckan/common.py index afe2edf23e3..cd7d81008ac 100644 --- a/ckan/common.py +++ b/ckan/common.py @@ -15,7 +15,11 @@ from werkzeug.local import Local, LocalProxy -from pylons.i18n import _, ungettext +from flask_babel import (gettext as flask_ugettext, + ngettext as flask_ungettext) +from pylons.i18n import (ugettext as pylons_ugettext, + ungettext as pylons_ungettext) + from pylons import session, response import simplejson as json @@ -41,6 +45,21 @@ def is_flask_request(): not pylons_request_available)) +def ugettext(*args, **kwargs): + if is_flask_request(): + return flask_ugettext(*args, **kwargs) + else: + return pylons_ugettext(*args, **kwargs) +_ = ugettext + + +def ungettext(*args, **kwargs): + if is_flask_request(): + return flask_ungettext(*args, **kwargs) + else: + return pylons_ungettext(*args, **kwargs) + + class CKANConfig(MutableMapping): u'''Main CKAN configuration object diff --git a/ckan/plugins/toolkit.py b/ckan/plugins/toolkit.py index 899763806ea..85d3ead3449 100644 --- a/ckan/plugins/toolkit.py +++ b/ckan/plugins/toolkit.py @@ -155,19 +155,21 @@ def _initialize(self): ''' t['_'] = common._ - self.docstring_overrides['_'] = '''The Pylons ``_()`` function. + self.docstring_overrides['_'] = '''Translates a string to the +current locale. -The Pylons ``_()`` function is a reference to the ``ugettext()`` function. +The ``_()`` function is a reference to the ``ugettext()`` function. Everywhere in your code where you want strings to be internationalized (made available for translation into different languages), wrap them in the ``_()`` function, eg.:: msg = toolkit._("Hello") +Returns the localized unicode string. ''' t['ungettext'] = common.ungettext - self.docstring_overrides['ungettext'] = '''The Pylons ``ungettext`` - function. + self.docstring_overrides['ungettext'] = '''Translates a string with +plural forms to the current locale. Mark a string for translation that has pural forms in the format ``ungettext(singular, plural, n)``. Returns the localized unicode string of From d6aad5d1b6f7a231ced4a6996010b0d6b6948181 Mon Sep 17 00:00:00 2001 From: amercader Date: Wed, 24 Aug 2016 16:30:17 +0100 Subject: [PATCH 3/3] [#3196] Consolidate i18n imports, update docs --- ckan/lib/base.py | 1 - ckan/logic/action/__init__.py | 2 +- ckan/logic/auth/delete.py | 2 +- ckan/logic/auth/get.py | 2 +- doc/contributing/string-i18n.rst | 12 +++++++++--- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ckan/lib/base.py b/ckan/lib/base.py index cd04d5a401f..e1e7000f6a1 100644 --- a/ckan/lib/base.py +++ b/ckan/lib/base.py @@ -11,7 +11,6 @@ from pylons.controllers import WSGIController from pylons.controllers.util import abort as _abort from pylons.decorators import jsonify -from pylons.i18n import N_, gettext, ngettext from pylons.templating import cached_template, pylons_globals from webhelpers.html import literal diff --git a/ckan/logic/action/__init__.py b/ckan/logic/action/__init__.py index a7cfc14ceda..f105c21001c 100644 --- a/ckan/logic/action/__init__.py +++ b/ckan/logic/action/__init__.py @@ -4,7 +4,7 @@ import re from ckan.logic import NotFound -from ckan.lib.base import _, abort +from ckan.common import _ def rename_keys(dict_, key_map, reverse=False, destructive=False): diff --git a/ckan/logic/auth/delete.py b/ckan/logic/auth/delete.py index ff1a52044bc..bf64074b4f2 100644 --- a/ckan/logic/auth/delete.py +++ b/ckan/logic/auth/delete.py @@ -4,7 +4,7 @@ import ckan.authz as authz from ckan.logic.auth import get_group_object from ckan.logic.auth import get_resource_object -from ckan.lib.base import _ +from ckan.common import _ def user_delete(context, data_dict): diff --git a/ckan/logic/auth/get.py b/ckan/logic/auth/get.py index 011b19c2e84..9561f77beaf 100644 --- a/ckan/logic/auth/get.py +++ b/ckan/logic/auth/get.py @@ -2,7 +2,7 @@ import ckan.logic as logic import ckan.authz as authz -from ckan.lib.base import _ +from ckan.common import _ from ckan.logic.auth import (get_package_object, get_group_object, get_resource_object) diff --git a/doc/contributing/string-i18n.rst b/doc/contributing/string-i18n.rst index da28699ebe3..32412f8dbc4 100644 --- a/doc/contributing/string-i18n.rst +++ b/doc/contributing/string-i18n.rst @@ -148,16 +148,22 @@ Singular and plural forms are handled by ``ungettext()``: Internationalizing strings in Python code ----------------------------------------- -CKAN uses the :py:func:`~pylons.i18n._` and :py:func:`~pylons.i18n.ungettext` -functions from the `pylons.i18n.translation`_ module to internationalize +CKAN uses the :py:func:`~flask_babel._` and :py:func:`~flask_babel.ngettext` +functions from the `Flask-Babel`_ library to internationalize strings in Python code. +.. _Flask-Babel: https://pythonhosted.org/Flask-Babel/ + +.. note:: + Code running on Pylons will use the functions provided by + the `pylons.i18n.translation`_ module, but their behaviour is the same. + .. _pylons.i18n.translation: http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/modules/i18n_translation.html#module-pylons.i18n.translation Core CKAN modules should import :py:func:`~ckan.common._` and :py:func:`~ckan.common.ungettext` from :py:mod:`ckan.common`, i.e. ``from ckan.common import _, ungettext`` -(don't import :py:func:`pylons.i18n.translation._` directly, for example). +(don't import :py:func:`flask_babel._` or :py:func:`pylons.i18n.translation._` directly, for example). CKAN plugins should import :py:mod:`ckan.plugins.toolkit` and use :py:func:`ckan.plugins.toolkit._` and