diff options
| author | Sébastien Dailly <sebastien@chimrod.com> | 2020-11-30 22:56:26 +0100 | 
|---|---|---|
| committer | Sébastien Dailly <sebastien@chimrod.com> | 2020-12-03 21:35:35 +0100 | 
| commit | 9b77ec15e5beeff3f57f845be883416d2a68b84d (patch) | |
| tree | 796f2aecfcdf5012ce611fac22b85fa481bf63de /plugins/i18n_subsites | |
| parent | 1c02ae819eee2d28040804d58872ceb4c003ee1f (diff) | |
New article on rst & Latex. Changed theme
Diffstat (limited to 'plugins/i18n_subsites')
41 files changed, 1989 insertions, 0 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;"> </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;"> </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.moBinary files differ new file mode 100644 index 0000000..2390102 --- /dev/null +++ b/plugins/i18n_subsites/test_data/localized_theme/translations/de/LC_MESSAGES/messages.mo 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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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"><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> +</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)) | 
