diff --git a/apps/legal/templates/eula/index.html b/apps/legal/templates/legal/eula.html similarity index 100% rename from apps/legal/templates/eula/index.html rename to apps/legal/templates/legal/eula.html diff --git a/apps/legal/urls.py b/apps/legal/urls.py index 6ee2c1e9ae..a11ba48db7 100644 --- a/apps/legal/urls.py +++ b/apps/legal/urls.py @@ -6,5 +6,5 @@ from django.conf.urls.defaults import * from mozorg.util import page urlpatterns = patterns('', - page('eula', 'eula/index.html'), -) \ No newline at end of file + page('eula', 'legal/eula.html'), +) diff --git a/lib/bedrock_util.py b/lib/bedrock_util.py index 45735f6ae7..9927227654 100644 --- a/lib/bedrock_util.py +++ b/lib/bedrock_util.py @@ -23,3 +23,18 @@ def secure_required(view_func): def server_error_view(request, template_name='500.html'): """500 error handler that runs context processors.""" return l10n_utils.render(request, template_name) + + +# backported from Django 1.4 +# https://github.com/django/django/blob/master/django/utils/functional.py#L34 +class cached_property(object): + """ + Decorator that converts a method with a single self argument into a + property cached on the instance. + """ + def __init__(self, func): + self.func = func + + def __get__(self, instance, type): + res = instance.__dict__[self.func.__name__] = self.func(instance) + return res diff --git a/lib/l10n_utils/management/commands/l10n_check.py b/lib/l10n_utils/management/commands/l10n_check.py index 03e36b2521..3cc369eb87 100644 --- a/lib/l10n_utils/management/commands/l10n_check.py +++ b/lib/l10n_utils/management/commands/l10n_check.py @@ -3,35 +3,27 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import datetime +import errno import itertools import re -import os, errno +import os from os import path -from optparse import make_option import codecs from contextlib import closing from StringIO import StringIO -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from django.conf import settings -from jinja2 import Environment, TemplateNotFound -from jinja2.parser import Parser +from jinja2 import Environment + +from bedrock_util import cached_property def l10n_file(*args): return path.join(settings.ROOT, 'locale', *args) -def l10n_tmpl(tmpl, lang): - return l10n_file(lang, 'templates', tmpl) - - -def app_tmpl(tmpl): - app = tmpl[:tmpl.index('/')] - return path.join(settings.ROOT, 'apps', app, 'templates', tmpl) - - def list_templates(): """List all the templates in all the installed apps""" @@ -45,27 +37,16 @@ def list_templates(): name, ext = os.path.splitext(filename) if ext in ['.txt', '.html']: - full_path = os.path.join(root, filename) - yield full_path.replace(tmpl_dir, '').lstrip('/') + yield os.path.join(root, filename) def update_templates(langs): """List templates with outdated/incorrect l10n blocks""" for tmpl in list_templates(): - print "%s..." % tmpl - - # Parse the reference template that will provide new content - # and only get the blocks from it - parser = L10nParser() - blocks = list(parser.parse_template(app_tmpl(tmpl), - only_blocks=True)) - - for lang in langs: - if path.exists(l10n_tmpl(tmpl, lang)): - update_template(tmpl, blocks, lang) - else: - copy_template(tmpl, blocks, lang) + template = L10nTemplate(tmpl) + print "%s..." % template.rel_path + template.process(langs) def get_todays_version(): @@ -82,22 +63,102 @@ def ensure_dir_exists(path): except OSError as exc: if exc.errno == errno.EEXIST: pass - else: raise + else: + raise -def update_template(tmpl, blocks, lang): - """Detect outdated l10n blocks and update the template""" +def write_block(block, dest, force_was=False): + """Write out a block to an l10n template""" + dest.write('{%% l10n %s %%}\n' % block['name']) + dest.write(block['main']) + if block['was'] or force_was: + dest.write('\n{% was %}') + dest.write('\n%s' % block['was'] if block['was'] else '') + dest.write('\n{% endl10n %}') + dest.write('\n\n') - def get_ref_block(name): + +class L10nTemplate(object): + + def __init__(self, template=None, source=None): + """ + Initialize the template class. + + :param template: + Full path to a template file. + :param source: + Template text as a string if there is no template file. + """ + self.full_path = template + self.source = source + self.parser = L10nParser() + + @cached_property + def rel_path(self): + """ + Return the part of the template path after the 'templates' directory. + """ + args = self.full_path.split(path.sep) + args = args[args.index('templates') + 1:] + return path.join(*args) + + def l10n_path(self, lang): + """ + Return the path to the localized version of the template for lang. + """ + return l10n_file(lang, 'templates', self.rel_path) + + @cached_property + def blocks(self): + if self.full_path: + blocks = self.parser.parse_template(self.full_path, + only_blocks=True) + elif self.source: + blocks = self.parser.parse(self.source, only_blocks=True) + return tuple(blocks) + + def blocks_for_lang(self, lang): + """Filter blocks to only those that allow this locale or all locales.""" + return tuple(b for b in self.blocks + if not b['locales'] or lang in b['locales']) + + def process(self, langs): + """ + Update existing templates and create new ones for specified langs. + """ + for lang in langs: + if path.exists(self.l10n_path(lang)): + self.update(lang) + else: + self.copy(lang) + + def copy(self, lang): + """Create a new l10n template by copying the l10n blocks""" + blocks = self.blocks_for_lang(lang) + if not blocks: + return + + dest_file = self.l10n_path(lang) + + # Make sure the templates directory for this locale and app exists + ensure_dir_exists(os.path.dirname(dest_file)) + + with codecs.open(dest_file, 'w', 'utf-8') as dest: + dest.write('{# Version: %s #}\n\n' % get_todays_version()) + dest.write('{%% extends "%s" %%}\n\n' % self.rel_path) + + for block in blocks: + write_block(block, dest) + + print '%s: %s' % (lang, self.rel_path) + + def _get_ref_block(self, name, blocks=None): """Return the reference block""" + blocks = blocks or self.blocks + return next((b for b in blocks if b['name'] == name), None) - for block in blocks: - if block['name'] == name: - return block - - def transfer_content(l10n_block, ref_block): + def _transfer_content(self, l10n_block, ref_block): """Transfer any new content from the reference block""" - if ref_block: # Update if the l10n file is older than this block if l10n_block['version'] < ref_block['version']: @@ -108,100 +169,62 @@ def update_template(tmpl, blocks, lang): l10n_block['main'] = ref_block['main'] l10n_block['locales'] = ref_block['locales'] - return l10n_block + def update(self, lang): + """Detect outdated l10n blocks and update the template""" + blocks = self.blocks_for_lang(lang) + if not blocks: + return - parser = L10nParser() - file_version = None - dest_tmpl = l10n_tmpl(tmpl, lang) - halted = False - written_blocks = [] + file_version = None + parser = L10nParser() + dest_tmpl = self.l10n_path(lang) + written_blocks = [] - # Make sure the templates directory for this locale and app exists - ensure_dir_exists(os.path.dirname(dest_tmpl)) + # Make sure the templates directory for this locale and app exists + ensure_dir_exists(os.path.dirname(dest_tmpl)) - # Parse the l10n template, run through it and update it where - # appropriate into a new template file - with closing(StringIO()) as buffer: - for token in parser.parse_template(dest_tmpl, strict=False, - halt_on_content=True): - if not token: - # If False is returned, that means a content block - # exists so we don't do anything to the template since - # it's customized - return - elif token[0] == 'content': - buffer.write(token[1]) - elif token[0] == 'version': - buffer.write('{# Version: %s #}' % get_todays_version()) - file_version = token[1] - elif token[0] == 'block': - if not file_version: - raise Exception('l10n file version tag does not exist ' - 'before initial l10n block') + # Parse the l10n template, run through it and update it where + # appropriate into a new template file + with closing(StringIO()) as buffer: + for token in parser.parse_template(dest_tmpl, strict=False, + halt_on_content=True): + if not token: + # If False is returned, that means a content block + # exists so we don't do anything to the template since + # it's customized + return + elif token[0] == 'content': + buffer.write(token[1]) + elif token[0] == 'version': + buffer.write('{# Version: %s #}' % get_todays_version()) + file_version = token[1] + elif token[0] == 'block': + if not file_version: + raise Exception('l10n file version tag does not exist ' + 'before initial l10n block') - # We have an l10n block, set its version and keep - # track of it for later use - l10n_block = token[1] - l10n_block['version'] = file_version - name = l10n_block['name'] - written_blocks.append(name) + # We have an l10n block, set its version and keep + # track of it for later use + l10n_block = token[1] + l10n_block['version'] = file_version + name = l10n_block['name'] + written_blocks.append(name) - # Update the block and write it out - l10n_block = transfer_content(l10n_block, - get_ref_block(name)) - locales = l10n_block['locales'] - if not locales or lang in locales: + # Update the block and write it out + self._transfer_content(l10n_block, + self._get_ref_block(name, blocks)) write_block(l10n_block, buffer) - # Check for any missing blocks - for block in blocks: - if block['name'] not in written_blocks: - locales = block['locales'] - if not locales or lang in locales: - buffer.write('\n\n') + # Check for any missing blocks + for block in blocks: + if block['name'] not in written_blocks: write_block(block, buffer) - # Write out the result to the l10n template - with codecs.open(dest_tmpl, 'w', 'utf-8') as dest: - dest.write(buffer.getvalue()) + # Write out the result to the l10n template + with codecs.open(dest_tmpl, 'w', 'utf-8') as dest: + dest.write(buffer.getvalue()) - print '%s: %s' % (lang, tmpl) - -def write_block(block, dest, force_was=False): - """Write out a block to an l10n template""" - - dest.write('{%% l10n %s %%}\n' % block['name']) - dest.write(block['main']) - if block['was'] or force_was: - dest.write('\n{% was %}') - dest.write('\n%s' % block['was'] if block['was'] else '') - dest.write('\n{% endl10n %}') - - -def copy_template(tmpl, blocks, lang): - """Create a new l10n template by copying the l10n blocks""" - - dest_file = l10n_tmpl(tmpl, lang) - - #If there is only one block and not for current lang - # no need to create the file. - if len(blocks) == 1 and not lang in blocks[0]['locales']: - return - - if blocks: - # Make sure the templates directory for this locale and app exists - ensure_dir_exists(os.path.dirname(dest_file)) - - with codecs.open(dest_file, 'w', 'utf-8') as dest: - dest.write('{# Version: %s #}\n\n' % get_todays_version()) - dest.write('{%% extends "%s" %%}\n\n' % tmpl) - - for block in blocks: - #Copy only for the allowed locales - locales = block['locales'] - if not locales or lang in locales: - write_block(block, dest) - dest.write('\n\n') + print '%s: %s' % (lang, self.rel_path) class L10nParser(): @@ -273,7 +296,7 @@ class L10nParser(): if not version: raise Exception('Invalid version metadata in ' - 'template: %s '% self.tmpl) + 'template: %s' % self.tmpl) yield ('version', version) self.scan_until('comment_end') @@ -341,7 +364,7 @@ class L10nParser(): if not block_version: raise Exception("Invalid l10n block declaration: " "bad version '%s' in %s" - % (block_name, self.tmpl)) + % (block_version, self.tmpl)) return block_version def parse_block(self, strict=True): @@ -353,48 +376,31 @@ class L10nParser(): self.scan_ignore('whitespace') - operator = self.scan_next('operator') - if operator == ',' or operator == ';': - self.scan_ignore('whitespace') - version_str = self.scan_next('integer') - if version_str: - block_version = self.get_block_version(version_str) - else: #We have new template - self.scan_ignore('whitespace') - locale_flag = self.scan_next('name') - if self.scan_next('operator') == '=': - past_token = None - while True: - token = self.scan_next('name') - operator = self.scan_next('operator') - if operator == ',': - if past_token: - token = '%s-%s' % (past_token, token) - past_token = None - locales.append(token) - continue - elif operator == '-': - past_token = token - continue - elif not operator: - if past_token: - token = '%s-%s' % (past_token, token) - past_token = None - locales.append(token) - break - self.scan_ignore('whitespace') - self.scan_next('operator') - self.scan_ignore('whitespace') - - version_str = self.scan_next('integer') - block_version = self.get_block_version(version_str) + # Grab the locales if provided + prev_sub = False + for _, token_type, token_value in self.tokens: + if token_type in ['integer', 'block_end']: + break + if token_type == 'operator' and token_value in [',', '=']: + continue + if token_type == 'name' and token_value != 'locales': + if prev_sub: + locales[-1] += token_value + prev_sub = False + else: + locales.append(token_value) + if token_type == 'operator' and token_value == '-': + locales[-1] += '-' + prev_sub = True + if token_type == 'integer': + block_version = self.get_block_version(token_value) + self.scan_until('block_end') elif strict: raise Exception("Invalid l10n block declaration: " "missing date for block '%s' in %s" % (block_name, self.tmpl)) - self.scan_until('block_end') (main, was_) = self.block_content() yield ('block', {'name': block_name, 'version': block_version, @@ -467,6 +473,6 @@ class Command(BaseCommand): langs = args else: langs = os.listdir(l10n_file()) - langs = filter(lambda x: x[0] != '.' , langs) + langs = filter(lambda x: x[0] != '.', langs) update_templates(langs) diff --git a/lib/l10n_utils/template.py b/lib/l10n_utils/template.py index 8ef11b45bd..f42fa51bea 100644 --- a/lib/l10n_utils/template.py +++ b/lib/l10n_utils/template.py @@ -30,10 +30,32 @@ class L10nBlockExtension(Extension): # Block name is mandatory. name = parser.stream.expect('name').value + locales = [] - # Comma optional. parser.stream.skip_if('comma') + # Grab the locales if provided + if parser.stream.current.type == 'name': + parser.stream.skip() # locales + parser.stream.skip() # assign (=) + prev_sub = False + while parser.stream.current.type not in ['integer', 'block_end']: + parser.stream.skip_if('comma') + parser.stream.skip_if('assign') + token = parser.stream.current + if token.type in ['integer', 'block_end']: + break + if token.type == 'name': + if prev_sub: + locales[-1] += token.value + prev_sub = False + else: + locales.append(token.value) + if token.type == 'sub': + locales[-1] += '-' + prev_sub = True + parser.stream.next() + # Add version if provided. if parser.stream.current.type == 'integer': version = int(parser.parse_expression().value) @@ -55,6 +77,7 @@ class L10nBlockExtension(Extension): node.set_lineno(lineno) node.name = '__l10n__{0}'.format(name) node.version = version # For debugging only, for now. + node.locales = locales node.body = body # I *think*, `true` would mean that variable assignments inside this # block do not persist beyond this block (like a `with` block). diff --git a/lib/l10n_utils/tests/__init__.py b/lib/l10n_utils/tests/__init__.py index e69de29bb2..7f8b884d5b 100644 --- a/lib/l10n_utils/tests/__init__.py +++ b/lib/l10n_utils/tests/__init__.py @@ -0,0 +1,12 @@ +from tempfile import TemporaryFile +from textwrap import dedent + + +class TempFileMixin(object): + """Provide a method for getting a temp file that is removed when closed.""" + def tempfile(self, data=None): + tempf = TemporaryFile() + if data: + tempf.write(dedent(data)) + tempf.seek(0) + return tempf diff --git a/lib/l10n_utils/tests/test_commands.py b/lib/l10n_utils/tests/test_commands.py index f9d47e84d7..29bf52c20e 100644 --- a/lib/l10n_utils/tests/test_commands.py +++ b/lib/l10n_utils/tests/test_commands.py @@ -1,27 +1,42 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +import codecs +from os import path +from StringIO import StringIO +from textwrap import dedent from django.utils import unittest -from l10n_utils.management.commands.l10n_check import (list_templates, - L10nParser) +from mock import MagicMock, patch + +from l10n_utils.management.commands.l10n_check import ( + get_todays_version, + L10nParser, + L10nTemplate, + list_templates, + update_templates, +) + + +ROOT = path.join(path.dirname(path.abspath(__file__)), 'test_files') +TEMPLATE_DIRS = (path.join(ROOT, 'templates'),) class TestL10nCheck(unittest.TestCase): def _get_block(self, blocks, name): """Out of all blocks, grab the one with the specified name.""" - try: - return filter(lambda b: b['name'] == name, blocks)[0] - except IndexError: - return None + for b in blocks: + if b['name'] == name: + return b + return None def test_list_templates(self): """Make sure we capture both html and txt templates.""" TEMPLATES = ['mozorg/home.html', 'mozorg/emails/other.txt'] - tmpls = filter(lambda tmpl: tmpl in TEMPLATES, - list_templates()) + tmpls = [t for t in list_templates() + if L10nTemplate(t).rel_path in TEMPLATES] assert len(tmpls) == len(TEMPLATES) def test_parse_templates(self): @@ -29,14 +44,15 @@ class TestL10nCheck(unittest.TestCase): correctly.""" parser = L10nParser() - blocks = parser.parse('foo bar bizzle what? ' - '{% l10n baz, 20110914 %}' - 'mumble' - '{% was %}' - 'wased' - '{% endl10n %}' - 'qux', - only_blocks=True) + blocks = parser.parse(""" + foo bar bizzle what? + {% l10n baz, 20110914 %} + mumble + {% was %} + wased + {% endl10n %} + qux + """, only_blocks=True) baz = self._get_block(blocks, 'baz') @@ -44,16 +60,18 @@ class TestL10nCheck(unittest.TestCase): self.assertEqual(baz['was'], 'wased') self.assertEqual(baz['version'], 20110914) - blocks = parser.parse('foo bar bizzle what? ' - '{% l10n baz ; locales=ru,bn-IN ; 20110914 %}' - 'mumble' - '{% endl10n %}' - 'qux', - only_blocks=True) + blocks = parser.parse(""" + foo bar bizzle what? + {% l10n baz locales=ru,bn-IN,fr 20110914 %} + mumble + {% endl10n %} + qux + """, only_blocks=True) baz = self._get_block(blocks, 'baz') self.assertEqual(baz['main'], 'mumble') - self.assertEqual(baz['locales'], ['ru', 'bn-IN']) + self.assertEqual(baz['locales'], ['ru', 'bn-IN', 'fr']) + self.assertEqual(baz['version'], 20110914) def test_content_halt(self): """Make sure the parser will halt on the content block if told @@ -68,6 +86,160 @@ class TestL10nCheck(unittest.TestCase): self.assertEqual(last_token, False) - # I need help writing tests for copy_template and update_template, - # which read files from the filesystem and write to it. I think I - # need to mock those somehow. + def test_filter_blocks(self): + """Should return a list of blocks appropriate for a given lang""" + template = L10nTemplate(source=""" + {% l10n dude locales=fr,es-ES,ru 20121212 %} + This aggression will not stand, man. + {% endl10n %} + {% l10n walter, locales=es-ES,ru 20121212 %} + I'm stayin'. Finishin' my coffee. + {% endl10n %} + {% l10n donnie 20121212 %} + Phone's ringing Dude. + {% endl10n %} + """) + + lang_blocks = template.blocks_for_lang('fr') + self.assertEqual(len(lang_blocks), 2) + self.assertEqual(lang_blocks[0]['name'], 'dude') + self.assertEqual(lang_blocks[1]['name'], 'donnie') + + lang_blocks = template.blocks_for_lang('es-ES') + self.assertEqual(len(lang_blocks), 3) + self.assertEqual(lang_blocks[0]['name'], 'dude') + self.assertEqual(lang_blocks[1]['name'], 'walter') + self.assertEqual(lang_blocks[2]['name'], 'donnie') + + lang_blocks = template.blocks_for_lang('pt-BR') + self.assertEqual(len(lang_blocks), 1) + self.assertEqual(lang_blocks[0]['name'], 'donnie') + + @patch('l10n_utils.management.commands.l10n_check.settings.ROOT', ROOT) + @patch('l10n_utils.management.commands.l10n_check.list_templates') + @patch('l10n_utils.management.commands.l10n_check.L10nTemplate.copy') + @patch('l10n_utils.management.commands.l10n_check.L10nTemplate.update') + def test_process_template(self, update_mock, copy_mock, lt_mock): + """ + template.process() should update existing templates and create missing + ones. It should only do so for the right locales. + """ + lt_mock.return_value = [ + path.join(TEMPLATE_DIRS[0], 'l10n_blocks_with_langs.html'), + path.join(TEMPLATE_DIRS[0], 'l10n_blocks_without_langs.html'), + ] + update_templates(['de']) + copy_mock.assert_called_once_with('de') + update_mock.assert_called_once_with('de') + + def test_blocks_called_once(self): + """ + Test that the cached_property decorator really works in our situation. + """ + template = L10nTemplate(source=""" + {% l10n donnie 20121212 %} + Phone's ringing Dude. + {% endl10n %} + """) + with patch.object(template, 'parser') as mock_parser: + template.blocks + template.blocks_for_lang('de') + template.blocks + self.assertEqual(mock_parser.parse.call_count, 1) + + def test_update_template_no_lang(self): + """ + template.update() should skip files without blocks for the given locale. + """ + template = L10nTemplate(path.join(TEMPLATE_DIRS[0], + 'l10n_blocks_with_langs.html')) + # cause the template to be read and parsed before mocking open + template.blocks + codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open' + open_mock = MagicMock(spec=file) + with patch(codecs_open, open_mock): + template.update('zh-TW') + file_handle = open_mock.return_value.__enter__.return_value + assert not file_handle.write.called + template.update('de') + assert file_handle.write.called + + @patch('l10n_utils.management.commands.l10n_check.settings.ROOT', ROOT) + def test_update_template(self): + """ + template.update() should update lang specific templates. + """ + template = L10nTemplate(path.join(TEMPLATE_DIRS[0], + 'l10n_blocks_with_langs.html')) + # cause the template to be read and parsed before mocking open + template.blocks + codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open' + open_mock = MagicMock(spec=file) + open_buffer = StringIO() + # for writing the new file + open_mock.return_value.__enter__.return_value = open_buffer + # for reading the old file + open_mock().read.return_value = codecs.open( + template.l10n_path('de')).read() + + with patch(codecs_open, open_mock): + template.update('de') + + # braces doubled for .format() + good_value = dedent("""\ + {{# Version: {0} #}} + + {{% extends "l10n_blocks_with_langs.html" %}} + + {{% l10n donnie %}} + Phone's ringing Dude. + {{% was %}} + I am the walrus. + {{% endl10n %}}\n\n + """.format(get_todays_version())) + self.assertEqual(open_buffer.getvalue(), good_value) + + def test_copy_template_no_lang(self): + """ + template.copy() should skip files with no blocks for the given locale. + :return: + """ + template = L10nTemplate(path.join(TEMPLATE_DIRS[0], + 'l10n_blocks_with_langs.html')) + # cause the template to be read and parsed before mocking open + template.blocks + codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open' + open_mock = MagicMock(spec=file) + with patch(codecs_open, open_mock): + template.copy('zh-TW') + file_handle = open_mock.return_value.__enter__.return_value + assert not file_handle.write.called + template.copy('de') + assert file_handle.write.called + + def test_copy_template(self): + """ + template.copy() should create missing lang specific templates. + """ + template = L10nTemplate(path.join(TEMPLATE_DIRS[0], + 'l10n_blocks_without_langs.html')) + # cause the template to be read and parsed before mocking open + template.blocks + codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open' + open_mock = MagicMock(spec=file) + open_buffer = StringIO() + open_mock.return_value.__enter__.return_value = open_buffer + with patch(codecs_open, open_mock): + template.copy('de') + + # braces doubled for .format() + good_value = dedent("""\ + {{# Version: {0} #}} + + {{% extends "l10n_blocks_without_langs.html" %}} + + {{% l10n donnie %}} + Phone's ringing Dude. + {{% endl10n %}}\n + """.format(get_todays_version())) + self.assertEqual(open_buffer.getvalue(), good_value) diff --git a/lib/l10n_utils/tests/test_files/locale/de/templates/l10n_blocks_with_langs.html b/lib/l10n_utils/tests/test_files/locale/de/templates/l10n_blocks_with_langs.html new file mode 100644 index 0000000000..50c681c69f --- /dev/null +++ b/lib/l10n_utils/tests/test_files/locale/de/templates/l10n_blocks_with_langs.html @@ -0,0 +1,8 @@ +{# Version: 20120101 #} + +{% extends "l10n_blocks_with_langs.html" %} + +{% l10n donnie %} + I am the walrus. +{% endl10n %} + diff --git a/lib/l10n_utils/tests/test_files/templates/l10n_blocks_with_langs.html b/lib/l10n_utils/tests/test_files/templates/l10n_blocks_with_langs.html new file mode 100644 index 0000000000..8b11b50593 --- /dev/null +++ b/lib/l10n_utils/tests/test_files/templates/l10n_blocks_with_langs.html @@ -0,0 +1,11 @@ +{% l10n dude locales=fr,es-ES,ru 20121212 %} + This aggression will not stand, man. +{% endl10n %} + +{% l10n walter, locales=es-ES,ru 20121212 %} + I'm stayin'. Finishin' my coffee. +{% endl10n %} + +{% l10n donnie locales=ru,de 20121212 %} + Phone's ringing Dude. +{% endl10n %} diff --git a/lib/l10n_utils/tests/test_files/templates/l10n_blocks_without_langs.html b/lib/l10n_utils/tests/test_files/templates/l10n_blocks_without_langs.html new file mode 100644 index 0000000000..a6b591036d --- /dev/null +++ b/lib/l10n_utils/tests/test_files/templates/l10n_blocks_without_langs.html @@ -0,0 +1,11 @@ +{% l10n dude locales=fr,es-ES,ru 20121212 %} + This aggression will not stand, man. +{% endl10n %} + +{% l10n walter, locales=es-ES,ru 20121212 %} + I'm stayin'. Finishin' my coffee. +{% endl10n %} + +{% l10n donnie 20121212 %} + Phone's ringing Dude. +{% endl10n %} diff --git a/lib/l10n_utils/tests/test_gettext.py b/lib/l10n_utils/tests/test_gettext.py index 262c030e34..557f90ca99 100644 --- a/lib/l10n_utils/tests/test_gettext.py +++ b/lib/l10n_utils/tests/test_gettext.py @@ -3,8 +3,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os -from tempfile import TemporaryFile -from textwrap import dedent from django.conf import settings @@ -12,6 +10,7 @@ from mock import patch from nose.tools import eq_ from l10n_utils.gettext import langfiles_for_path, parse_python, parse_template +from l10n_utils.tests import TempFileMixin from mozorg.tests import TestCase @@ -19,15 +18,6 @@ ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files') TEMPLATE_DIRS = (os.path.join(ROOT, 'templates')) -class TempFileMixin(object): - """Provide a method for getting a temp file that is removed when closed.""" - def tempfile(self, data): - tempf = TemporaryFile() - tempf.write(dedent(data)) - tempf.seek(0) - return tempf - - class TestParseTemplate(TempFileMixin, TestCase): @patch('l10n_utils.gettext.codecs') def test_single_lang_file_added(self, codecs_mock): diff --git a/lib/l10n_utils/tests/test_template.py b/lib/l10n_utils/tests/test_template.py index 7c0fad5343..e70224e92f 100644 --- a/lib/l10n_utils/tests/test_template.py +++ b/lib/l10n_utils/tests/test_template.py @@ -10,6 +10,7 @@ from django.test.client import Client from jingo import env from jinja2 import FileSystemLoader +from jinja2.nodes import Block from mock import patch from nose.plugins.skip import SkipTest from nose.tools import eq_, ok_ @@ -22,6 +23,20 @@ ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files') TEMPLATE_DIRS = (os.path.join(ROOT, 'templates'),) +class TestL10nBlocks(TestCase): + def test_l10n_block_locales(self): + """ + Parsing an l10n block with locales info should put that info + on the node. + """ + tree = env.parse("""{% l10n dude locales=ru,es-ES,fr 20121212 %} + This stuff is totally translated. + {% endl10n %}""") + l10n_block = tree.find(Block) + self.assertEqual(l10n_block.locales, ['ru', 'es-ES', 'fr']) + self.assertEqual(l10n_block.version, 20121212) + + @patch.object(env, 'loader', FileSystemLoader(TEMPLATE_DIRS)) @patch.object(settings, 'ROOT_URLCONF', 'l10n_utils.tests.test_files.urls') @patch.object(settings, 'ROOT', ROOT)