Feat: french translation
This commit is contained in:
parent
f47fe95757
commit
8696d8c176
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "pelican-plugins"]
|
|
||||||
path = pelican-plugins
|
|
||||||
url = https://github.com/getpelican/pelican-plugins
|
|
@ -1 +0,0 @@
|
|||||||
Subproject commit 5c5f965c984d9cb9324c1c7b251d448992c4bda5
|
|
@ -24,6 +24,23 @@ DEFAULT_LANG = 'fr'
|
|||||||
|
|
||||||
# theme
|
# theme
|
||||||
THEME = "./theme/"
|
THEME = "./theme/"
|
||||||
|
# Plugins
|
||||||
|
PLUGIN_PATHS = ["plugins"]
|
||||||
|
PLUGINS = [
|
||||||
|
'i18n_subsites',
|
||||||
|
# 'pdf-img',
|
||||||
|
]
|
||||||
|
# Readers
|
||||||
|
READERS = {"html": None}
|
||||||
|
# Everythings in french
|
||||||
|
JINJA_ENVIRONMENT = {'extensions': ['jinja2.ext.i18n']}
|
||||||
|
# Default theme language.
|
||||||
|
I18N_TEMPLATES_LANG = "en"
|
||||||
|
# Your language.
|
||||||
|
DEFAULT_LANG = "fr"
|
||||||
|
OG_LOCALE = "fr"
|
||||||
|
LOCALE = ("fr", "fr_FR.utf8")
|
||||||
|
|
||||||
USE_GOOGLE_FONTS = False
|
USE_GOOGLE_FONTS = False
|
||||||
|
|
||||||
# Pages, articles and static
|
# Pages, articles and static
|
||||||
|
165
plugins/i18n_subsites/README.rst
Normal file
165
plugins/i18n_subsites/README.rst
Normal file
@ -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/
|
1
plugins/i18n_subsites/__init__.py
Normal file
1
plugins/i18n_subsites/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .i18n_subsites import *
|
462
plugins/i18n_subsites/i18n_subsites.py
Normal file
462
plugins/i18n_subsites/i18n_subsites.py
Normal file
@ -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)
|
128
plugins/i18n_subsites/implementing_language_buttons.rst
Normal file
128
plugins/i18n_subsites/implementing_language_buttons.rst
Normal file
@ -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>`_.
|
202
plugins/i18n_subsites/localizing_using_jinja2.rst
Normal file
202
plugins/i18n_subsites/localizing_using_jinja2.rst
Normal file
@ -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.
|
@ -0,0 +1,7 @@
|
|||||||
|
404 stránka
|
||||||
|
===========
|
||||||
|
:slug: 404
|
||||||
|
:lang: cz
|
||||||
|
:status: hidden
|
||||||
|
|
||||||
|
Jednoduchá 404 stránka.
|
@ -0,0 +1,7 @@
|
|||||||
|
Eine 404 Seite
|
||||||
|
==============
|
||||||
|
:slug: 404
|
||||||
|
:lang: de
|
||||||
|
:status: hidden
|
||||||
|
|
||||||
|
Eine einfache 404 Seite.
|
@ -0,0 +1,7 @@
|
|||||||
|
A 404 page
|
||||||
|
==========
|
||||||
|
:slug: 404
|
||||||
|
:lang: en
|
||||||
|
:status: hidden
|
||||||
|
|
||||||
|
A simple 404 page.
|
@ -0,0 +1,5 @@
|
|||||||
|
Untranslated page
|
||||||
|
=================
|
||||||
|
:lang: en
|
||||||
|
|
||||||
|
This page has no translation.
|
@ -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>`_.
|
@ -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>`_.
|
@ -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>`_.
|
@ -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
|
@ -0,0 +1,2 @@
|
|||||||
|
[jinja2: templates/**.html]
|
||||||
|
|
23
plugins/i18n_subsites/test_data/localized_theme/messages.pot
Normal file
23
plugins/i18n_subsites/test_data/localized_theme/messages.pot
Normal file
@ -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 ""
|
||||||
|
|
@ -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 %}
|
Binary file not shown.
@ -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"
|
||||||
|
|
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testing site - An untranslated article</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testovací stránka - An untranslated article</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
10
plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml
Normal file
10
plugins/i18n_subsites/test_data/output/cz/feeds_all.atom.xml
Normal file
@ -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></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></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></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></entry></feed>
|
55
plugins/i18n_subsites/test_data/output/cz/index.html
Normal file
55
plugins/i18n_subsites/test_data/output/cz/index.html
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cz">
|
||||||
|
<head>
|
||||||
|
<title>Welcome to our Testovací stránka</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
39
plugins/i18n_subsites/test_data/output/cz/pages/404.html
Normal file
39
plugins/i18n_subsites/test_data/output/cz/pages/404.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cz">
|
||||||
|
<head>
|
||||||
|
<title>Testovací stránka - 404 stránka</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cz">
|
||||||
|
<head>
|
||||||
|
<title>Testovací stránka - Přeložený článek</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testseite - An untranslated article</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -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></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></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></entry></feed>
|
43
plugins/i18n_subsites/test_data/output/de/index.html
Normal file
43
plugins/i18n_subsites/test_data/output/de/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<title>Willkommen Sie zur unserer Testseite</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
39
plugins/i18n_subsites/test_data/output/de/pages/404.html
Normal file
39
plugins/i18n_subsites/test_data/output/de/pages/404.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<title>Testseite - Eine 404 Seite</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testseite - Untranslated page</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<title>Testseite - Ein übersetzter Artikel</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
10
plugins/i18n_subsites/test_data/output/feeds_all.atom.xml
Normal file
10
plugins/i18n_subsites/test_data/output/feeds_all.atom.xml
Normal file
@ -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></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></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></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></entry></feed>
|
56
plugins/i18n_subsites/test_data/output/index.html
Normal file
56
plugins/i18n_subsites/test_data/output/index.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Welcome to our Testing site</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
40
plugins/i18n_subsites/test_data/output/pages/404.html
Normal file
40
plugins/i18n_subsites/test_data/output/pages/404.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testing site - A 404 page</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testing site - Untranslated page</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Testing site - A translated article</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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 <strong></strong></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="http://getpelican.com/">Pelican</a>,
|
||||||
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
||||||
|
</address><!-- /#about -->
|
||||||
|
</footer><!-- /#contentinfo -->
|
||||||
|
</body>
|
||||||
|
</html>
|
53
plugins/i18n_subsites/test_data/pelicanconf.py
Normal file
53
plugins/i18n_subsites/test_data/pelicanconf.py
Normal file
@ -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',
|
||||||
|
},
|
||||||
|
}
|
139
plugins/i18n_subsites/test_i18n_subsites.py
Normal file
139
plugins/i18n_subsites/test_i18n_subsites.py
Normal file
@ -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(out))
|
Loading…
Reference in New Issue
Block a user