Skip to content

Commit

Permalink
Merge pull request getpelican/pelican-plugins#1350 from chevah/i18n-d…
Browse files Browse the repository at this point in the history
…ict-merge

[i18n_subsites] Allow merging the base settings with the translations settings.
  • Loading branch information
rschiang committed Aug 9, 2022
2 parents 27cf01a + 4c12e7b commit 5c3ff28
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 37 deletions.
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
26 changes: 24 additions & 2 deletions i18n_subsites.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion test_data/localized_theme/templates/base.html
Original file line number Diff line number Diff line change
@@ -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() }}
<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/style.css" />
Expand Down
24 changes: 21 additions & 3 deletions test_data/pelicanconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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',
},
Expand Down
82 changes: 51 additions & 31 deletions test_i18n_subsites.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'''
Expand All @@ -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'''

Expand All @@ -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</a>',
root_index
)
self.assertIn(
'example.com/test/de/author/der-tester.html">Der Tester</a>',
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)

0 comments on commit 5c3ff28

Please sign in to comment.