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)