Repository: ckan/ckan

Issue:
i18n: add test of ckan.i18n_directory config option
I'm presenting this issue as a "pull request" to give me a more convenient way to illustrate my suspicions that the `ckan.i18n_directory` config option is broken on `master`.

What I think _should_ happen is the dummy `es` translation should override the real one and result in the dummy strings being produced, but I can't get it to have any effect on any of my systems.

Coming up: a similar test for 2.8, showing it passing for pylons views but not flask views.


Discussion:

Resolving patch:
diff --git a/ckan/config/middleware/flask_app.py b/ckan/config/middleware/flask_app.py
--- a/ckan/config/middleware/flask_app.py
+++ b/ckan/config/middleware/flask_app.py
@@ -30,6 +30,7 @@
 from ckan.lib import helpers
 from ckan.lib import jinja_extensions
 from ckan.lib import uploader
+from ckan.lib import i18n
 from ckan.common import config, g, request, ungettext
 from ckan.config.middleware.common_middleware import TrackingMiddleware
 import ckan.lib.app_globals as app_globals
@@ -207,7 +208,11 @@ def ungettext_alias():
         return dict(ungettext=ungettext)
 
     # Babel
-    pairs = [(os.path.join(root, u'i18n'), 'ckan')] + [
+    _ckan_i18n_dir = i18n.get_ckan_i18n_dir()
+
+    pairs = [
+        (_ckan_i18n_dir, u'ckan')
+    ] + [
         (p.i18n_directory(), p.i18n_domain())
         for p in PluginImplementations(ITranslation)
     ]
diff --git a/ckan/lib/i18n.py b/ckan/lib/i18n.py
--- a/ckan/lib/i18n.py
+++ b/ckan/lib/i18n.py
@@ -73,6 +73,15 @@
 _JS_TRANSLATIONS_DIR = os.path.join(_CKAN_DIR, u'public', u'base', u'i18n')
 
 
+def get_ckan_i18n_dir():
+    path = config.get(
+        u'ckan.i18n_directory', os.path.join(_CKAN_DIR, u'i18n'))
+    if os.path.isdir(os.path.join(path, u'i18n')):
+        path = os.path.join(path, u'i18n')
+
+    return path
+
+
 def get_locales_from_config():
     ''' despite the name of this function it gets the locales defined by
     the config AND also the locals available subject to the config. '''
@@ -101,11 +110,7 @@ def _get_locales():
     locale_order = config.get('ckan.locale_order', '').split()
 
     locales = ['en']
-    if config.get('ckan.i18n_directory'):
-        i18n_path = os.path.join(config.get('ckan.i18n_directory'), 'i18n')
-    else:
-        i18n_path = os.path.dirname(ckan.i18n.__file__)
-
+    i18n_path = get_ckan_i18n_dir()
     # For every file in the ckan i18n directory see if babel can understand
     # the locale. If yes, add it to the available locales
     for locale in os.listdir(i18n_path):
@@ -224,9 +229,11 @@ def _set_lang(lang):
     sets the Pylons root path to desired i18n_directory.
     This is needed as Pylons will only look for an i18n directory in
     the application root.'''
-    if config.get('ckan.i18n_directory'):
-        fake_config = {'pylons.paths': {'root': config['ckan.i18n_directory']},
-                       'pylons.package': config['pylons.package']}
+    i18n_dir = get_ckan_i18n_dir()
+    if i18n_dir:
+        fake_config = {'pylons.paths': {
+            'root': os.path.dirname(i18n_dir.rstrip('/'))
+        }, 'pylons.package': config['pylons.package']}
         pylons_i18n.set_lang(
             lang, pylons_config=fake_config, class_=Translations)
     else:
@@ -359,9 +366,7 @@ def build_js_translations():
     strings that are actually used in JS files.
     '''
     log.debug(u'Generating JavaScript translations')
-    ckan_i18n_dir = config.get(u'ckan.i18n_directory',
-                               os.path.join(_CKAN_DIR, u'i18n'))
-
+    ckan_i18n_dir = get_ckan_i18n_dir()
     # Collect all language codes (an extension might add support for a
     # language that isn't supported by CKAN core, yet).
     langs = set()


Test patch:
diff --git a/ckan/tests/lib/_i18n_dummy_es/i18n/es/LC_MESSAGES/ckan.mo b/ckan/tests/lib/_i18n_dummy_es/i18n/es/LC_MESSAGES/ckan.mo
new file mode 100644
Binary files /dev/null and b/ckan/tests/lib/_i18n_dummy_es/i18n/es/LC_MESSAGES/ckan.mo differ
diff --git a/ckan/tests/lib/_i18n_dummy_es/i18n/es/LC_MESSAGES/ckan.po b/ckan/tests/lib/_i18n_dummy_es/i18n/es/LC_MESSAGES/ckan.po
new file mode 100644
--- /dev/null
+++ b/ckan/tests/lib/_i18n_dummy_es/i18n/es/LC_MESSAGES/ckan.po
@@ -0,0 +1,36 @@
+# Translations template for ckan.
+# Copyright (C) 2020 ORGANIZATION
+# This file is distributed under the same license as the ckan project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
+#
+# Translators:
+# Mihai Pantazi <malexandru_70@hotmail.com>, 2018
+# Isabel M Ruiz Mellado <inactive+imruiz@transifex.com>, 2018
+# David Portoles <dportoles@idearium-consultores.com>, 2018
+# Adrià Mercader <adria.mercader@okfn.org>, 2018
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: ckan 2.8.4b0\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2020-03-31 17:05+0200\n"
+"PO-Revision-Date: 2018-03-27 14:15+0000\n"
+"Last-Translator: Nobody\n"
+"Language-Team: Spanish (https://www.transifex.com/okfn/teams/11162/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Generated-By: Babel 2.5.3\n"
+
+#: ckan/logic/converters.py:148 ckan/logic/validators.py:151
+#: ckan/logic/validators.py:193 ckan/templates/package/read_base.html:19
+#: ckan/tests/config/test_middleware.py:619
+#: ckanext/stats/templates/ckanext/stats/index.html:89
+msgid "Dataset"
+msgstr "Foo baz 123"
+
+msgid "Groups"
+msgstr "Bar Buz 321"
diff --git a/ckan/tests/lib/test_i18n.py b/ckan/tests/lib/test_i18n.py
--- a/ckan/tests/lib/test_i18n.py
+++ b/ckan/tests/lib/test_i18n.py
@@ -19,6 +19,7 @@
 
 HERE = os.path.abspath(os.path.dirname(__file__))
 I18N_DIR = os.path.join(HERE, u"_i18n_build_js_translations")
+I18N_DUMMY_DIR = os.path.join(HERE, u"_i18n_dummy_es")
 
 
 class TestJSTranslationsPlugin(plugins.SingletonPlugin, DefaultTranslation):
@@ -153,3 +154,18 @@ def test_translation_works_on_flask_and_pylons(self, app):
 
             resp = app.get(u"/es/pylons_translated")
             assert six.ensure_text(resp.data) == six.text_type(u"Grupos")
+
+    @pytest.mark.ckan_config(u"ckan.i18n_directory", I18N_DUMMY_DIR)
+    def test_config_i18n_directory(self, app):
+        resp = app.get(u"/flask_translated")
+        assert six.ensure_text(resp.data) == six.text_type(u"Dataset")
+
+        resp = app.get(u"/es/flask_translated")
+        assert six.ensure_text(resp.data) == six.text_type(u"Foo baz 123")
+
+        if six.PY2:
+            resp = app.get(u"/pylons_translated")
+            assert six.ensure_text(resp.data) == six.text_type(u"Groups")
+
+            resp = app.get(u"/es/pylons_translated")
+            assert six.ensure_text(resp.data) == six.text_type(u"Bar Buz 321")