i18n_subsites: add lang_subsites template var, improve docs

- the new variable makes it easier to implement language buttons
- a short howto on language buttons is also provided
- the dictionary mapping template vars are now OrderedDict
  for consistent-looking language buttons
This commit is contained in:
Ondrej Grover 2014-02-05 21:28:15 +01:00
parent aa5c414d0b
commit 4c33944bb9
4 changed files with 167 additions and 25 deletions

View File

@ -8,7 +8,7 @@ What it does
============ ============
1. The *\*_LANG_URL* and *\*_LANG_SAVE_AS* variables are set to their normal counterparts (e.g. *ARTICLE_URL*) so they don't conflict with this scheme. 1. The *\*_LANG_URL* and *\*_LANG_SAVE_AS* variables are set to their normal counterparts (e.g. *ARTICLE_URL*) so they don't conflict with this scheme.
2. While building the site for *DEFAULT_LANG* the translations of pages and articles are not generated, but their relations to the original content is kept via links to them. 2. While building the site for *DEFAULT_LANG* the translations of pages and articles are not generated, but their relations to the original content is kept via links to them.
3. For each non-default language a "sub-site" with a modified config [#conf]_ is created [#run]_, linking the translations to the originals (if available). The configured language code is appended to the *OUTPUT_PATH* and *SITEURL* of each sub-site. 3. For each non-default language a "sub-site" with a modified config [#conf]_ is created [#run]_, linking the translations to the originals (if available). The configured language code is appended to the *OUTPUT_PATH* and *SITEURL* of each sub-site. For each sub-site, *DEFAULT_LANG* is changed to the language of the sub-site so that articles in a different language are treated as translations.
If *HIDE_UNTRANSLATED_CONTENT* is True (default), content without a translation for a language is generated as hidden (for pages) or draft (for articles) for the corresponding language sub-site. If *HIDE_UNTRANSLATED_CONTENT* is True (default), content without a translation for a language is generated as hidden (for pages) or draft (for articles) for the corresponding language sub-site.
@ -18,7 +18,9 @@ If *HIDE_UNTRANSLATED_CONTENT* is True (default), content without a translation
Setting it up Setting it up
============= =============
For each extra used language code, a language-specific variables overrides dictionary must be given (but can be empty) in the *I18N_SUBSITES* dictionary:: For each extra used language code, a language-specific variables overrides dictionary must be given (but can be empty) in the *I18N_SUBSITES* dictionary
.. code-block:: python
PLUGINS = ['i18n_subsites', ...] PLUGINS = ['i18n_subsites', ...]
@ -40,18 +42,24 @@ Most importantly, this plugin can use localized templates for each sub-site. The
- 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 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>`_. - 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>`_.
It may be convenient to add language buttons to your theme in addition to the translation links. 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: 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:
extra_siteurls
A dictionary mapping languages to their *SITEURL*. The *DEFAULT_LANG* language of the current sub-site is not included, so this dictionary serves as a complement to current *DEFAULT_LANG* and *SITEURL*. This dictionary is useful for implementing global language buttons.
main_lang main_lang
The language of the top-level site — the original *DEFAULT_LANG* The language of the top-level site — the original *DEFAULT_LANG*
main_siteurl main_siteurl
The *SITEURL* of the top-level site — the original *SITEURL* The *SITEURL* of the top-level 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.
If you don't like the default ordering of the ordered dictionaries, use a Jinja2 filter to alter the ordering.
This short `howto <./implementing_language_buttons.rst>`_ shows two example implementations of language buttons.
Usage notes Usage notes
=========== ===========
- It is **mandatory** to specify *lang* metadata for each article and page as *DEFAULT_LANG* is later changed for each sub-site. - 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 woudl 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. - 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.
Future plans Future plans
@ -63,5 +71,3 @@ Development
=========== ===========
- A demo and test site is in the ``gh-pages`` branch and can be seen at http://smartass101.github.io/pelican-plugins/ - A demo and test site is in the ``gh-pages`` branch and can be seen at http://smartass101.github.io/pelican-plugins/
.. LocalWords: lang metadata

View File

@ -6,7 +6,7 @@ import os
import six import six
import logging import logging
from itertools import chain from itertools import chain
from collections import defaultdict from collections import defaultdict, OrderedDict
import gettext import gettext
@ -22,6 +22,7 @@ from ._regenerate_context_helpers import regenerate_context_articles
_main_site_generated = False _main_site_generated = False
_main_site_lang = "en" _main_site_lang = "en"
_main_siteurl = '' _main_siteurl = ''
_lang_siteurls = None
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -32,7 +33,7 @@ def disable_lang_vars(pelican_obj):
e.g. ARTICLE_LANG_URL = ARTICLE_URL e.g. ARTICLE_LANG_URL = ARTICLE_URL
They would conflict with this plugin otherwise They would conflict with this plugin otherwise
""" """
global _main_site_lang, _main_siteurl global _main_site_lang, _main_siteurl, _lang_siteurls
s = pelican_obj.settings s = pelican_obj.settings
for content in ['ARTICLE', 'PAGE']: for content in ['ARTICLE', 'PAGE']:
for meta in ['_URL', '_SAVE_AS']: for meta in ['_URL', '_SAVE_AS']:
@ -40,6 +41,10 @@ def disable_lang_vars(pelican_obj):
if not _main_site_generated: if not _main_site_generated:
_main_site_lang = s['DEFAULT_LANG'] _main_site_lang = s['DEFAULT_LANG']
_main_siteurl = s['SITEURL'] _main_siteurl = s['SITEURL']
_lang_siteurls = [(lang, _main_siteurl + '/' + lang) for lang in s.get('I18N_SUBSITES', {}).keys()]
# To be able to use url for main site root when SITEURL == '' (e.g. when developing)
_lang_siteurls = [(_main_site_lang, ('/' if _main_siteurl == '' else _main_siteurl))] + _lang_siteurls
_lang_siteurls = OrderedDict(_lang_siteurls)
@ -61,7 +66,7 @@ def create_lang_subsites(pelican_obj):
for lang, overrides in orig_settings.get('I18N_SUBSITES', {}).items(): for lang, overrides in orig_settings.get('I18N_SUBSITES', {}).items():
settings = orig_settings.copy() settings = orig_settings.copy()
settings.update(overrides) settings.update(overrides)
settings['SITEURL'] = _main_siteurl + '/' + lang settings['SITEURL'] = _lang_siteurls[lang]
settings['OUTPUT_PATH'] = os.path.join(orig_settings['OUTPUT_PATH'], lang, '') settings['OUTPUT_PATH'] = os.path.join(orig_settings['OUTPUT_PATH'], lang, '')
settings['DEFAULT_LANG'] = lang # to change what is perceived as translations settings['DEFAULT_LANG'] = lang # to change what is perceived as translations
settings['DELETE_OUTPUT_DIRECTORY'] = False # prevent deletion of previous runs settings['DELETE_OUTPUT_DIRECTORY'] = False # prevent deletion of previous runs
@ -150,10 +155,9 @@ def install_templates_translations(generator):
""" """
generator.context['main_siteurl'] = _main_siteurl generator.context['main_siteurl'] = _main_siteurl
generator.context['main_lang'] = _main_site_lang generator.context['main_lang'] = _main_site_lang
extra_siteurls = { lang: _main_siteurl + '/' + lang for lang in generator.settings.get('I18N_SUBSITES', {}).keys() } generator.context['lang_siteurls'] = _lang_siteurls
# To be able to use url for main site root when SITEURL == '' (e.g. when developing)
extra_siteurls[_main_site_lang] = '/' if _main_siteurl == '' else _main_siteurl
current_def_lang = generator.settings['DEFAULT_LANG'] current_def_lang = generator.settings['DEFAULT_LANG']
extra_siteurls = _lang_siteurls.copy()
extra_siteurls.pop(current_def_lang) extra_siteurls.pop(current_def_lang)
generator.context['extra_siteurls'] = extra_siteurls generator.context['extra_siteurls'] = extra_siteurls

View File

@ -0,0 +1,113 @@
-----------------------------
Implementing language buttons
-----------------------------
Each article with translations has translations links, but that's the only way to switch between language subsites.
For this reason it is convenient to add language buttons to the top menu bar to make it simple to switch between the language subsites on all pages.
Example designs
---------------
Language buttons showing other available languages
..................................................
The ``extra_siteurls`` dictionary is a mapping of all other (not the *DEFAULT_LANG* of the current (sub-)site) languages to the *SITEURL* of the respective (sub-)sites
.. code-block:: jinja
<!-- SNIP -->
<nav><ul>
{% if extra_siteurls %}
{% for lang, url in extra_siteurls.items() %}
<li><a href="{{ url }}">{{ lang }}</a></li>
{% endfor %}
<!-- separator -->
<li style="background-color: white; padding: 5px;">&nbsp</li>
{% endif %}
{% for title, link in MENUITEMS %}
<!-- SNIP -->
Language buttons showing all available languages, current is active
..................................................................
The ``extra_siteurls`` dictionary is a mapping of all languages to the *SITEURL* of the respective (sub-)sites. This template sets the language of the current (sub-)site as active.
.. code-block:: jinja
<!-- SNIP -->
<nav><ul>
{% if lang_siteurls %}
{% for lang, url in lang_siteurls.items() %}
<li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ url }}">{{ lang }}</a></li>
{% endfor %}
<!-- separator -->
<li style="background-color: white; padding: 5px;">&nbsp</li>
{% endif %}
{% for title, link in MENUITEMS %}
<!-- SNIP -->
Tips and tricks
---------------
Showing more than language codes
................................
If you want the language buttons to show e.g. the names of the languages or flags [#flags]_, not just the language codes, you can use a jinja filter to translate the language codes
.. code-block:: python
languages_lookup = {
'en': 'English',
'cz': 'Čeština',
}
def lookup_lang_name(lang_code):
return languages_lookup[lang_code]
JINJA_FILTERS = {
...
'lookup_lang_name': lookup_lang_name,
}
And then the link content becomes
.. code-block:: jinja
<!-- SNIP -->
{% for lang, siteurl in lang_siteurls.items() %}
<li{% if lang == DEFAULT_LANG %} class="active"{% endif %}><a href="{{ siteurl }}">{{ lang | lookup_lang_name }}</a></li>
{% endfor %}
<!-- SNIP -->
Changing the order of language buttons
......................................
Because ``lang_siteurls`` and ``extra_siteurls`` are instances of ``OrderedDict`` with ``main_lang`` being always the first key, you can change the order through a jinja filter.
.. code-block:: python
def my_ordered_items(ordered_dict):
items = list(ordered_dict.items())
# swap first and last using tuple unpacking
items[0], items[-1] = items[-1], items[0]
return items
JINJA_FILTERS = {
...
'my_ordered_items': my_ordered_items,
}
And then the ``for`` loop line in the template becomes
.. code-block:: jinja
<!-- SNIP -->
{% for lang, siteurl in lang_siteurls | my_ordered_items %}
<!-- SNIP -->
.. [#flags] Although it may look nice, `w3 discourages it <http://www.w3.org/TR/i18n-html-tech-lang/#ri20040808.173208643>`_.

View File

@ -6,11 +6,15 @@ Localizing themes with Jinja2
--------------------- ---------------------
To enable the |ext| extension in your templates, you must add it to To enable the |ext| extension in your templates, you must add it to
*JINJA_EXTENSIONS* in your Pelican configuration:: *JINJA_EXTENSIONS* in your Pelican configuration
.. code-block:: python
JINJA_EXTENSIONS = ['jinja2.ext.i18n', ...] JINJA_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:: 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 %} {% trans %}translatable content{% endtrans %}
{{ gettext('a translatable string') }} {{ gettext('a translatable string') }}
@ -33,7 +37,9 @@ The domain of the translations (the name of each translation file is ``domain.mo
Example Example
....... .......
With the following in your Pelican settings file:: With the following in your Pelican settings file
.. code-block:: python
I18N_GETTEXT_LOCALEDIR = 'some/path/' I18N_GETTEXT_LOCALEDIR = 'some/path/'
I18N_GETTEXT_DOMAIN = 'my_domain' I18N_GETTEXT_DOMAIN = 'my_domain'
@ -60,7 +66,9 @@ Let's assume that you are localizing a theme in ``themes/my_theme/`` and that yo
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). 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:: 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] [jinja2: ./templates/**.html]
@ -68,7 +76,9 @@ To tell babel to extract translatable strings from the templates create a mappin
2. Extract translatable strings from templates 2. Extract translatable strings from templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Run the following command to create a ``messages.pot`` message catalog template file from extracted translatable strings:: 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 ./ pybabel extract --mapping babel.cfg --output messages.pot ./
@ -77,7 +87,9 @@ Run the following command to create a ``messages.pot`` message catalog template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to translate the template to language ``lang``, run the following command to create a message catalog 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``:: ``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 pybabel init --input-file messages.pot --output-dir translations/ --locale lang --domain messages
@ -86,7 +98,10 @@ babel expects ``lang`` to be a valid locale identifier, so if e.g. you are trans
4. Fill the message catalogs 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:: 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" msgid "just a simple string"
msgstr "jenom jednoduchý řetězec" msgstr "jenom jednoduchý řetězec"
@ -103,7 +118,9 @@ You might also want to remove ``#,fuzzy`` flags once the translation is complete
5. Compile the message catalogs 5. Compile the message catalogs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The message catalogs must be compiled into binary format using this command:: The message catalogs must be compiled into binary format using this command
.. code-block:: bash
pybabel compile --directory translations/ --domain messages pybabel compile --directory translations/ --domain messages
@ -113,7 +130,9 @@ This command might complain about "fuzzy" translations, which means you should r
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you add any translatable patterns into your templates, you have to update your message catalogs too. 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]_:: 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 pybabel update --input-file messages.pot --output-dir translations/ --domain messages