Browse Source

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
Ondrej Grover 10 years ago
parent
commit
6f7dafaee6

+ 14 - 8
i18n_subsites/README.rst

@@ -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

+ 11 - 7
i18n_subsites/i18n_subsites.py

@@ -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,7 +41,11 @@ 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)
+        
 
 
     
     
 def create_lang_subsites(pelican_obj):
 def create_lang_subsites(pelican_obj):
@@ -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() }
-    # 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
+    generator.context['lang_siteurls'] = _lang_siteurls
     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
     
     

+ 113 - 0
i18n_subsites/implementing_language_buttons.rst

@@ -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>`_.

+ 29 - 10
i18n_subsites/localizing_using_jinja2.rst

@@ -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,9 +130,11 @@ 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
 
 
 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.
 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.