diff --git a/lib/l10n_utils/__init__.py b/lib/l10n_utils/__init__.py index 1ce60ca1eef..782a135a22d 100644 --- a/lib/l10n_utils/__init__.py +++ b/lib/l10n_utils/__init__.py @@ -1,9 +1,12 @@ from django.conf import settings +from django.http import HttpResponseRedirect -from dotlang import get_lang_path import jingo +from funfactory.urlresolvers import split_path from jinja2.exceptions import TemplateNotFound +from dotlang import get_lang_path, lang_file_is_active + def render(request, template, context={}, **kwargs): """ @@ -18,8 +21,21 @@ def render(request, template, context={}, **kwargs): if present, otherwise, it'll render the specified (en-US) template. """ + # Every template gets its own .lang file, so figure out what it is + # and pass it in the context + context['langfile'] = get_lang_path(template) + # Look for localized template if not default lang. if request.locale != settings.LANGUAGE_CODE: + + # redirect to default lang if locale not active + if not (settings.DEV or + lang_file_is_active(context['langfile'], request.locale)): + return HttpResponseRedirect('/' + '/'.join([ + settings.LANGUAGE_CODE, + split_path(request.get_full_path())[1] + ])) + localized_tmpl = '%s/templates/%s' % (request.locale, template) try: return jingo.render(request, localized_tmpl, context, **kwargs) @@ -27,8 +43,4 @@ def render(request, template, context={}, **kwargs): # If not found, just go on and try rendering the parent template. pass - # Every template gets its own .lang file, so figure out what it is - # and pass it in the context - context['langfile'] = get_lang_path(template) - return jingo.render(request, template, context, **kwargs) diff --git a/lib/l10n_utils/dotlang.py b/lib/l10n_utils/dotlang.py index 8aea9cb5a05..be489e34d8e 100644 --- a/lib/l10n_utils/dotlang.py +++ b/lib/l10n_utils/dotlang.py @@ -155,3 +155,30 @@ def get_lang_path(path): path = '/'.join(p) base, ext = os.path.splitext(path) return base + + +def lang_file_is_active(path, lang): + """ + If the lang file for a locale exists and has the correct comment returns + True, and False otherwise. + :param path: the relative lang file name + :param lang: the language code + :return: bool + """ + rel_path = os.path.join('locale', lang, '%s.lang' % path) + cache_key = 'active:%s' % rel_path + is_active = cache.get(cache_key) + if is_active is None: + is_active = False + fpath = os.path.join(settings.ROOT, rel_path) + try: + with codecs.open(fpath, 'r', 'utf-8', errors='replace') as lines: + firstline = lines.readline() + if firstline.startswith('## active ##'): + is_active = True + except IOError: + pass + + cache.set(cache_key, is_active, settings.DOTLANG_CACHE) + + return is_active diff --git a/lib/l10n_utils/tests/test_dotlang.py b/lib/l10n_utils/tests/test_dotlang.py index 31bf8b886cf..1ce47e841b2 100644 --- a/lib/l10n_utils/tests/test_dotlang.py +++ b/lib/l10n_utils/tests/test_dotlang.py @@ -4,17 +4,74 @@ from django.conf import settings from django.core import mail +from django.core.urlresolvers import clear_url_caches +from django.test.client import Client +from jingo import env +from jinja2 import FileSystemLoader from mock import patch -from nose.tools import assert_not_equal, eq_ +from nose.tools import assert_not_equal, eq_, ok_ +from pyquery import PyQuery as pq from tower.management.commands.extract import extract_tower_python -from l10n_utils.dotlang import _, FORMAT_IDENTIFIER_RE, parse, translate +from l10n_utils.dotlang import (_, FORMAT_IDENTIFIER_RE, lang_file_is_active, + parse, translate) from mozorg.tests import TestCase ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files') LANG_FILES = 'test_file' +TEMPLATE_DIRS = (os.path.join(ROOT, 'templates'),) + + +@patch.object(env, 'loader', FileSystemLoader(TEMPLATE_DIRS)) +@patch.object(settings, 'ROOT_URLCONF', 'l10n_utils.tests.test_files.urls') +@patch.object(settings, 'ROOT', ROOT) +class TestLangFilesActivation(TestCase): + def setUp(self): + clear_url_caches() + self.client = Client() + + @patch.object(settings, 'DEV', False) + def test_lang_file_is_active(self): + """ + `lang_file_is_active` should return true if lang file has the + comment, and false otherwise. + """ + ok_(lang_file_is_active('active_de_lang_file', 'de')) + ok_(not lang_file_is_active('active_de_lang_file', 'es')) + ok_(not lang_file_is_active('inactive_de_lang_file', 'de')) + ok_(not lang_file_is_active('does_not_exist', 'de')) + + @patch.object(settings, 'DEV', False) + def test_active_locale_not_redirected(self): + """ Active lang file should render correctly. """ + response = self.client.get('/de/active-de-lang-file/') + eq_(response.status_code, 200) + doc = pq(response.content) + eq_(doc('h1').text(), 'Die Lage von Mozilla') + + @patch.object(settings, 'DEV', False) + @patch.object(settings, 'LANGUAGE_CODE', 'en-US') + def test_inactive_locale_redirected(self): + """ Inactive locale should redirect to en-US. """ + response = self.client.get('/de/inactive-de-lang-file/') + eq_(response.status_code, 302) + eq_(response['location'], + 'http://testserver/en-US/inactive-de-lang-file/') + response = self.client.get('/de/inactive-de-lang-file/', follow=True) + doc = pq(response.content) + eq_(doc('h1').text(), 'The State of Mozilla') + + @patch.object(settings, 'DEV', True) + def test_inactive_locale_not_redirected_dev_true(self): + """ + Inactive lang file should not redirect in DEV mode. + """ + response = self.client.get('/de/inactive-de-lang-file/') + eq_(response.status_code, 200) + doc = pq(response.content) + eq_(doc('h1').text(), 'Die Lage von Mozilla') class TestDotlang(TestCase): diff --git a/lib/l10n_utils/tests/test_files/locale/de/active_de_lang_file.lang b/lib/l10n_utils/tests/test_files/locale/de/active_de_lang_file.lang new file mode 100644 index 00000000000..8de010866e4 --- /dev/null +++ b/lib/l10n_utils/tests/test_files/locale/de/active_de_lang_file.lang @@ -0,0 +1,8 @@ +## active ## + +;The State of Mozilla +Die Lage von Mozilla + + +;Mozilla‘s vision of the Internet is a place where anyone can access information, a place where everyone can hack and tinker; one that has openness, freedom and transparency; where users have control over their personal data and where all minds have the freedom to create and to consume without walls or tight restrictions. +Mozillas Vision des Internets ist ein Ort, wo jeder auf Informationen zugreifen kann, ein Ort, wo jeder programmieren und herumspielen kann; einer, der offen, frei und transparent ist; wo Benutzer die Kontrolle über ihre persönlichen Daten haben und wo jeder Geist die Freiheit hat, zu schaffen und zu konsumieren, ohne Mauern oder enge Einschränkungen. diff --git a/lib/l10n_utils/tests/test_files/locale/de/inactive_de_lang_file.lang b/lib/l10n_utils/tests/test_files/locale/de/inactive_de_lang_file.lang new file mode 100644 index 00000000000..b03fe517d4e --- /dev/null +++ b/lib/l10n_utils/tests/test_files/locale/de/inactive_de_lang_file.lang @@ -0,0 +1,6 @@ +;The State of Mozilla +Die Lage von Mozilla + + +;Mozilla‘s vision of the Internet is a place where anyone can access information, a place where everyone can hack and tinker; one that has openness, freedom and transparency; where users have control over their personal data and where all minds have the freedom to create and to consume without walls or tight restrictions. +Mozillas Vision des Internets ist ein Ort, wo jeder auf Informationen zugreifen kann, ein Ort, wo jeder programmieren und herumspielen kann; einer, der offen, frei und transparent ist; wo Benutzer die Kontrolle über ihre persönlichen Daten haben und wo jeder Geist die Freiheit hat, zu schaffen und zu konsumieren, ohne Mauern oder enge Einschränkungen. diff --git a/lib/l10n_utils/tests/test_files/templates/active_de_lang_file.html b/lib/l10n_utils/tests/test_files/templates/active_de_lang_file.html new file mode 100644 index 00000000000..b7ab04e8f6e --- /dev/null +++ b/lib/l10n_utils/tests/test_files/templates/active_de_lang_file.html @@ -0,0 +1,14 @@ + +
++ {% trans %} + Mozilla‘s vision of the Internet is a place where anyone can + access information, a place where everyone can hack and tinker; + one that has openness, freedom and transparency; where users have + control over their personal data and where all minds have the + freedom to create and to consume without walls or tight restrictions. + {% endtrans %} +
+ + diff --git a/lib/l10n_utils/tests/test_files/templates/inactive_de_lang_file.html b/lib/l10n_utils/tests/test_files/templates/inactive_de_lang_file.html new file mode 100644 index 00000000000..b7ab04e8f6e --- /dev/null +++ b/lib/l10n_utils/tests/test_files/templates/inactive_de_lang_file.html @@ -0,0 +1,14 @@ + + ++ {% trans %} + Mozilla‘s vision of the Internet is a place where anyone can + access information, a place where everyone can hack and tinker; + one that has openness, freedom and transparency; where users have + control over their personal data and where all minds have the + freedom to create and to consume without walls or tight restrictions. + {% endtrans %} +
+ + diff --git a/lib/l10n_utils/tests/test_files/urls.py b/lib/l10n_utils/tests/test_files/urls.py index 082b6412166..04ea291025f 100644 --- a/lib/l10n_utils/tests/test_files/urls.py +++ b/lib/l10n_utils/tests/test_files/urls.py @@ -4,4 +4,6 @@ urlpatterns = patterns('', page('trans-block-reload-test', 'trans_block_reload_test.html'), + page('active-de-lang-file', 'active_de_lang_file.html'), + page('inactive-de-lang-file', 'inactive_de_lang_file.html'), ) diff --git a/lib/l10n_utils/tests/test_template.py b/lib/l10n_utils/tests/test_template.py index 39cc9000b3c..9ee1036ff11 100644 --- a/lib/l10n_utils/tests/test_template.py +++ b/lib/l10n_utils/tests/test_template.py @@ -18,14 +18,14 @@ TEMPLATE_DIRS = (os.path.join(ROOT, 'templates'),) +@patch.object(env, 'loader', FileSystemLoader(TEMPLATE_DIRS)) +@patch.object(settings, 'ROOT_URLCONF', 'l10n_utils.tests.test_files.urls') +@patch.object(settings, 'ROOT', ROOT) class TestTransBlocks(TestCase): def setUp(self): clear_url_caches() self.client = Client() - @patch.object(env, 'loader', FileSystemLoader(TEMPLATE_DIRS)) - @patch.object(settings, 'ROOT_URLCONF', 'l10n_utils.tests.test_files.urls') - @patch.object(settings, 'ROOT', ROOT) def test_trans_block_works(self): """ Sanity check to make sure translations work at all. """ response = self.client.get('/de/trans-block-reload-test/')