From 1e169481b0bcca433e41c56eae986ce814461649 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 12 Aug 2017 22:30:13 +0000 Subject: [PATCH] Revert "Generate language list automatically" This reverts commit a88092630014c53c9e6fe4ee9265f8443eea96bd. The reverted implementation depends on LOCALE_PATHS but this assumption turns out not correct. Django searches messages catalogs LOCALE_PATHS and locale directory in individual INSTALLED_APPS, but the usage of LOCALE_PATHS varies on deployers and we cannot assume the default value of LOCALE_PATSH. In addition, the logic of auto-generating the language list cannot handle locale name alias ('fallback' in the Django code). Django 1.9 or later perfers to zh-hant and zh-hans, and zh-cn and zh-tw are now defined as fallback. We can explore a better approach for auto-generation of the language list, but we do not have more reliable way so far. Cconsidering the timing of Pike release, the safest approach looks like to revert the original patch back to the manula maintenance of the lang list. Languages with over 50% progress (based on the number of translated messages as total) are listed in settings.LANGUAGES. (http://paste.openstack.org/show/618254/) Closes-Bug: #1710131 Change-Id: I5133d6317aba6107fc37bd5f30388c130b1fdaac (cherry picked from commit 00f74fc06c37c58c23ff00ee9f36048d158ceeb1) --- .../dashboards/settings/user/forms.py | 71 ++------- .../dashboards/settings/user/tests.py | 145 +----------------- .../local/local_settings.py.example | 24 --- openstack_dashboard/settings.py | 19 +++ ...erated-automatically-4a9bf752752d09f6.yaml | 11 -- 5 files changed, 35 insertions(+), 235 deletions(-) delete mode 100644 releasenotes/notes/language-list-generated-automatically-4a9bf752752d09f6.yaml diff --git a/openstack_dashboard/dashboards/settings/user/forms.py b/openstack_dashboard/dashboards/settings/user/forms.py index b136636e9f..ec9f029474 100644 --- a/openstack_dashboard/dashboards/settings/user/forms.py +++ b/openstack_dashboard/dashboards/settings/user/forms.py @@ -13,8 +13,6 @@ # under the License. from datetime import datetime -import gettext as gettext_module -import itertools import string import babel @@ -22,7 +20,6 @@ import babel.dates from django.conf import settings from django import shortcuts from django.utils import encoding -from django.utils import lru_cache from django.utils import translation from django.utils.translation import ugettext_lazy as _ import pytz @@ -32,60 +29,6 @@ from horizon import messages from horizon.utils import functions -def _get_language_display_name(code, desc): - try: - desc = translation.get_language_info(code)['name_local'] - desc = string.capwords(desc) - except KeyError: - # If a language is not defined in django.conf.locale.LANG_INFO - # get_language_info raises KeyError - pass - return "%s (%s)" % (desc, code) - - -@lru_cache.lru_cache() -def _get_languages(): - - languages = [] - processed_catalogs = set([]) - # sorted() here is important to make processed_catalogs checking - # work properly. - for lang_code, lang_label in sorted(settings.LANGUAGES): - if lang_code == 'en': - # Add English as source language - languages.append(('en', - _get_language_display_name('en', 'English'))) - continue - found_catalogs = [ - gettext_module.find(domain, locale_path, - [translation.to_locale(lang_code)]) - for locale_path, domain - in itertools.product(settings.LOCALE_PATHS, - ['django', 'djangojs']) - ] - if not all(found_catalogs): - continue - # NOTE(amotoki): - # Check if found catalogs are already processed or not. - # settings.LANGUAGES can contains languages with a same prefix - # like es, es-ar, es-mx. gettext_module.find() searchess Message - # catalog for es-ar in the order of 'es_AR' and then 'es'. - # If 'es' locale is translated, 'es-ar' is included in the list of - # found_catalogs even if 'es-ar' is not translated. - # In this case, the list already includes 'es' and - # there is no need to include 'es-ar'. - result = [catalog in processed_catalogs - for catalog in found_catalogs] - if any(result): - continue - processed_catalogs |= set(found_catalogs) - languages.append( - (lang_code, - _get_language_display_name(lang_code, lang_label)) - ) - return sorted(languages) - - class UserSettingsForm(forms.SelfHandlingForm): max_value = getattr(settings, 'API_RESULT_LIMIT', 1000) language = forms.ChoiceField(label=_("Language")) @@ -108,7 +51,19 @@ class UserSettingsForm(forms.SelfHandlingForm): def __init__(self, *args, **kwargs): super(UserSettingsForm, self).__init__(*args, **kwargs) - self.fields['language'].choices = _get_languages() + # Languages + def get_language_display_name(code, desc): + try: + desc = translation.get_language_info(code)['name_local'] + desc = string.capwords(desc) + except KeyError: + # If a language is not defined in django.conf.locale.LANG_INFO + # get_language_info raises KeyError + pass + return "%s (%s)" % (desc, code) + languages = [(k, get_language_display_name(k, v)) + for k, v in settings.LANGUAGES] + self.fields['language'].choices = languages # Timezones timezones = [] diff --git a/openstack_dashboard/dashboards/settings/user/tests.py b/openstack_dashboard/dashboards/settings/user/tests.py index 8818ad74f2..5ecb5aa0af 100644 --- a/openstack_dashboard/dashboards/settings/user/tests.py +++ b/openstack_dashboard/dashboards/settings/user/tests.py @@ -14,12 +14,7 @@ from django.conf import settings from django.core.urlresolvers import reverse -from django.test.utils import override_settings -from django.utils import translation -import mock - -from openstack_dashboard.dashboards.settings.user import forms from openstack_dashboard.test import helpers as test @@ -35,146 +30,12 @@ class UserSettingsTest(test.TestCase): self.assertContains(res, "UTC -03:00: Falkland Islands Time") self.assertContains(res, "UTC -10:00: United States (Honolulu) Time") - @override_settings(LOCALE_PATHS=['openstack_dashboard/locale']) def test_display_language(self): # Add an unknown language to LANGUAGES list - # to check it works with unknown language in the list. settings.LANGUAGES += (('unknown', 'Unknown Language'),) res = self.client.get(INDEX_URL) - # In this test, we just checks language list is properly - # generated without an error as the result depends on - # existence of translation message catalogs. + # Known language self.assertContains(res, 'English') - - -class LanguageTest(test.TestCase): - """Tests for _get_languages().""" - - def setUp(self): - super(LanguageTest, self).setUp() - # _get_languages is decorated by lru_cache, - # so we need to clear cache info before each test run. - forms._get_languages.cache_clear() - - @staticmethod - def _patch_gettext_find_all_translated(*args, **kwargs): - domain = args[0] - locale_path = args[1] - locale = args[2][0] - return '%s/%s/LC_MESSAGES/%s.mo' % (locale_path, locale, domain) - - @override_settings(LANGUAGES=(('de', 'Germany'), - ('en', 'English'), - ('ja', 'Japanese')), - LOCALE_PATHS=['horizon/locale', - 'openstack_dashboard/locale']) - def test_language_list_all_translated(self): - with mock.patch.object( - forms.gettext_module, 'find', - side_effect=LanguageTest._patch_gettext_find_all_translated): - languages = forms._get_languages() - self.assertEqual(['de', 'en', 'ja'], - [code for code, name in languages]) - - @override_settings(LANGUAGES=(('de', 'Germany'), - ('en', 'English'), - ('fr', 'French'), - ('ja', 'Japanese')), - LOCALE_PATHS=['horizon/locale', - 'openstack_dashboard/locale']) - def test_language_list_partially_translated(self): - def _patch_gettext_find(*args, **kwargs): - domain = args[0] - locale_path = args[1] - locale = args[2][0] - # Assume de and fr are partially translated. - if locale == translation.to_locale('de'): - if (domain == 'django' and - locale_path == 'openstack_dashboard/locale'): - return - elif locale == translation.to_locale('fr'): - if (domain == 'djangojs' and locale_path == 'horizon/locale'): - return - return '%s/%s/LC_MESSAGES/%s.mo' % (locale_path, locale, - domain) - with mock.patch.object( - forms.gettext_module, 'find', - side_effect=_patch_gettext_find): - languages = forms._get_languages() - self.assertEqual(['en', 'ja'], - [code for code, name in languages]) - - @override_settings(LANGUAGES=(('de', 'Germany'), - ('ja', 'Japanese')), - LOCALE_PATHS=['horizon/locale', - 'openstack_dashboard/locale']) - def test_language_list_no_english(self): - with mock.patch.object( - forms.gettext_module, 'find', - side_effect=LanguageTest._patch_gettext_find_all_translated): - languages = forms._get_languages() - self.assertEqual(['de', 'ja'], - [code for code, name in languages]) - - @override_settings(LANGUAGES=(('de', 'Germany'), - ('en', 'English'), - ('ja', 'Japanese')), - LOCALE_PATHS=['horizon/locale', - 'openstack_dashboard/locale']) - def test_language_list_with_untranslated_language(self): - def _patch_gettext_find(*args, **kwargs): - domain = args[0] - locale_path = args[1] - locale = args[2][0] - # Assume ja is not translated - if locale == translation.to_locale('ja'): - return - return '%s/%s/LC_MESSAGES/%s.mo' % (locale_path, locale, domain) - with mock.patch.object( - forms.gettext_module, 'find', - side_effect=_patch_gettext_find): - languages = forms._get_languages() - self.assertEqual(['de', 'en'], - [code for code, name in languages]) - - @override_settings(LANGUAGES=(('es', 'Spanish'), - ('es-ar', 'Argentinian Spanish'), - ('es-mx', 'Mexican Spanish'), - ('en', 'English')), - LOCALE_PATHS=['horizon/locale', - 'openstack_dashboard/locale']) - def test_language_list_with_untranslated_same_prefix(self): - def _patch_gettext_find(*args, **kwargs): - domain = args[0] - locale_path = args[1] - locale = args[2][0] - # Assume es-ar is not translated and - # es-mx is partially translated. - # es is returned as fallback. - if locale == translation.to_locale('es-ar'): - locale = translation.to_locale('es') - elif (locale == translation.to_locale('es-mx') and - locale_path == 'openstack_dashboard/locale'): - locale = translation.to_locale('es') - return '%s/%s/LC_MESSAGES/%s.mo' % (locale_path, locale, - domain) - with mock.patch.object( - forms.gettext_module, 'find', - side_effect=_patch_gettext_find): - languages = forms._get_languages() - self.assertEqual(['en', 'es'], - [code for code, name in languages]) - - @override_settings(LANGUAGES=(('en', 'English'), - ('pt', 'Portuguese'), - ('pt-br', 'Brazilian Portuguese')), - LOCALE_PATHS=['horizon/locale', - 'openstack_dashboard/locale']) - def test_language_list_with_both_translated_same_prefix(self): - with mock.patch.object( - forms.gettext_module, 'find', - side_effect=LanguageTest._patch_gettext_find_all_translated): - languages = forms._get_languages() - self.assertEqual(['en', 'pt', 'pt-br'], - [code for code, name in languages]) + # Unknown language + self.assertContains(res, 'Unknown Language') diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index d99618c02c..d2521b944c 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -523,30 +523,6 @@ TIME_ZONE = "UTC" # ('material', 'Material', 'themes/material'), #] -# By default all languages with translation catalogs are enabled. -# If you would like to enable a specific set of languages, -# you can do this by setting LANGUAGES list below. -# Each entry is a tuple of language code and language name. -# LANGUAGES = ( -# ('cs', 'Czech'), -# ('de', 'German'), -# ('en', 'English'), -# ('en-au', 'Australian English'), -# ('en-gb', 'British English'), -# ('es', 'Spanish'), -# ('fr', 'French'), -# ('id', 'Indonesian'), -# ('it', 'Italian'), -# ('ja', 'Japanese'), -# ('ko', 'Korean (Korea)'), -# ('pl', 'Polish'), -# ('pt-br', 'Portuguese (Brazil)'), -# ('ru', 'Russian'), -# ('tr', 'Turkish'), -# ('zh-cn', 'Simplified Chinese'), -# ('zh-tw', 'Chinese (Taiwan)'), -# ) - LOGGING = { 'version': 1, # When set to True this will disable all logging except diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index eae39e4bf7..51db4cab04 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -212,6 +212,25 @@ SESSION_COOKIE_MAX_SIZE = 4093 # https://bugs.launchpad.net/horizon/+bug/1349463 SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' +LANGUAGES = ( + ('cs', 'Czech'), + ('de', 'German'), + ('en', 'English'), + ('en-au', 'Australian English'), + ('en-gb', 'British English'), + ('es', 'Spanish'), + ('fr', 'French'), + ('id', 'Indonesian'), + ('it', 'Italian'), + ('ja', 'Japanese'), + ('ko', 'Korean (Korea)'), + ('pl', 'Polish'), + ('pt-br', 'Portuguese (Brazil)'), + ('ru', 'Russian'), + ('tr', 'Turkish'), + ('zh-cn', 'Simplified Chinese'), + ('zh-tw', 'Chinese (Taiwan)'), +) LANGUAGE_CODE = 'en' LANGUAGE_COOKIE_NAME = 'horizon_language' USE_I18N = True diff --git a/releasenotes/notes/language-list-generated-automatically-4a9bf752752d09f6.yaml b/releasenotes/notes/language-list-generated-automatically-4a9bf752752d09f6.yaml deleted file mode 100644 index 91b35b22c4..0000000000 --- a/releasenotes/notes/language-list-generated-automatically-4a9bf752752d09f6.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -features: - - | - The available language list is now automatically generated based on - the availability of translation message catalogs of languages - instead of maintaining the language list manually. - If message catalogs (PO files) of some language exist for both - django and djangojs domains of horizon and openstack_dashboard, - the language will appear in the language list. - If you need to change the initial set of languages, - set ``LANGUAGES`` in ``local_settings.py``.