aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@chimrod.com>2020-11-30 22:56:26 +0100
committerSébastien Dailly <sebastien@chimrod.com>2020-12-03 21:35:35 +0100
commit9b77ec15e5beeff3f57f845be883416d2a68b84d (patch)
tree796f2aecfcdf5012ce611fac22b85fa481bf63de /plugins
parent1c02ae819eee2d28040804d58872ceb4c003ee1f (diff)
New article on rst & Latex. Changed theme
Diffstat (limited to 'plugins')
-rw-r--r--plugins/i18n_subsites/README.rst165
-rw-r--r--plugins/i18n_subsites/__init__.py1
-rw-r--r--plugins/i18n_subsites/i18n_subsites.py462
-rw-r--r--plugins/i18n_subsites/implementing_language_buttons.rst128
-rw-r--r--plugins/i18n_subsites/localizing_using_jinja2.rst202
-rw-r--r--plugins/i18n_subsites/test_data/content/images/img.png0
-rw-r--r--plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst7
-rw-r--r--plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst7
-rw-r--r--plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst7
-rw-r--r--plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst5
-rw-r--r--plugins/i18n_subsites/test_data/content/translated_article-cz.rst8
-rw-r--r--plugins/i18n_subsites/test_data/content/translated_article-de.rst8
-rw-r--r--plugins/i18n_subsites/test_data/content/translated_article-en.rst8
-rw-r--r--plugins/i18n_subsites/test_data/content/untranslated_article-en.rst9
-rw-r--r--plugins/i18n_subsites/test_data/localized_theme/babel.cfg2
-rw-r--r--plugins/i18n_subsites/test_data/localized_theme/messages.pot23
-rw-r--r--plugins/i18n_subsites/test_data/localized_theme/static/style.css0
-rw-r--r--plugins/i18n_subsites/test_data/localized_theme/templates/base.html7
-rw-r--r--plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mobin0 -> 486 bytes
-rw-r--r--plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po23
-rw-r--r--plugins/i18n_subsites/test_data/output/an-untranslated-article.html56
-rw-r--r--plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html55
-rw-r--r--plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml10
-rw-r--r--plugins/i18n_subsites/test_data/output/cz/index.html56
-rw-r--r--plugins/i18n_subsites/test_data/output/cz/pages/404.html40
-rw-r--r--plugins/i18n_subsites/test_data/output/cz/translated-article.html61
-rw-r--r--plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html55
-rw-r--r--plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml8
-rw-r--r--plugins/i18n_subsites/test_data/output/de/index.html44
-rw-r--r--plugins/i18n_subsites/test_data/output/de/pages/404.html40
-rw-r--r--plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html34
-rw-r--r--plugins/i18n_subsites/test_data/output/de/translated-article.html61
-rw-r--r--plugins/i18n_subsites/test_data/output/feeds_all.atom.xml10
-rw-r--r--plugins/i18n_subsites/test_data/output/images/img.png0
-rw-r--r--plugins/i18n_subsites/test_data/output/index.html57
-rw-r--r--plugins/i18n_subsites/test_data/output/pages/404.html41
-rw-r--r--plugins/i18n_subsites/test_data/output/pages/untranslated-page.html35
-rw-r--r--plugins/i18n_subsites/test_data/output/theme/style.css0
-rw-r--r--plugins/i18n_subsites/test_data/output/translated-article.html62
-rw-r--r--plugins/i18n_subsites/test_data/pelicanconf.py53
-rw-r--r--plugins/i18n_subsites/test_i18n_subsites.py139
-rw-r--r--plugins/related_posts/Readme.rst38
-rw-r--r--plugins/related_posts/__init__.py2
-rw-r--r--[-rwxr-xr-x]plugins/related_posts/related_posts.py11
44 files changed, 2033 insertions, 7 deletions
diff --git a/plugins/i18n_subsites/README.rst b/plugins/i18n_subsites/README.rst
new file mode 100644
index 0000000..340109b
--- /dev/null
+++ b/plugins/i18n_subsites/README.rst
@@ -0,0 +1,165 @@
+=======================
+ I18N Sub-sites Plugin
+=======================
+
+This plugin extends the translations functionality by creating
+internationalized sub-sites for the default site.
+
+This plugin is designed for Pelican 3.4 and later.
+
+What it does
+============
+
+1. When the content of the main site is being generated, the settings
+ are saved and the generation stops when content is ready to be
+ written. While reading source files and generating content objects,
+ the output queue is modified in certain ways:
+
+ - translations that will appear as native in a different (sub-)site
+ will be removed
+ - untranslated articles will be transformed to drafts if
+ ``I18N_UNTRANSLATED_ARTICLES`` is ``'hide'`` (default), removed if
+ ``'remove'`` or kept as they are if ``'keep'``.
+ - untranslated pages will be transformed into hidden pages if
+ ``I18N_UNTRANSLATED_PAGES`` is ``'hide'`` (default), removed if
+ ``'remove'`` or kept as they are if ``'keep'``.''
+ - additional content manipulation similar to articles and pages can
+ be specified for custom generators in the ``I18N_GENERATOR_INFO``
+ setting.
+
+2. For each language specified in the ``I18N_SUBSITES`` dictionary the
+ settings overrides are applied to the settings from the main site
+ and a new sub-site is generated in the same way as with the main
+ site until content is ready to be written.
+3. When all (sub-)sites are waiting for content writing, all removed
+ contents, translations and static files are interlinked across the
+ (sub-)sites.
+4. Finally, all the output is written.
+
+Setting it up
+=============
+
+For each extra used language code, a language-specific settings overrides
+dictionary must be given (but can be empty) in the ``I18N_SUBSITES`` dictionary
+
+.. code-block:: python
+
+ PLUGINS = ['i18n_subsites', ...]
+
+ # mapping: language_code -> settings_overrides_dict
+ I18N_SUBSITES = {
+ 'cz': {
+ 'SITENAME': 'Hezkej blog',
+ }
+ }
+
+You must also have the following in your pelican configuration
+
+.. code-block:: python
+ JINJA_ENVIRONMENT = {
+ 'extensions': ['jinja2.ext.i18n'],
+ }
+
+
+
+Default and special overrides
+-----------------------------
+The settings overrides may contain arbitrary settings, however, there
+are some that are handled in a special way:
+
+``SITEURL``
+ Any overrides to this setting should ensure that there is some level
+ of hierarchy between all (sub-)sites, because Pelican makes all URLs
+ relative to ``SITEURL`` and the plugin can only cross-link between
+ the sites using this hierarchy. For instance, with the main site
+ ``http://example.com`` a sub-site ``http://example.com/de`` will
+ work, but ``http://de.example.com`` will not. If not overridden, the
+ language code (the language identifier used in the ``lang``
+ metadata) is appended to the main ``SITEURL`` for each sub-site.
+``OUTPUT_PATH``, ``CACHE_PATH``
+ If not overridden, the language code is appended as with ``SITEURL``.
+ Separate cache paths are required as parser results depend on the locale.
+``STATIC_PATHS``, ``THEME_STATIC_PATHS``
+ If not overridden, they are set to ``[]`` and all links to static
+ files are cross-linked to the main site.
+``THEME``, ``THEME_STATIC_DIR``
+ If overridden, the logic with ``THEME_STATIC_PATHS`` does not apply.
+``DEFAULT_LANG``
+ This should not be overridden as the plugin changes it to the
+ language code of each sub-site to change what is perceived as translations.
+
+Localizing templates
+--------------------
+
+Most importantly, this plugin can use localized templates for each
+sub-site. There are two approaches to having the templates localized:
+
+- You can set a different ``THEME`` override for each language in
+ ``I18N_SUBSITES``, e.g. by making a copy of a theme ``my_theme`` to
+ ``my_theme_lang`` and then editing the templates in the new
+ localized theme. This approach means you don't have to deal with
+ gettext ``*.po`` files, but it is harder to maintain over time.
+- You use only one theme and localize the templates using the
+ `jinja2.ext.i18n Jinja2 extension
+ <http://jinja.pocoo.org/docs/templates/#i18n>`_. For a kickstart
+ read this `guide <./localizing_using_jinja2.rst>`_.
+
+Additional context variables
+............................
+
+It may be convenient to add language buttons to your theme in addition
+to the translation links of articles and pages. These buttons could,
+for example, point to the ``SITEURL`` of each (sub-)site. For this
+reason the plugin adds these variables to the template context:
+
+``main_lang``
+ The language of the main site — the original ``DEFAULT_LANG``
+``main_siteurl``
+ The ``SITEURL`` of the main site — the original ``SITEURL``
+``lang_siteurls``
+ An ordered dictionary, mapping all used languages to their
+ ``SITEURL``. The ``main_lang`` is the first key with ``main_siteurl``
+ as the value. This dictionary is useful for implementing global
+ language buttons that show the language of the currently viewed
+ (sub-)site too.
+``extra_siteurls``
+ An ordered dictionary, subset of ``lang_siteurls``, the current
+ ``DEFAULT_LANG`` of the rendered (sub-)site is not included, so for
+ each (sub-)site ``set(extra_siteurls) == set(lang_siteurls) -
+ set([DEFAULT_LANG])``. This dictionary is useful for implementing
+ global language buttons that do not show the current language.
+``relpath_to_site``
+ A function that returns a relative path from the first (sub-)site to
+ the second (sub-)site where the (sub-)sites are identified by the
+ language codes given as two arguments.
+
+If you don't like the default ordering of the ordered dictionaries,
+use a Jinja2 filter to alter the ordering.
+
+All the siteurls above are always absolute even in the case of
+``RELATIVE_URLS == True`` (it would be to complicated to replicate the
+Pelican internals for local siteurls), so you may rather use something
+like ``{{ SITEURL }}/{{ relpath_to_site(DEFAULT_LANG, main_lang }}``
+to link to the main site.
+
+This short `howto <./implementing_language_buttons.rst>`_ shows two
+example implementations of language buttons.
+
+Usage notes
+===========
+- It is **mandatory** to specify ``lang`` metadata for each article
+ and page as ``DEFAULT_LANG`` is later changed for each sub-site, so
+ content without ``lang`` metadata would be rendered in every
+ (sub-)site.
+- As with the original translations functionality, ``slug`` metadata
+ is used to group translations. It is therefore often convenient to
+ compensate for this by overriding the content URL (which defaults to
+ slug) using the ``url`` and ``save_as`` metadata. You could also
+ give articles e.g. ``name`` metadata and use it in ``ARTICLE_URL =
+ '{name}.html'``.
+
+Development
+===========
+
+- A demo and a test site is in the ``gh-pages`` branch and can be seen
+ at http://smartass101.github.io/pelican-plugins/
diff --git a/plugins/i18n_subsites/__init__.py b/plugins/i18n_subsites/__init__.py
new file mode 100644
index 0000000..7dfbde0
--- /dev/null
+++ b/plugins/i18n_subsites/__init__.py
@@ -0,0 +1 @@
+from .i18n_subsites import *
diff --git a/plugins/i18n_subsites/i18n_subsites.py b/plugins/i18n_subsites/i18n_subsites.py
new file mode 100644
index 0000000..dc27799
--- /dev/null
+++ b/plugins/i18n_subsites/i18n_subsites.py
@@ -0,0 +1,462 @@
+"""i18n_subsites plugin creates i18n-ized subsites of the default site
+
+This plugin is designed for Pelican 3.4 and later
+"""
+
+
+import os
+import six
+import logging
+import posixpath
+
+from copy import copy
+from itertools import chain
+from operator import attrgetter
+try:
+ from collections.abc import OrderedDict
+except ImportError:
+ from collections import OrderedDict
+from contextlib import contextmanager
+from six.moves.urllib.parse import urlparse
+
+import gettext
+import locale
+
+from pelican import signals
+from pelican.generators import ArticlesGenerator, PagesGenerator
+from pelican.settings import configure_settings
+try:
+ from pelican.contents import Draft
+except ImportError:
+ from pelican.contents import Article as Draft
+
+
+# Global vars
+_MAIN_SETTINGS = None # settings dict of the main Pelican instance
+_MAIN_LANG = None # lang of the main Pelican instance
+_MAIN_SITEURL = None # siteurl of the main Pelican instance
+_MAIN_STATIC_FILES = None # list of Static instances the main Pelican instance
+_SUBSITE_QUEUE = {} # map: lang -> settings overrides
+_SITE_DB = OrderedDict() # OrderedDict: lang -> siteurl
+_SITES_RELPATH_DB = {} # map: (lang, base_lang) -> relpath
+# map: generator -> list of removed contents that need interlinking
+_GENERATOR_DB = {}
+_NATIVE_CONTENT_URL_DB = {} # map: source_path -> content in its native lang
+_LOGGER = logging.getLogger(__name__)
+
+
+@contextmanager
+def temporary_locale(temp_locale=None):
+ '''Enable code to run in a context with a temporary locale
+
+ Resets the locale back when exiting context.
+ Can set a temporary locale if provided
+ '''
+ orig_locale = locale.setlocale(locale.LC_ALL)
+ if temp_locale is not None:
+ locale.setlocale(locale.LC_ALL, temp_locale)
+ yield
+ locale.setlocale(locale.LC_ALL, orig_locale)
+
+
+def initialize_dbs(settings):
+ '''Initialize internal DBs using the Pelican settings dict
+
+ This clears the DBs for e.g. autoreload mode to work
+ '''
+ global _MAIN_SETTINGS, _MAIN_SITEURL, _MAIN_LANG, _SUBSITE_QUEUE
+ _MAIN_SETTINGS = settings
+ _MAIN_LANG = settings['DEFAULT_LANG']
+ _MAIN_SITEURL = settings['SITEURL']
+ _SUBSITE_QUEUE = settings.get('I18N_SUBSITES', {}).copy()
+ prepare_site_db_and_overrides()
+ # clear databases in case of autoreload mode
+ _SITES_RELPATH_DB.clear()
+ _NATIVE_CONTENT_URL_DB.clear()
+ _GENERATOR_DB.clear()
+
+
+def prepare_site_db_and_overrides():
+ '''Prepare overrides and create _SITE_DB
+
+ _SITE_DB.keys() need to be ready for filter_translations
+ '''
+ _SITE_DB.clear()
+ _SITE_DB[_MAIN_LANG] = _MAIN_SITEURL
+ # make sure it works for both root-relative and absolute
+ main_siteurl = '/' if _MAIN_SITEURL == '' else _MAIN_SITEURL
+ for lang, overrides in _SUBSITE_QUEUE.items():
+ if 'SITEURL' not in overrides:
+ overrides['SITEURL'] = posixpath.join(main_siteurl, lang)
+ _SITE_DB[lang] = overrides['SITEURL']
+ # default subsite hierarchy
+ if 'OUTPUT_PATH' not in overrides:
+ overrides['OUTPUT_PATH'] = os.path.join(
+ _MAIN_SETTINGS['OUTPUT_PATH'], lang)
+ if 'CACHE_PATH' not in overrides:
+ overrides['CACHE_PATH'] = os.path.join(
+ _MAIN_SETTINGS['CACHE_PATH'], lang)
+ if 'STATIC_PATHS' not in overrides:
+ overrides['STATIC_PATHS'] = []
+ if ('THEME' not in overrides and 'THEME_STATIC_DIR' not in overrides and
+ 'THEME_STATIC_PATHS' not in overrides):
+ relpath = relpath_to_site(lang, _MAIN_LANG)
+ overrides['THEME_STATIC_DIR'] = posixpath.join(
+ relpath, _MAIN_SETTINGS['THEME_STATIC_DIR'])
+ overrides['THEME_STATIC_PATHS'] = []
+ # to change what is perceived as translations
+ overrides['DEFAULT_LANG'] = lang
+
+
+def subscribe_filter_to_signals(settings):
+ '''Subscribe content filter to requested signals'''
+ for sig in settings.get('I18N_FILTER_SIGNALS', []):
+ sig.connect(filter_contents_translations)
+
+
+def initialize_plugin(pelican_obj):
+ '''Initialize plugin variables and Pelican settings'''
+ if _MAIN_SETTINGS is None:
+ initialize_dbs(pelican_obj.settings)
+ subscribe_filter_to_signals(pelican_obj.settings)
+
+
+def get_site_path(url):
+ '''Get the path component of an url, excludes siteurl
+
+ also normalizes '' to '/' for relpath to work,
+ otherwise it could be interpreted as a relative filesystem path
+ '''
+ path = urlparse(url).path
+ if path == '':
+ path = '/'
+ return path
+
+
+def relpath_to_site(lang, target_lang):
+ '''Get relative path from siteurl of lang to siteurl of base_lang
+
+ the output is cached in _SITES_RELPATH_DB
+ '''
+ path = _SITES_RELPATH_DB.get((lang, target_lang), None)
+ if path is None:
+ siteurl = _SITE_DB.get(lang, _MAIN_SITEURL)
+ target_siteurl = _SITE_DB.get(target_lang, _MAIN_SITEURL)
+ path = posixpath.relpath(get_site_path(target_siteurl),
+ get_site_path(siteurl))
+ _SITES_RELPATH_DB[(lang, target_lang)] = path
+ return path
+
+
+def save_generator(generator):
+ '''Save the generator for later use
+
+ initialize the removed content list
+ '''
+ _GENERATOR_DB[generator] = []
+
+
+def article2draft(article):
+ '''Transform an Article to Draft'''
+ draft = Draft(article._content, article.metadata, article.settings,
+ article.source_path, article._context)
+ draft.status = 'draft'
+ return draft
+
+
+def page2hidden_page(page):
+ '''Transform a Page to a hidden Page'''
+ page.status = 'hidden'
+ return page
+
+
+class GeneratorInspector(object):
+ '''Inspector of generator instances'''
+
+ generators_info = {
+ ArticlesGenerator: {
+ 'translations_lists': ['translations', 'drafts_translations'],
+ 'contents_lists': [('articles', 'drafts')],
+ 'hiding_func': article2draft,
+ 'policy': 'I18N_UNTRANSLATED_ARTICLES',
+ },
+ PagesGenerator: {
+ 'translations_lists': ['translations', 'hidden_translations'],
+ 'contents_lists': [('pages', 'hidden_pages')],
+ 'hiding_func': page2hidden_page,
+ 'policy': 'I18N_UNTRANSLATED_PAGES',
+ },
+ }
+
+ def __init__(self, generator):
+ '''Identify the best known class of the generator instance
+
+ The class '''
+ self.generator = generator
+ self.generators_info.update(generator.settings.get(
+ 'I18N_GENERATORS_INFO', {}))
+ for cls in generator.__class__.__mro__:
+ if cls in self.generators_info:
+ self.info = self.generators_info[cls]
+ break
+ else:
+ self.info = {}
+
+ def translations_lists(self):
+ '''Iterator over lists of content translations'''
+ return (getattr(self.generator, name) for name in
+ self.info.get('translations_lists', []))
+
+ def contents_list_pairs(self):
+ '''Iterator over pairs of normal and hidden contents'''
+ return (tuple(getattr(self.generator, name) for name in names)
+ for names in self.info.get('contents_lists', []))
+
+ def hiding_function(self):
+ '''Function for transforming content to a hidden version'''
+ hiding_func = self.info.get('hiding_func', lambda x: x)
+ return hiding_func
+
+ def untranslated_policy(self, default):
+ '''Get the policy for untranslated content'''
+ return self.generator.settings.get(self.info.get('policy', None),
+ default)
+
+ def all_contents(self):
+ '''Iterator over all contents'''
+ translations_iterator = chain(*self.translations_lists())
+ return chain(translations_iterator,
+ *(pair[i] for pair in self.contents_list_pairs()
+ for i in (0, 1)))
+
+
+def filter_contents_translations(generator):
+ '''Filter the content and translations lists of a generator
+
+ Filters out
+ 1) translations which will be generated in a different site
+ 2) content that is not in the language of the currently
+ generated site but in that of a different site, content in a
+ language which has no site is generated always. The filtering
+ method bay be modified by the respective untranslated policy
+ '''
+ inspector = GeneratorInspector(generator)
+ current_lang = generator.settings['DEFAULT_LANG']
+ langs_with_sites = _SITE_DB.keys()
+ removed_contents = _GENERATOR_DB[generator]
+
+ for translations in inspector.translations_lists():
+ for translation in translations[:]: # copy to be able to remove
+ if translation.lang in langs_with_sites:
+ translations.remove(translation)
+ removed_contents.append(translation)
+
+ hiding_func = inspector.hiding_function()
+ untrans_policy = inspector.untranslated_policy(default='hide')
+ for (contents, other_contents) in inspector.contents_list_pairs():
+ for content in other_contents: # save any hidden native content first
+ if content.lang == current_lang: # in native lang
+ # save the native URL attr formatted in the current locale
+ _NATIVE_CONTENT_URL_DB[content.source_path] = content.url
+ for content in contents[:]: # copy for removing in loop
+ if content.lang == current_lang: # in native lang
+ # save the native URL attr formatted in the current locale
+ _NATIVE_CONTENT_URL_DB[content.source_path] = content.url
+ elif content.lang in langs_with_sites and untrans_policy != 'keep':
+ contents.remove(content)
+ if untrans_policy == 'hide':
+ other_contents.append(hiding_func(content))
+ elif untrans_policy == 'remove':
+ removed_contents.append(content)
+
+
+def install_templates_translations(generator):
+ '''Install gettext translations in the jinja2.Environment
+
+ Only if the 'jinja2.ext.i18n' jinja2 extension is enabled
+ the translations for the current DEFAULT_LANG are installed.
+ '''
+ if 'JINJA_ENVIRONMENT' in generator.settings: # pelican 3.7+
+ jinja_extensions = generator.settings['JINJA_ENVIRONMENT'].get(
+ 'extensions', [])
+ else:
+ jinja_extensions = generator.settings['JINJA_EXTENSIONS']
+
+ if 'jinja2.ext.i18n' in jinja_extensions:
+ domain = generator.settings.get('I18N_GETTEXT_DOMAIN', 'messages')
+ localedir = generator.settings.get('I18N_GETTEXT_LOCALEDIR')
+ if localedir is None:
+ localedir = os.path.join(generator.theme, 'translations')
+ current_lang = generator.settings['DEFAULT_LANG']
+ if current_lang == generator.settings.get('I18N_TEMPLATES_LANG',
+ _MAIN_LANG):
+ translations = gettext.NullTranslations()
+ else:
+ langs = [current_lang]
+ try:
+ translations = gettext.translation(domain, localedir, langs)
+ except (IOError, OSError):
+ _LOGGER.error((
+ "Cannot find translations for language '{}' in '{}' with "
+ "domain '{}'. Installing NullTranslations.").format(
+ langs[0], localedir, domain))
+ translations = gettext.NullTranslations()
+ newstyle = generator.settings.get('I18N_GETTEXT_NEWSTYLE', True)
+ generator.env.install_gettext_translations(translations, newstyle)
+
+
+def add_variables_to_context(generator):
+ '''Adds useful iterable variables to template context'''
+ context = generator.context # minimize attr lookup
+ context['relpath_to_site'] = relpath_to_site
+ context['main_siteurl'] = _MAIN_SITEURL
+ context['main_lang'] = _MAIN_LANG
+ context['lang_siteurls'] = _SITE_DB
+ current_lang = generator.settings['DEFAULT_LANG']
+ extra_siteurls = _SITE_DB.copy()
+ extra_siteurls.pop(current_lang)
+ context['extra_siteurls'] = extra_siteurls
+
+
+def interlink_translations(content):
+ '''Link content to translations in their main language
+
+ so the URL (including localized month names) of the different subsites
+ will be honored
+ '''
+ lang = content.lang
+ # sort translations by lang
+ content.translations.sort(key=attrgetter('lang'))
+ for translation in content.translations:
+ relpath = relpath_to_site(lang, translation.lang)
+ url = _NATIVE_CONTENT_URL_DB[translation.source_path]
+ translation.override_url = posixpath.join(relpath, url)
+
+
+def interlink_translated_content(generator):
+ '''Make translations link to the native locations
+
+ for generators that may contain translated content
+ '''
+ inspector = GeneratorInspector(generator)
+ for content in inspector.all_contents():
+ interlink_translations(content)
+
+
+def interlink_removed_content(generator):
+ '''For all contents removed from generation queue update interlinks
+
+ link to the native location
+ '''
+ current_lang = generator.settings['DEFAULT_LANG']
+ for content in _GENERATOR_DB[generator]:
+ url = _NATIVE_CONTENT_URL_DB[content.source_path]
+ relpath = relpath_to_site(current_lang, content.lang)
+ content.override_url = posixpath.join(relpath, url)
+
+
+def interlink_static_files(generator):
+ '''Add links to static files in the main site if necessary'''
+ if generator.settings['STATIC_PATHS'] != []:
+ return # customized STATIC_PATHS
+ try: # minimize attr lookup
+ static_content = generator.context['static_content']
+ except KeyError:
+ static_content = generator.context['filenames']
+ relpath = relpath_to_site(generator.settings['DEFAULT_LANG'], _MAIN_LANG)
+ for staticfile in _MAIN_STATIC_FILES:
+ if staticfile.get_relative_source_path() not in static_content:
+ staticfile = copy(staticfile) # prevent override in main site
+ staticfile.override_url = posixpath.join(relpath, staticfile.url)
+ try:
+ generator.add_source_path(staticfile, static=True)
+ except TypeError:
+ generator.add_source_path(staticfile)
+
+
+def save_main_static_files(static_generator):
+ '''Save the static files generated for the main site'''
+ global _MAIN_STATIC_FILES
+ # test just for current lang as settings change in autoreload mode
+ if static_generator.settings['DEFAULT_LANG'] == _MAIN_LANG:
+ _MAIN_STATIC_FILES = static_generator.staticfiles
+
+
+def update_generators():
+ '''Update the context of all generators
+
+ Ads useful variables and translations into the template context
+ and interlink translations
+ '''
+ for generator in _GENERATOR_DB.keys():
+ install_templates_translations(generator)
+ add_variables_to_context(generator)
+ interlink_static_files(generator)
+ interlink_removed_content(generator)
+ interlink_translated_content(generator)
+
+
+def get_pelican_cls(settings):
+ '''Get the Pelican class requested in settings'''
+ cls = settings['PELICAN_CLASS']
+ if isinstance(cls, six.string_types):
+ module, cls_name = cls.rsplit('.', 1)
+ module = __import__(module)
+ cls = getattr(module, cls_name)
+ return cls
+
+
+def create_next_subsite(pelican_obj):
+ '''Create the next subsite using the lang-specific config
+
+ If there are no more subsites in the generation queue, update all
+ the generators (interlink translations and removed content, add
+ variables and translations to template context). Otherwise get the
+ language and overrides for next the subsite in the queue and apply
+ overrides. Then generate the subsite using a PELICAN_CLASS
+ instance and its run method. Finally, restore the previous locale.
+ '''
+ global _MAIN_SETTINGS
+ if len(_SUBSITE_QUEUE) == 0:
+ _LOGGER.debug(
+ 'i18n: Updating cross-site links and context of all generators.')
+ update_generators()
+ _MAIN_SETTINGS = None # to initialize next time
+ else:
+ with temporary_locale():
+ settings = _MAIN_SETTINGS.copy()
+ lang, overrides = _SUBSITE_QUEUE.popitem()
+ settings.update(overrides)
+ settings = configure_settings(settings) # to set LOCALE, etc.
+ cls = get_pelican_cls(settings)
+
+ new_pelican_obj = cls(settings)
+ _LOGGER.debug(("Generating i18n subsite for language '{}' "
+ "using class {}").format(lang, cls))
+ new_pelican_obj.run()
+
+
+# map: signal name -> function name
+_SIGNAL_HANDLERS_DB = {
+ 'get_generators': initialize_plugin,
+ 'article_generator_pretaxonomy': filter_contents_translations,
+ 'page_generator_finalized': filter_contents_translations,
+ 'get_writer': create_next_subsite,
+ 'static_generator_finalized': save_main_static_files,
+ 'generator_init': save_generator,
+}
+
+
+def register():
+ '''Register the plugin only if required signals are available'''
+ for sig_name in _SIGNAL_HANDLERS_DB.keys():
+ if not hasattr(signals, sig_name):
+ _LOGGER.error((
+ 'The i18n_subsites plugin requires the {} '
+ 'signal available for sure in Pelican 3.4.0 and later, '
+ 'plugin will not be used.').format(sig_name))
+ return
+
+ for sig_name, handler in _SIGNAL_HANDLERS_DB.items():
+ sig = getattr(signals, sig_name)
+ sig.connect(handler)
diff --git a/plugins/i18n_subsites/implementing_language_buttons.rst b/plugins/i18n_subsites/implementing_language_buttons.rst
new file mode 100644
index 0000000..55b7bf3
--- /dev/null
+++ b/plugins/i18n_subsites/implementing_language_buttons.rst
@@ -0,0 +1,128 @@
+-----------------------------
+Implementing language buttons
+-----------------------------
+
+Each article with translations has translations links, but that's the
+only way to switch between language subsites.
+
+For this reason it is convenient to add language buttons to the top
+menu bar to make it simple to switch between the language subsites on
+all pages.
+
+Example designs
+---------------
+
+Language buttons showing other available languages
+..................................................
+
+The ``extra_siteurls`` dictionary is a mapping of all other (not the
+``DEFAULT_LANG`` of the current (sub-)site) languages to the
+``SITEURL`` of the respective (sub-)sites
+
+.. code-block:: jinja
+
+ <!-- SNIP -->
+ <nav><ul>
+ {% if extra_siteurls %}
+ {% for lang, url in extra_siteurls.items() %}
+ <li><a href="{{ url }}/">{{ lang }}</a></li>
+ {% endfor %}
+ <!-- separator -->
+ <li style="background-color: white; padding: 5px;">&nbsp</li>
+ {% endif %}
+ {% for title, link in MENUITEMS %}
+ <!-- SNIP -->
+
+Notice the slash ``/`` after ``{{ url }}``, this makes sure it works
+with local development when ``SITEURL == ''``.
+
+Language buttons showing all available languages, current is active
+...................................................................
+
+The ``lang_subsites`` dictionary is a mapping of all languages to the
+``SITEURL`` of the respective (sub-)sites. This template sets the
+language of the current (sub-)site as active.
+
+.. code-block:: jinja
+
+ <!-- SNIP -->
+ <nav><ul>
+ {% if lang_siteurls %}
+ {% for lang, url in lang_siteurls.items() %}
+ <li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ url }}/">{{ lang }}</a></li>
+ {% endfor %}
+ <!-- separator -->
+ <li style="background-color: white; padding: 5px;">&nbsp</li>
+ {% endif %}
+ {% for title, link in MENUITEMS %}
+ <!-- SNIP -->
+
+
+Tips and tricks
+---------------
+
+Showing more than language codes
+................................
+
+If you want the language buttons to show e.g. the names of the
+languages or flags [#flags]_, not just the language codes, you can use
+a jinja filter to translate the language codes
+
+
+.. code-block:: python
+
+ languages_lookup = {
+ 'en': 'English',
+ 'cz': 'Čeština',
+ }
+
+ def lookup_lang_name(lang_code):
+ return languages_lookup[lang_code]
+
+ JINJA_FILTERS = {
+ ...
+ 'lookup_lang_name': lookup_lang_name,
+ }
+
+And then the link content becomes
+
+.. code-block:: jinja
+
+ <!-- SNIP -->
+ {% for lang, siteurl in lang_siteurls.items() %}
+ <li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ siteurl }}/">{{ lang | lookup_lang_name }}</a></li>
+ {% endfor %}
+ <!-- SNIP -->
+
+
+Changing the order of language buttons
+......................................
+
+Because ``lang_siteurls`` and ``extra_siteurls`` are instances of
+``OrderedDict`` with ``main_lang`` being always the first key, you can
+change the order through a jinja filter.
+
+.. code-block:: python
+
+ def my_ordered_items(ordered_dict):
+ items = list(ordered_dict.items())
+ # swap first and last using tuple unpacking
+ items[0], items[-1] = items[-1], items[0]
+ return items
+
+ JINJA_FILTERS = {
+ ...
+ 'my_ordered_items': my_ordered_items,
+ }
+
+And then the ``for`` loop line in the template becomes
+
+.. code-block:: jinja
+
+ <!-- SNIP -->
+ {% for lang, siteurl in lang_siteurls | my_ordered_items %}
+ <!-- SNIP -->
+
+
+.. [#flags] Although it may look nice, `w3 discourages it
+ <http://www.w3.org/TR/i18n-html-tech-lang/#ri20040808.173208643>`_.
diff --git a/plugins/i18n_subsites/localizing_using_jinja2.rst b/plugins/i18n_subsites/localizing_using_jinja2.rst
new file mode 100644
index 0000000..a28bedd
--- /dev/null
+++ b/plugins/i18n_subsites/localizing_using_jinja2.rst
@@ -0,0 +1,202 @@
+-----------------------------
+Localizing themes with Jinja2
+-----------------------------
+
+1. Localize templates
+---------------------
+
+To enable the |ext| extension in your templates, you must add it to
+``JINJA_ENVIRONMENT`` in your Pelican configuration
+
+.. code-block:: python
+
+ JINJA_ENVIRONMENT = {
+ 'extensions': ['jinja2.ext.i18n', ...]
+ }
+
+Then follow the `Jinja2 templating documentation for the I18N plugin
+<http://jinja.pocoo.org/docs/templates/#i18n>`_ to make your templates
+localizable. This usually means surrounding strings with the ``{%
+trans %}`` directive or using ``gettext()`` in expressions
+
+.. code-block:: jinja
+
+ {% trans %}translatable content{% endtrans %}
+ {{ gettext('a translatable string') }}
+
+For pluralization support, etc. consult the documentation.
+
+To enable `newstyle gettext calls
+<http://jinja.pocoo.org/docs/extensions/#newstyle-gettext>`_ the
+``I18N_GETTEXT_NEWSTYLE`` config variable must be set to ``True``
+(default).
+
+.. |ext| replace:: ``jinja2.ext.i18n``
+
+2. Specify translations location
+--------------------------------
+
+The |ext| extension uses the `Python gettext library
+<http://docs.python.org/library/gettext.html>`_ for translating
+strings.
+
+In your Pelican config you can give the path in which to look for
+translations in the ``I18N_GETTEXT_LOCALEDIR`` variable. If not given,
+it is assumed to be the ``translations`` subfolder in the top folder
+of the theme specified by ``THEME``.
+
+The domain of the translations (the name of each translation file is
+``domain.mo``) is controlled by the ``I18N_GETTEXT_DOMAIN`` config
+variable (defaults to ``messages``).
+
+Example
+.......
+
+With the following in your Pelican settings file
+
+.. code-block:: python
+
+ I18N_GETTEXT_LOCALEDIR = 'some/path/'
+ I18N_GETTEXT_DOMAIN = 'my_domain'
+
+the translation for language 'cz' will be expected to be in
+``some/path/cz/LC_MESSAGES/my_domain.mo``
+
+
+3. Extract translatable strings and translate them
+--------------------------------------------------
+
+There are many ways to extract translatable strings and create
+``gettext`` compatible translations. You can create the ``*.po`` and
+``*.mo`` message catalog files yourself, or you can use some helper
+tool as described in `the Python gettext library tutorial
+<http://docs.python.org/library/gettext.html#internationalizing-your-programs-and-modules>`_.
+
+You of course don't need to provide a translation for the language in
+which the templates are written which is assumed to be the original
+``DEFAULT_LANG``. This can be overridden in the
+``I18N_TEMPLATES_LANG`` variable.
+
+Recommended tool: babel
+.......................
+
+`Babel <http://babel.pocoo.org/>`_ makes it easy to extract
+translatable strings from the localized Jinja2 templates and assists
+with creating translations as documented in this `Jinja2-Babel
+tutorial
+<http://pythonhosted.org/Flask-Babel/#translating-applications>`_
+[#flask]_ on which the following is based.
+
+1. Add babel mapping
+~~~~~~~~~~~~~~~~~~~~
+
+Let's assume that you are localizing a theme in ``themes/my_theme/``
+and that you use the default settings, i.e. the default domain
+``messages`` and will put the translations in the ``translations``
+subdirectory of the theme directory as
+``themes/my_theme/translations/``.
+
+It is up to you where to store babel mappings and translation files
+templates (``*.pot``), but a convenient place is to put them in
+``themes/my_theme/`` and work in that directory. From now on let's
+assume that it will be our current working directory (CWD).
+
+To tell babel to extract translatable strings from the templates
+create a mapping file ``babel.cfg`` with the following line
+
+.. code-block:: cfg
+
+ [jinja2: templates/**.html]
+
+
+2. Extract translatable strings from templates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Run the following command to create a ``messages.pot`` message catalog
+template file from extracted translatable strings
+
+.. code-block:: bash
+
+ pybabel extract --mapping babel.cfg --output messages.pot ./
+
+
+3. Initialize message catalogs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to translate the template to language ``lang``, run the
+following command to create a message catalog
+``translations/lang/LC_MESSAGES/messages.po`` using the template
+``messages.pot``
+
+.. code-block:: bash
+
+ pybabel init --input-file messages.pot --output-dir translations/ --locale lang --domain messages
+
+babel expects ``lang`` to be a valid locale identifier, so if e.g. you
+are translating for language ``cz`` but the corresponding locale is
+``cs``, you have to use the locale identifier. Nevertheless, the
+gettext infrastructure should later correctly find the locale for a
+given language.
+
+4. Fill the message catalogs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The message catalog files format is quite intuitive, it is fully
+documented in the `GNU gettext manual
+<http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files>`_. Essentially,
+you fill in the ``msgstr`` strings
+
+
+.. code-block:: po
+
+ msgid "just a simple string"
+ msgstr "jenom jednoduchý řetězec"
+
+ msgid ""
+ "some multiline string"
+ "looks like this"
+ msgstr ""
+ "nějaký více řádkový řetězec"
+ "vypadá takto"
+
+You might also want to remove ``#,fuzzy`` flags once the translation
+is complete and reviewed to show that it can be compiled.
+
+5. Compile the message catalogs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The message catalogs must be compiled into binary format using this
+command
+
+.. code-block:: bash
+
+ pybabel compile --directory translations/ --domain messages
+
+This command might complain about "fuzzy" translations, which means
+you should review the translations and once done, remove the fuzzy
+flag line.
+
+(6.) Update the catalogs when templates change
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you add any translatable patterns into your templates, you have to
+update your message catalogs too. First you extract a new message
+catalog template as described in the 2. step. Then you run the
+following command [#pybabel_error]_
+
+.. code-block:: bash
+
+ pybabel update --input-file messages.pot --output-dir translations/ --domain messages
+
+This will merge the new patterns with the old ones. Once you review
+and fill them, you have to recompile them as described in the 5. step.
+
+.. [#flask] Although the tutorial is focused on Flask-based web
+ applications, the linked translation tutorial is not
+ Flask-specific.
+.. [#pybabel_error] If you get an error ``TypeError: must be str, not
+ bytes`` with Python 3.3, it is likely you are
+ suffering from this `bug
+ <https://github.com/mitsuhiko/flask-babel/issues/43>`_.
+ Until the fix is released, you can use babel with
+ Python 2.7.
diff --git a/plugins/i18n_subsites/test_data/content/images/img.png b/plugins/i18n_subsites/test_data/content/images/img.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/images/img.png
diff --git a/plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst b/plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst
new file mode 100644
index 0000000..c282faa
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/pages/hidden-page-cz.rst
@@ -0,0 +1,7 @@
+404 stránka
+===========
+:slug: 404
+:lang: cz
+:status: hidden
+
+Jednoduchá 404 stránka.
diff --git a/plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst b/plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst
new file mode 100644
index 0000000..d8410a1
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/pages/hidden-page-de.rst
@@ -0,0 +1,7 @@
+Eine 404 Seite
+==============
+:slug: 404
+:lang: de
+:status: hidden
+
+Eine einfache 404 Seite.
diff --git a/plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst b/plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst
new file mode 100644
index 0000000..74a97d7
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/pages/hidden-page-en.rst
@@ -0,0 +1,7 @@
+A 404 page
+==========
+:slug: 404
+:lang: en
+:status: hidden
+
+A simple 404 page.
diff --git a/plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst b/plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst
new file mode 100644
index 0000000..ae4c2b8
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/pages/untranslated-page.rst
@@ -0,0 +1,5 @@
+Untranslated page
+=================
+:lang: en
+
+This page has no translation.
diff --git a/plugins/i18n_subsites/test_data/content/translated_article-cz.rst b/plugins/i18n_subsites/test_data/content/translated_article-cz.rst
new file mode 100644
index 0000000..555a69d
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/translated_article-cz.rst
@@ -0,0 +1,8 @@
+Přeložený článek
+================
+:slug: translated-article
+:lang: cz
+:date: 2014-09-15
+
+Jednoduchý článek s překlady.
+Zde je odkaz na `nějaký obrázek <{filename}/images/img.png>`_.
diff --git a/plugins/i18n_subsites/test_data/content/translated_article-de.rst b/plugins/i18n_subsites/test_data/content/translated_article-de.rst
new file mode 100644
index 0000000..01bf565
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/translated_article-de.rst
@@ -0,0 +1,8 @@
+Ein übersetzter Artikel
+=======================
+:slug: translated-article
+:lang: de
+:date: 2014-09-14
+
+Ein einfacher Artikel mit einer Übersetzung.
+Hier ist ein Link zur `einigem Bild <{filename}/images/img.png>`_.
diff --git a/plugins/i18n_subsites/test_data/content/translated_article-en.rst b/plugins/i18n_subsites/test_data/content/translated_article-en.rst
new file mode 100644
index 0000000..d7f5dad
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/translated_article-en.rst
@@ -0,0 +1,8 @@
+A translated article
+====================
+:slug: translated-article
+:lang: en
+:date: 2014-09-13
+
+A simple article with a translation.
+Here is a link to `some image <{filename}/images/img.png>`_.
diff --git a/plugins/i18n_subsites/test_data/content/untranslated_article-en.rst b/plugins/i18n_subsites/test_data/content/untranslated_article-en.rst
new file mode 100644
index 0000000..867ae5d
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/content/untranslated_article-en.rst
@@ -0,0 +1,9 @@
+An untranslated article
+=======================
+:date: 2014-07-14
+:lang: en
+
+An article without a translation.
+Here is a link to an `untranslated page`_
+
+.. _`untranslated page`: {filename}/pages/untranslated-page.rst
diff --git a/plugins/i18n_subsites/test_data/localized_theme/babel.cfg b/plugins/i18n_subsites/test_data/localized_theme/babel.cfg
new file mode 100644
index 0000000..4406732
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/localized_theme/babel.cfg
@@ -0,0 +1,2 @@
+[jinja2: templates/**.html]
+
diff --git a/plugins/i18n_subsites/test_data/localized_theme/messages.pot b/plugins/i18n_subsites/test_data/localized_theme/messages.pot
new file mode 100644
index 0000000..578917f
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/localized_theme/messages.pot
@@ -0,0 +1,23 @@
+# Translations template for PROJECT.
+# Copyright (C) 2014 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2014-07-13 12:25+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+
+#: templates/base.html:3
+msgid "Welcome to our"
+msgstr ""
+
diff --git a/plugins/i18n_subsites/test_data/localized_theme/static/style.css b/plugins/i18n_subsites/test_data/localized_theme/static/style.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/localized_theme/static/style.css
diff --git a/plugins/i18n_subsites/test_data/localized_theme/templates/base.html b/plugins/i18n_subsites/test_data/localized_theme/templates/base.html
new file mode 100644
index 0000000..a24eb1d
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/localized_theme/templates/base.html
@@ -0,0 +1,7 @@
+{% extends "!simple/base.html" %}
+
+{% block title %}{% trans %}Welcome to our{% endtrans %} {{ SITENAME }}{% endblock %}
+{% block head %}
+{{ super() }}
+<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/style.css" />
+{% endblock %}
diff --git a/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo
new file mode 100644
index 0000000..2390102
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po
new file mode 100644
index 0000000..2eb4efb
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.po
@@ -0,0 +1,23 @@
+# German translations for PROJECT.
+# Copyright (C) 2014 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2014-07-13 12:25+0200\n"
+"PO-Revision-Date: 2014-07-13 12:26+0200\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: de <LL@li.org>\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+
+#: templates/base.html:3
+msgid "Welcome to our"
+msgstr "Willkommen Sie zur unserer"
+
diff --git a/plugins/i18n_subsites/test_data/output/an-untranslated-article.html b/plugins/i18n_subsites/test_data/output/an-untranslated-article.html
new file mode 100644
index 0000000..f2f6494
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/an-untranslated-article.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testing site - An untranslated article</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/theme/style.css" />
+
+
+
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/">Testing site</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li>
+ <li class="active"><a href="http://example.com/test/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content" class="body">
+ <header>
+ <h2 class="entry-title">
+ <a href="http://example.com/test/an-untranslated-article.html" rel="bookmark"
+ title="Permalink to An untranslated article">An untranslated article</a></h2>
+
+ </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-07-14T00:00:00+00:00">
+ Mon 14 July 2014
+ </time>
+ <address class="vcard author">
+ By <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a>
+ </address>
+ <div class="category">
+ Category: <a href="http://example.com/test/category/misc.html">misc</a>
+ </div>
+ </footer><!-- /.post-info -->
+ <div class="entry-content">
+ <p>An article without a translation.
+Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p>
+
+ </div><!-- /.entry-content -->
+</section>
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html b/plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html
new file mode 100644
index 0000000..4673ee5
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/cz/an-untranslated-article-en.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testovací stránka - An untranslated article</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" />
+
+
+
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li class="active"><a href="http://example.com/test/cz/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content" class="body">
+ <header>
+ <h2 class="entry-title">
+ <a href="http://example.com/test/cz/an-untranslated-article-en.html" rel="bookmark"
+ title="Permalink to An untranslated article">An untranslated article</a></h2>
+
+ </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-07-14T00:00:00+00:00">
+ Mon 14 July 2014
+ </time>
+ <address class="vcard author">
+ By <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a>
+ </address>
+ <div class="category">
+ Category: <a href="http://example.com/test/cz/category/misc.html">misc</a>
+ </div>
+ </footer><!-- /.post-info -->
+ <div class="entry-content">
+ <p>An article without a translation.
+Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p>
+
+ </div><!-- /.entry-content -->
+</section>
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml b/plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml
new file mode 100644
index 0000000..7415e1f
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"><title>Testovací stránka</title><link href="http://example.com/test/cz/" rel="alternate"></link><link href="http://example.com/test/feeds_all.atom.xml" rel="self"></link><id>http://example.com/test/cz/</id><updated>2014-09-15T00:00:00+00:00</updated><entry><title>Přeložený článek</title><link href="http://example.com/test/cz/translated-article.html" rel="alternate"></link><published>2014-09-15T00:00:00+00:00</published><updated>2014-09-15T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-09-15:/test/cz/translated-article.html</id><content type="html">&lt;p&gt;Jednoduchý článek s překlady.
+Zde je odkaz na &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;nějaký obrázek&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>Ein übersetzter Artikel</title><link href="http://example.com/test/de/translated-article.html" rel="alternate"></link><published>2014-09-14T00:00:00+00:00</published><updated>2014-09-14T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-09-14:/test/de/translated-article.html</id><content type="html">&lt;p&gt;Ein einfacher Artikel mit einer Übersetzung.
+Hier ist ein Link zur &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;einigem Bild&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>A translated article</title><link href="http://example.com/test/translated-article.html" rel="alternate"></link><published>2014-09-13T00:00:00+00:00</published><updated>2014-09-13T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-09-13:/test/translated-article.html</id><content type="html">&lt;p&gt;A simple article with a translation.
+Here is a link to &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;some image&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>An untranslated article</title><link href="http://example.com/test/cz/an-untranslated-article-en.html" rel="alternate"></link><published>2014-07-14T00:00:00+00:00</published><updated>2014-07-14T00:00:00+00:00</updated><author><name>Test Testovič</name></author><id>tag:example.com,2014-07-14:/test/cz/an-untranslated-article-en.html</id><content type="html">&lt;p&gt;An article without a translation.
+Here is a link to an &lt;a class="reference external" href="http://example.com/test/pages/untranslated-page.html"&gt;untranslated page&lt;/a&gt;&lt;/p&gt;
+</content><category term="misc"></category></entry></feed> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/cz/index.html b/plugins/i18n_subsites/test_data/output/cz/index.html
new file mode 100644
index 0000000..c5fca32
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/cz/index.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="cz">
+<head>
+ <title>Welcome to our Testovací stránka</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" />
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/cz/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content">
+<h2>All articles</h2>
+
+<ol id="post-list">
+ <li><article class="hentry">
+ <header> <h2 class="entry-title"><a href="http://example.com/test/cz/translated-article.html" rel="bookmark" title="Permalink to Přeložený článek">Přeložený článek</a></h2> </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-09-15T00:00:00+00:00"> Mon 15 September 2014 </time>
+ <address class="vcard author">By
+ <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a>
+ </address>
+ </footer><!-- /.post-info -->
+ <div class="entry-content"> <p>Jednoduchý článek s překlady.
+Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p>
+ </div><!-- /.entry-content -->
+ </article></li>
+ <li><article class="hentry">
+ <header> <h2 class="entry-title"><a href="http://example.com/test/cz/an-untranslated-article-en.html" rel="bookmark" title="Permalink to An untranslated article">An untranslated article</a></h2> </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-07-14T00:00:00+00:00"> Mon 14 July 2014 </time>
+ <address class="vcard author">By
+ <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a>
+ </address>
+ </footer><!-- /.post-info -->
+ <div class="entry-content"> <p>An article without a translation.
+Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p>
+ </div><!-- /.entry-content -->
+ </article></li>
+</ol><!-- /#posts-list -->
+</section><!-- /#content -->
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/cz/pages/404.html b/plugins/i18n_subsites/test_data/output/cz/pages/404.html
new file mode 100644
index 0000000..884203b
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/cz/pages/404.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="cz">
+<head>
+ <title>Testovací stránka - 404 stránka</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" />
+
+
+ <link rel="alternate" hreflang="de" href="http://example.com/test/cz/../de/pages/404.html">
+ <link rel="alternate" hreflang="en" href="http://example.com/test/cz/../pages/404.html">
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/cz/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+ <h1>404 stránka</h1>
+ Translations:
+<a href="http://example.com/test/cz/../de/pages/404.html" hreflang="de">de</a>
+<a href="http://example.com/test/cz/../pages/404.html" hreflang="en">en</a>
+
+
+ <p>Jednoduchá 404 stránka.</p>
+
+
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html>
diff --git a/plugins/i18n_subsites/test_data/output/cz/translated-article.html b/plugins/i18n_subsites/test_data/output/cz/translated-article.html
new file mode 100644
index 0000000..5a5e545
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/cz/translated-article.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="cz">
+<head>
+ <title>Testovací stránka - Přeložený článek</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testovací stránka Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/cz/../theme/style.css" />
+
+
+ <link rel="alternate" hreflang="de" href="http://example.com/test/cz/../de/translated-article.html">
+ <link rel="alternate" hreflang="en" href="http://example.com/test/cz/../translated-article.html">
+
+
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/cz/">Testovací stránka</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li class="active"><a href="http://example.com/test/cz/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content" class="body">
+ <header>
+ <h2 class="entry-title">
+ <a href="http://example.com/test/cz/translated-article.html" rel="bookmark"
+ title="Permalink to Přeložený článek">Přeložený článek</a></h2>
+ Translations:
+<a href="http://example.com/test/cz/../de/translated-article.html" hreflang="de">de</a>
+<a href="http://example.com/test/cz/../translated-article.html" hreflang="en">en</a>
+
+ </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-09-15T00:00:00+00:00">
+ Mon 15 September 2014
+ </time>
+ <address class="vcard author">
+ By <a class="url fn" href="http://example.com/test/cz/author/test-testovic.html">Test Testovič</a>
+ </address>
+ <div class="category">
+ Category: <a href="http://example.com/test/cz/category/misc.html">misc</a>
+ </div>
+ </footer><!-- /.post-info -->
+ <div class="entry-content">
+ <p>Jednoduchý článek s překlady.
+Zde je odkaz na <a class="reference external" href="http://example.com/test/images/img.png">nějaký obrázek</a>.</p>
+
+ </div><!-- /.entry-content -->
+</section>
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html b/plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html
new file mode 100644
index 0000000..f01dc31
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/de/drafts/an-untranslated-article-en.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testseite - An untranslated article</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" />
+
+
+
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/de/">Testseite</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li class="active"><a href="http://example.com/test/de/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content" class="body">
+ <header>
+ <h2 class="entry-title">
+ <a href="http://example.com/test/de/drafts/an-untranslated-article-en.html" rel="bookmark"
+ title="Permalink to An untranslated article">An untranslated article</a></h2>
+
+ </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-07-14T00:00:00+00:00">
+ Mo 14 Juli 2014
+ </time>
+ <address class="vcard author">
+ By <a class="url fn" href="http://example.com/test/de/author/der-tester.html">Der Tester</a>
+ </address>
+ <div class="category">
+ Category: <a href="http://example.com/test/de/category/misc.html">misc</a>
+ </div>
+ </footer><!-- /.post-info -->
+ <div class="entry-content">
+ <p>An article without a translation.
+Here is a link to an <a class="reference external" href="http://example.com/test/de/pages/untranslated-page-en.html">untranslated page</a></p>
+
+ </div><!-- /.entry-content -->
+</section>
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml b/plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml
new file mode 100644
index 0000000..8d845fe
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/de/feeds_all.atom.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"><title>Testseite</title><link href="http://example.com/test/de/" rel="alternate"></link><link href="http://example.com/test/feeds_all.atom.xml" rel="self"></link><id>http://example.com/test/de/</id><updated>2014-09-15T00:00:00+00:00</updated><entry><title>Přeložený článek</title><link href="http://example.com/test/cz/translated-article.html" rel="alternate"></link><published>2014-09-15T00:00:00+00:00</published><updated>2014-09-15T00:00:00+00:00</updated><author><name>Der Tester</name></author><id>tag:example.com,2014-09-15:/test/cz/translated-article.html</id><content type="html">&lt;p&gt;Jednoduchý článek s překlady.
+Zde je odkaz na &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;nějaký obrázek&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>Ein übersetzter Artikel</title><link href="http://example.com/test/de/translated-article.html" rel="alternate"></link><published>2014-09-14T00:00:00+00:00</published><updated>2014-09-14T00:00:00+00:00</updated><author><name>Der Tester</name></author><id>tag:example.com,2014-09-14:/test/de/translated-article.html</id><content type="html">&lt;p&gt;Ein einfacher Artikel mit einer Übersetzung.
+Hier ist ein Link zur &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;einigem Bild&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>A translated article</title><link href="http://example.com/test/translated-article.html" rel="alternate"></link><published>2014-09-13T00:00:00+00:00</published><updated>2014-09-13T00:00:00+00:00</updated><author><name>Der Tester</name></author><id>tag:example.com,2014-09-13:/test/translated-article.html</id><content type="html">&lt;p&gt;A simple article with a translation.
+Here is a link to &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;some image&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry></feed> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/de/index.html b/plugins/i18n_subsites/test_data/output/de/index.html
new file mode 100644
index 0000000..fc43ae1
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/de/index.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <title>Willkommen Sie zur unserer Testseite</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" />
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/de/">Testseite</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/de/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content">
+<h2>All articles</h2>
+
+<ol id="post-list">
+ <li><article class="hentry">
+ <header> <h2 class="entry-title"><a href="http://example.com/test/de/translated-article.html" rel="bookmark" title="Permalink to Ein übersetzter Artikel">Ein übersetzter Artikel</a></h2> </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-09-14T00:00:00+00:00"> So 14 September 2014 </time>
+ <address class="vcard author">By
+ <a class="url fn" href="http://example.com/test/de/author/der-tester.html">Der Tester</a>
+ </address>
+ </footer><!-- /.post-info -->
+ <div class="entry-content"> <p>Ein einfacher Artikel mit einer Übersetzung.
+Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p>
+ </div><!-- /.entry-content -->
+ </article></li>
+</ol><!-- /#posts-list -->
+</section><!-- /#content -->
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/de/pages/404.html b/plugins/i18n_subsites/test_data/output/de/pages/404.html
new file mode 100644
index 0000000..6425660
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/de/pages/404.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <title>Testseite - Eine 404 Seite</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" />
+
+
+ <link rel="alternate" hreflang="cz" href="http://example.com/test/de/../cz/pages/404.html">
+ <link rel="alternate" hreflang="en" href="http://example.com/test/de/../pages/404.html">
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/de/">Testseite</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/de/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+ <h1>Eine 404 Seite</h1>
+ Translations:
+<a href="http://example.com/test/de/../cz/pages/404.html" hreflang="cz">cz</a>
+<a href="http://example.com/test/de/../pages/404.html" hreflang="en">en</a>
+
+
+ <p>Eine einfache 404 Seite.</p>
+
+
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html b/plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html
new file mode 100644
index 0000000..2df5ff9
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/de/pages/untranslated-page-en.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testseite - Untranslated page</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" />
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/de/">Testseite</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/de/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+ <h1>Untranslated page</h1>
+
+
+ <p>This page has no translation.</p>
+
+
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/de/translated-article.html b/plugins/i18n_subsites/test_data/output/de/translated-article.html
new file mode 100644
index 0000000..9393b95
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/de/translated-article.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+ <title>Testseite - Ein übersetzter Artikel</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testseite Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/de/../theme/style.css" />
+
+
+ <link rel="alternate" hreflang="cz" href="http://example.com/test/de/../cz/translated-article.html">
+ <link rel="alternate" hreflang="en" href="http://example.com/test/de/../translated-article.html">
+
+
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/de/">Testseite</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li class="active"><a href="http://example.com/test/de/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content" class="body">
+ <header>
+ <h2 class="entry-title">
+ <a href="http://example.com/test/de/translated-article.html" rel="bookmark"
+ title="Permalink to Ein übersetzter Artikel">Ein übersetzter Artikel</a></h2>
+ Translations:
+<a href="http://example.com/test/de/../cz/translated-article.html" hreflang="cz">cz</a>
+<a href="http://example.com/test/de/../translated-article.html" hreflang="en">en</a>
+
+ </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-09-14T00:00:00+00:00">
+ So 14 September 2014
+ </time>
+ <address class="vcard author">
+ By <a class="url fn" href="http://example.com/test/de/author/der-tester.html">Der Tester</a>
+ </address>
+ <div class="category">
+ Category: <a href="http://example.com/test/de/category/misc.html">misc</a>
+ </div>
+ </footer><!-- /.post-info -->
+ <div class="entry-content">
+ <p>Ein einfacher Artikel mit einer Übersetzung.
+Hier ist ein Link zur <a class="reference external" href="http://example.com/test/images/img.png">einigem Bild</a>.</p>
+
+ </div><!-- /.entry-content -->
+</section>
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/feeds_all.atom.xml b/plugins/i18n_subsites/test_data/output/feeds_all.atom.xml
new file mode 100644
index 0000000..c6c0908
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/feeds_all.atom.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"><title>Testing site</title><link href="http://example.com/test/" rel="alternate"></link><link href="http://example.com/test/feeds_all.atom.xml" rel="self"></link><id>http://example.com/test/</id><updated>2014-09-15T00:00:00+00:00</updated><entry><title>Přeložený článek</title><link href="http://example.com/test/cz/translated-article.html" rel="alternate"></link><published>2014-09-15T00:00:00+00:00</published><updated>2014-09-15T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-09-15:/test/cz/translated-article.html</id><content type="html">&lt;p&gt;Jednoduchý článek s překlady.
+Zde je odkaz na &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;nějaký obrázek&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>Ein übersetzter Artikel</title><link href="http://example.com/test/de/translated-article.html" rel="alternate"></link><published>2014-09-14T00:00:00+00:00</published><updated>2014-09-14T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-09-14:/test/de/translated-article.html</id><content type="html">&lt;p&gt;Ein einfacher Artikel mit einer Übersetzung.
+Hier ist ein Link zur &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;einigem Bild&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>A translated article</title><link href="http://example.com/test/translated-article.html" rel="alternate"></link><published>2014-09-13T00:00:00+00:00</published><updated>2014-09-13T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-09-13:/test/translated-article.html</id><content type="html">&lt;p&gt;A simple article with a translation.
+Here is a link to &lt;a class="reference external" href="http://example.com/test/images/img.png"&gt;some image&lt;/a&gt;.&lt;/p&gt;
+</content><category term="misc"></category></entry><entry><title>An untranslated article</title><link href="http://example.com/test/an-untranslated-article.html" rel="alternate"></link><published>2014-07-14T00:00:00+00:00</published><updated>2014-07-14T00:00:00+00:00</updated><author><name>The Tester</name></author><id>tag:example.com,2014-07-14:/test/an-untranslated-article.html</id><content type="html">&lt;p&gt;An article without a translation.
+Here is a link to an &lt;a class="reference external" href="http://example.com/test/pages/untranslated-page.html"&gt;untranslated page&lt;/a&gt;&lt;/p&gt;
+</content><category term="misc"></category></entry></feed> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/images/img.png b/plugins/i18n_subsites/test_data/output/images/img.png
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/images/img.png
diff --git a/plugins/i18n_subsites/test_data/output/index.html b/plugins/i18n_subsites/test_data/output/index.html
new file mode 100644
index 0000000..c455687
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Welcome to our Testing site</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/theme/style.css" />
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/">Testing site</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li>
+ <li><a href="http://example.com/test/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content">
+<h2>All articles</h2>
+
+<ol id="post-list">
+ <li><article class="hentry">
+ <header> <h2 class="entry-title"><a href="http://example.com/test/translated-article.html" rel="bookmark" title="Permalink to A translated article">A translated article</a></h2> </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-09-13T00:00:00+00:00"> Sat 13 September 2014 </time>
+ <address class="vcard author">By
+ <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a>
+ </address>
+ </footer><!-- /.post-info -->
+ <div class="entry-content"> <p>A simple article with a translation.
+Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p>
+ </div><!-- /.entry-content -->
+ </article></li>
+ <li><article class="hentry">
+ <header> <h2 class="entry-title"><a href="http://example.com/test/an-untranslated-article.html" rel="bookmark" title="Permalink to An untranslated article">An untranslated article</a></h2> </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-07-14T00:00:00+00:00"> Mon 14 July 2014 </time>
+ <address class="vcard author">By
+ <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a>
+ </address>
+ </footer><!-- /.post-info -->
+ <div class="entry-content"> <p>An article without a translation.
+Here is a link to an <a class="reference external" href="http://example.com/test/pages/untranslated-page.html">untranslated page</a></p>
+ </div><!-- /.entry-content -->
+ </article></li>
+</ol><!-- /#posts-list -->
+</section><!-- /#content -->
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/pages/404.html b/plugins/i18n_subsites/test_data/output/pages/404.html
new file mode 100644
index 0000000..4695f3c
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/pages/404.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testing site - A 404 page</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/theme/style.css" />
+
+
+ <link rel="alternate" hreflang="cz" href="http://example.com/test/cz/pages/404.html">
+ <link rel="alternate" hreflang="de" href="http://example.com/test/de/pages/404.html">
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/">Testing site</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li>
+ <li><a href="http://example.com/test/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+ <h1>A 404 page</h1>
+ Translations:
+<a href="http://example.com/test/cz/pages/404.html" hreflang="cz">cz</a>
+<a href="http://example.com/test/de/pages/404.html" hreflang="de">de</a>
+
+
+ <p>A simple 404 page.</p>
+
+
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/pages/untranslated-page.html b/plugins/i18n_subsites/test_data/output/pages/untranslated-page.html
new file mode 100644
index 0000000..3ff02df
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/pages/untranslated-page.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testing site - Untranslated page</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/theme/style.css" />
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/">Testing site</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li class="active"><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li>
+ <li><a href="http://example.com/test/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+ <h1>Untranslated page</h1>
+
+
+ <p>This page has no translation.</p>
+
+
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/output/theme/style.css b/plugins/i18n_subsites/test_data/output/theme/style.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/theme/style.css
diff --git a/plugins/i18n_subsites/test_data/output/translated-article.html b/plugins/i18n_subsites/test_data/output/translated-article.html
new file mode 100644
index 0000000..45be1bb
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/output/translated-article.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Testing site - A translated article</title>
+ <meta charset="utf-8" />
+ <meta name="generator" content="Pelican" />
+ <link href="http://example.com/test/feeds_all.atom.xml" type="application/atom+xml" rel="alternate" title="Testing site Full Atom Feed" />
+
+<link rel="stylesheet" href="http://example.com/test/theme/style.css" />
+
+
+ <link rel="alternate" hreflang="cz" href="http://example.com/test/cz/translated-article.html">
+ <link rel="alternate" hreflang="de" href="http://example.com/test/de/translated-article.html">
+
+
+
+
+</head>
+
+<body id="index" class="home">
+ <header id="banner" class="body">
+ <h1><a href="http://example.com/test/">Testing site</a></h1>
+ </header><!-- /#banner -->
+ <nav id="menu"><ul>
+ <li><a href="http://example.com/test/pages/untranslated-page.html">Untranslated page</a></li>
+ <li class="active"><a href="http://example.com/test/category/misc.html">misc</a></li>
+ </ul></nav><!-- /#menu -->
+<section id="content" class="body">
+ <header>
+ <h2 class="entry-title">
+ <a href="http://example.com/test/translated-article.html" rel="bookmark"
+ title="Permalink to A translated article">A translated article</a></h2>
+ Translations:
+<a href="http://example.com/test/cz/translated-article.html" hreflang="cz">cz</a>
+<a href="http://example.com/test/de/translated-article.html" hreflang="de">de</a>
+
+ </header>
+ <footer class="post-info">
+ <time class="published" datetime="2014-09-13T00:00:00+00:00">
+ Sat 13 September 2014
+ </time>
+ <address class="vcard author">
+ By <a class="url fn" href="http://example.com/test/author/the-tester.html">The Tester</a>
+ </address>
+ <div class="category">
+ Category: <a href="http://example.com/test/category/misc.html">misc</a>
+ </div>
+ </footer><!-- /.post-info -->
+ <div class="entry-content">
+ <p>A simple article with a translation.
+Here is a link to <a class="reference external" href="http://example.com/test/images/img.png">some image</a>.</p>
+
+ </div><!-- /.entry-content -->
+</section>
+ <footer id="contentinfo" class="body">
+ <address id="about" class="vcard body">
+ Proudly powered by <a href="https://getpelican.com/">Pelican</a>,
+ which takes great advantage of <a href="https://www.python.org/">Python</a>.
+ </address><!-- /#about -->
+ </footer><!-- /#contentinfo -->
+</body>
+</html> \ No newline at end of file
diff --git a/plugins/i18n_subsites/test_data/pelicanconf.py b/plugins/i18n_subsites/test_data/pelicanconf.py
new file mode 100644
index 0000000..55018f2
--- /dev/null
+++ b/plugins/i18n_subsites/test_data/pelicanconf.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*- #
+from __future__ import unicode_literals
+
+AUTHOR = 'The Tester'
+SITENAME = 'Testing site'
+SITEURL = 'http://example.com/test'
+
+# to make the test suite portable
+TIMEZONE = 'UTC'
+
+DEFAULT_LANG = 'en'
+LOCALE = 'en_US.UTF-8'
+
+# Generate only one feed
+FEED_ALL_ATOM = 'feeds_all.atom.xml'
+CATEGORY_FEED_ATOM = None
+TRANSLATION_FEED_ATOM = None
+AUTHOR_FEED_ATOM = None
+AUTHOR_FEED_RSS = None
+
+# Disable unnecessary pages
+CATEGORY_SAVE_AS = ''
+TAG_SAVE_AS = ''
+AUTHOR_SAVE_AS = ''
+ARCHIVES_SAVE_AS = ''
+AUTHORS_SAVE_AS = ''
+CATEGORIES_SAVE_AS = ''
+TAGS_SAVE_AS = ''
+
+PLUGIN_PATHS = ['../../']
+PLUGINS = ['i18n_subsites']
+
+THEME = 'localized_theme'
+JINJA_ENVIRONMENT = {'extensions': ['jinja2.ext.i18n']}
+
+from blinker import signal
+tmpsig = signal('tmpsig')
+I18N_FILTER_SIGNALS = [tmpsig]
+
+I18N_SUBSITES = {
+ 'de': {
+ 'SITENAME': 'Testseite',
+ 'AUTHOR': 'Der Tester',
+ 'LOCALE': 'de_DE.UTF-8',
+ },
+ 'cz': {
+ 'SITENAME': 'Testovací stránka',
+ 'AUTHOR': 'Test Testovič',
+ 'I18N_UNTRANSLATED_PAGES': 'remove',
+ 'I18N_UNTRANSLATED_ARTICLES': 'keep',
+ },
+ }
diff --git a/plugins/i18n_subsites/test_i18n_subsites.py b/plugins/i18n_subsites/test_i18n_subsites.py
new file mode 100644
index 0000000..83d0cb9
--- /dev/null
+++ b/plugins/i18n_subsites/test_i18n_subsites.py
@@ -0,0 +1,139 @@
+'''Unit tests for the i18n_subsites plugin'''
+
+import os
+import locale
+import unittest
+import subprocess
+from tempfile import mkdtemp
+from shutil import rmtree
+
+from . import i18n_subsites as i18ns
+from pelican import Pelican
+from pelican.tests.support import get_settings
+from pelican.settings import read_settings
+
+
+class TestTemporaryLocale(unittest.TestCase):
+ '''Test the temporary locale context manager'''
+
+ def test_locale_restored(self):
+ '''Test that the locale is restored after exiting context'''
+ orig_locale = locale.setlocale(locale.LC_ALL)
+ with i18ns.temporary_locale():
+ locale.setlocale(locale.LC_ALL, 'C')
+ self.assertEqual(locale.setlocale(locale.LC_ALL), 'C')
+ self.assertEqual(locale.setlocale(locale.LC_ALL), orig_locale)
+
+ def test_temp_locale_set(self):
+ '''Test that the temporary locale is set'''
+ with i18ns.temporary_locale('C'):
+ self.assertEqual(locale.setlocale(locale.LC_ALL), 'C')
+
+
+class TestSettingsManipulation(unittest.TestCase):
+ '''Test operations on settings dict'''
+
+ def setUp(self):
+ '''Prepare default settings'''
+ self.settings = get_settings()
+
+ def test_get_pelican_cls_class(self):
+ '''Test that we get class given as an object'''
+ 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'''
+
+ def setUp(self):
+ '''Generate some sample siteurls'''
+ self.siteurl = 'http://example.com'
+ i18ns._SITE_DB['en'] = self.siteurl
+ i18ns._SITE_DB['de'] = self.siteurl + '/de'
+
+ def tearDown(self):
+ '''Remove sites from db'''
+ i18ns._SITE_DB.clear()
+
+ def test_get_site_path(self):
+ '''Test getting the path within a site'''
+ self.assertEqual(i18ns.get_site_path(self.siteurl), '/')
+ self.assertEqual(i18ns.get_site_path(self.siteurl + '/de'), '/de')
+
+ def test_relpath_to_site(self):
+ '''Test getting relative paths between sites'''
+ self.assertEqual(i18ns.relpath_to_site('en', 'de'), 'de')
+ self.assertEqual(i18ns.relpath_to_site('de', 'en'), '..')
+
+
+class TestRegistration(unittest.TestCase):
+ '''Test plugin registration'''
+
+ def test_return_on_missing_signal(self):
+ '''Test return on missing required signal'''
+ i18ns._SIGNAL_HANDLERS_DB['tmp_sig'] = None
+ i18ns.register()
+ self.assertNotIn(id(i18ns.save_generator),
+ i18ns.signals.generator_init.receivers)
+
+ def test_registration(self):
+ '''Test registration of all signal handlers'''
+ i18ns.register()
+ for sig_name, handler in i18ns._SIGNAL_HANDLERS_DB.items():
+ sig = getattr(i18ns.signals, sig_name)
+ 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 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.
+ '''
+ 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,
+ '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))
diff --git a/plugins/related_posts/Readme.rst b/plugins/related_posts/Readme.rst
new file mode 100644
index 0000000..0edf4bd
--- /dev/null
+++ b/plugins/related_posts/Readme.rst
@@ -0,0 +1,38 @@
+Related posts
+-------------
+
+**NOTE:** `This plugin has been moved to its own repository <https://github.com/pelican-plugins/related-posts>`_. Please file any issues/PRs there. Once all plugins have been migrated to the `new Pelican Plugins organization <https://github.com/pelican-plugins>`_, this monolithic repository will be archived.
+
+-------------------------------------------------------------------------------
+
+This plugin adds the ``related_posts`` variable to the article's context.
+By default, up to 5 articles are listed. You can customize this value by
+defining ``RELATED_POSTS_MAX`` in your settings file::
+
+ RELATED_POSTS_MAX = 10
+
+You can then use the ``article.related_posts`` variable in your templates.
+For example::
+
+ {% if article.related_posts %}
+ <ul>
+ {% for related_post in article.related_posts %}
+ <li><a href="{{ SITEURL }}/{{ related_post.url }}">{{ related_post.title }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+
+Your related posts should share a common tag. You can also use ``related_posts:`` in your post's meta data.
+The 'related_posts:' meta data works together with your existing slugs::
+
+ related_posts: slug1, slug2, slug3, ... slugN
+
+``N`` represents the ``RELATED_POSTS_MAX``.
+
+Additionally, you can specify::
+
+ RELATED_POSTS_SKIP_SAME_CATEGORY = True
+
+in your settings file. With this setting, ``article.related_posts`` will
+contain only related posts from categories other than the original article's.
diff --git a/plugins/related_posts/__init__.py b/plugins/related_posts/__init__.py
index c78861e..057540e 100644
--- a/plugins/related_posts/__init__.py
+++ b/plugins/related_posts/__init__.py
@@ -1 +1 @@
-from .related_posts import *
+from .related_posts import *
diff --git a/plugins/related_posts/related_posts.py b/plugins/related_posts/related_posts.py
index a0cfa15..fbb2426 100755..100644
--- a/plugins/related_posts/related_posts.py
+++ b/plugins/related_posts/related_posts.py
@@ -14,15 +14,14 @@ def add_related_posts(generator):
# get the max number of entries from settings
# or fall back to default (5)
numentries = generator.settings.get('RELATED_POSTS_MAX', 5)
- ignore_tags = generator.settings.get('RELATED_POSTS_IGNORE_TAGS', [])
# Skip all posts in the same category as the article
skipcategory = generator.settings.get('RELATED_POSTS_SKIP_SAME_CATEGORY', False)
- for article in generator.articles:
+ for article in chain(generator.articles, generator.drafts):
# set priority in case of forced related posts
if hasattr(article,'related_posts'):
- # split slugs
+ # split slugs
related_posts = article.related_posts.split(',')
- posts = []
+ posts = []
# get related articles
for slug in related_posts:
i = 0
@@ -41,7 +40,7 @@ def add_related_posts(generator):
continue
# score = number of common tags
- related = chain(*(generator.tags[tag] for tag in article.tags if tag.name not in ignore_tags))
+ related = chain(*(generator.tags[tag] for tag in article.tags))
if skipcategory:
related = (other for other in related
if other.category != article.category)
@@ -50,7 +49,7 @@ def add_related_posts(generator):
# remove itself
scores.pop(article, None)
- article.related_posts = [other for other, count
+ article.related_posts = [other for other, count
in scores.most_common(numentries)]
def register():