diff --git a/README.rst b/README.rst index 340109b..e4d2857 100644 --- a/README.rst +++ b/README.rst @@ -163,3 +163,6 @@ Development - A demo and a test site is in the ``gh-pages`` branch and can be seen at http://smartass101.github.io/pelican-plugins/ +- A demo site used for automated end to end testing is defined in + i18n_subsites/test_data. +- Run the tests using `python -m unittest i18n_subsites/test_i18n_subsites.py` diff --git a/i18n_subsites.py b/i18n_subsites.py index 2a80abb..c974207 100644 --- a/i18n_subsites.py +++ b/i18n_subsites.py @@ -426,9 +426,8 @@ def create_next_subsite(pelican_obj): _MAIN_SETTINGS = None # to initialize next time else: with temporary_locale(): - settings = _MAIN_SETTINGS.copy() lang, overrides = _SUBSITE_QUEUE.popitem() - settings.update(overrides) + settings = _merge_dict(_MAIN_SETTINGS, overrides) settings = configure_settings(settings) # to set LOCALE, etc. cls = get_pelican_cls(settings) @@ -438,6 +437,29 @@ def create_next_subsite(pelican_obj): new_pelican_obj.run() +def _merge_dict(target, source): + """ + Update the values in `target` mapping based on the values from source. + + Any keys from `target` not found in `source` are kept. + """ + result = {} + for key, value in target.items(): + if key not in source: + # Keep the original value as it's not found. + result[key] = copy(value) + continue + + update = source[key] + + if isinstance(value, dict): + update = _merge_dict(value, update) + + result[key] = update + + return result + + # map: signal name -> function name _SIGNAL_HANDLERS_DB = { 'get_generators': initialize_plugin, diff --git a/test_data/localized_theme/templates/base.html b/test_data/localized_theme/templates/base.html index a24eb1d..f36504f 100644 --- a/test_data/localized_theme/templates/base.html +++ b/test_data/localized_theme/templates/base.html @@ -1,6 +1,10 @@ {% extends "!simple/base.html" %} -{% block title %}{% trans %}Welcome to our{% endtrans %} {{ SITENAME }}{% endblock %} +{% block title %} +{% trans %}Welcome to our{% endtrans %} {{ L10N.SITE_NAME }} | {{ L10N.COMPANY.NAME }} {{ L10N.COMPANY.INCORPORATION }} +{% endblock %} + + {% block head %} {{ super() }} diff --git a/test_data/pelicanconf.py b/test_data/pelicanconf.py index b644ef4..bc20e00 100644 --- a/test_data/pelicanconf.py +++ b/test_data/pelicanconf.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals AUTHOR = 'The Tester' -SITENAME = 'Testing site' SITEURL = 'http://example.com/test' # to make the test suite portable @@ -37,15 +36,34 @@ tmpsig = signal('tmpsig') I18N_FILTER_SIGNALS = [tmpsig] +# Having all the translatable values in a root dict help organize +# the translation and coordinate with the development and +# the translation teams. +L10N = { + 'SITE_NAME': 'Testing site', + 'COMPANY': { + # This is not translated to test deep merge. + 'NAME': 'Acme', + # This is translated. + 'INCORPORATION': 'Ltd' + }, +} + +# Translation for pelicanconf.py settings. I18N_SUBSITES = { 'de': { - 'SITENAME': 'Testseite', 'AUTHOR': 'Der Tester', + 'L10N': { + 'SITE_NAME': 'Testseite', + 'COMPANY': {'INCORPORATION': 'AG'} + }, 'LOCALE': 'de_DE.UTF-8', }, 'cz': { - 'SITENAME': 'Testovací stránka', 'AUTHOR': 'Test Testovič', + 'L10N': { + 'SITE_NAME': 'Testovací stránka', + }, 'I18N_UNTRANSLATED_PAGES': 'remove', 'I18N_UNTRANSLATED_ARTICLES': 'keep', }, diff --git a/test_i18n_subsites.py b/test_i18n_subsites.py index 83d0cb9..759992d 100644 --- a/test_i18n_subsites.py +++ b/test_i18n_subsites.py @@ -42,12 +42,12 @@ def test_get_pelican_cls_class(self): self.settings['PELICAN_CLASS'] = object cls = i18ns.get_pelican_cls(self.settings) self.assertIs(cls, object) - + def test_get_pelican_cls_str(self): '''Test that we get correct class given by string''' cls = i18ns.get_pelican_cls(self.settings) self.assertIs(cls, Pelican) - + class TestSitesRelpath(unittest.TestCase): '''Test relative path between sites generation''' @@ -72,7 +72,7 @@ def test_relpath_to_site(self): self.assertEqual(i18ns.relpath_to_site('en', 'de'), 'de') self.assertEqual(i18ns.relpath_to_site('de', 'en'), '..') - + class TestRegistration(unittest.TestCase): '''Test plugin registration''' @@ -91,49 +91,69 @@ def test_registration(self): self.assertIn(id(handler), sig.receivers) # clean up sig.disconnect(handler) - + class TestFullRun(unittest.TestCase): '''Test running Pelican with the Plugin''' - def setUp(self): - '''Create temporary output and cache folders''' - self.temp_path = mkdtemp(prefix='pelicantests.') - self.temp_cache = mkdtemp(prefix='pelican_cache.') - - def tearDown(self): - '''Remove output and cache folders''' - rmtree(self.temp_path) - rmtree(self.temp_cache) + def cleanDirs(self, dirs): + """ + Recursive delete each path from `dirs`. + """ + for path in dirs: + rmtree(path) + + def getContent(self, base, target): + """ + Return the text content of file. + """ + path = os.path.join(base, target) + with open(path, 'r') as stream: + return stream.read() def test_sites_generation(self): - '''Test generation of sites with the plugin - - Compare with recorded output via ``git diff``. - To generate output for comparison run the command - ``pelican -o test_data/output -s test_data/pelicanconf.py \ - test_data/content`` - Remember to remove the output/ folder before that. - ''' + """ + It will generate multiple copies of the site. + + Once copy is the default language, and the others are copies for each + enabled language in I18N_SUBSITES + from i18n_subsites/test_data/pelicanconf.py + """ + output_path = mkdtemp(prefix='pelicantests.') + cache_path = mkdtemp(prefix='pelican_cache.') + self.addCleanup(self.cleanDirs, [output_path, cache_path]) + base_path = os.path.dirname(os.path.abspath(__file__)) base_path = os.path.join(base_path, 'test_data') content_path = os.path.join(base_path, 'content') - output_path = os.path.join(base_path, 'output') settings_path = os.path.join(base_path, 'pelicanconf.py') settings = read_settings(path=settings_path, override={ 'PATH': content_path, - 'OUTPUT_PATH': self.temp_path, - 'CACHE_PATH': self.temp_cache, + 'OUTPUT_PATH': output_path, + 'CACHE_PATH': cache_path, 'PLUGINS': [i18ns], } ) pelican = Pelican(settings) pelican.run() - # compare output - out, err = subprocess.Popen( - ['git', 'diff', '--no-ext-diff', '--exit-code', '-w', output_path, - self.temp_path], env={'PAGER': ''}, - stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() - self.assertFalse(out, 'non-empty `diff` stdout:\n{}'.format(out)) - self.assertFalse(err, 'non-empty `diff` stderr:\n{}'.format(err)) + root_deploy = os.listdir(output_path) + self.assertIn('de', root_deploy) + self.assertIn('cz', root_deploy) + + root_index = self.getContent(output_path, 'index.html') + de_index = self.getContent(output_path, 'de/index.html') + + # Check pelicanconf,py translation. + self.assertIn( + 'example.com/test/author/the-tester.html">The Tester', + root_index + ) + self.assertIn( + 'example.com/test/de/author/der-tester.html">Der Tester', + de_index + ) + + # Check jinja2 translation. + self.assertIn('Welcome to our Testing site | Acme Ltd', root_index) + self.assertIn('Willkommen Sie zur unserer Testseite | Acme AG', de_index)