i18n_subsites.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. """i18n_subsites plugin creates i18n-ized subsites of the default site"""
  2. import os
  3. import six
  4. import logging
  5. from itertools import chain
  6. from collections import defaultdict, OrderedDict
  7. import gettext
  8. from pelican import signals
  9. from pelican.contents import Page, Article
  10. from pelican.settings import configure_settings
  11. from ._regenerate_context_helpers import regenerate_context_articles
  12. # Global vars
  13. _main_site_generated = False
  14. _main_site_lang = "en"
  15. _main_siteurl = ''
  16. _lang_siteurls = None
  17. logger = logging.getLogger(__name__)
  18. def disable_lang_vars(pelican_obj):
  19. """Set lang specific url and save_as vars to the non-lang defaults
  20. e.g. ARTICLE_LANG_URL = ARTICLE_URL
  21. They would conflict with this plugin otherwise
  22. """
  23. global _main_site_lang, _main_siteurl, _lang_siteurls
  24. s = pelican_obj.settings
  25. for content in ['ARTICLE', 'PAGE']:
  26. for meta in ['_URL', '_SAVE_AS']:
  27. s[content + '_LANG' + meta] = s[content + meta]
  28. if not _main_site_generated:
  29. _main_site_lang = s['DEFAULT_LANG']
  30. _main_siteurl = s['SITEURL']
  31. _lang_siteurls = [(lang, _main_siteurl + '/' + lang) for lang in s.get('I18N_SUBSITES', {}).keys()]
  32. # To be able to use url for main site root when SITEURL == '' (e.g. when developing)
  33. _lang_siteurls = [(_main_site_lang, ('/' if _main_siteurl == '' else _main_siteurl))] + _lang_siteurls
  34. _lang_siteurls = OrderedDict(_lang_siteurls)
  35. def create_lang_subsites(pelican_obj):
  36. """For each language create a subsite using the lang-specific config
  37. for each generated lang append language subpath to SITEURL and OUTPUT_PATH
  38. and set DEFAULT_LANG to the language code to change perception of what is translated
  39. and set DELETE_OUTPUT_DIRECTORY to False to prevent deleting output from previous runs
  40. Then generate the subsite using a PELICAN_CLASS instance and its run method.
  41. """
  42. global _main_site_generated
  43. if _main_site_generated: # make sure this is only called once
  44. return
  45. else:
  46. _main_site_generated = True
  47. orig_settings = pelican_obj.settings
  48. for lang, overrides in orig_settings.get('I18N_SUBSITES', {}).items():
  49. settings = orig_settings.copy()
  50. settings.update(overrides)
  51. settings['SITEURL'] = _lang_siteurls[lang]
  52. settings['OUTPUT_PATH'] = os.path.join(orig_settings['OUTPUT_PATH'], lang, '')
  53. settings['DEFAULT_LANG'] = lang # to change what is perceived as translations
  54. settings['DELETE_OUTPUT_DIRECTORY'] = False # prevent deletion of previous runs
  55. settings = configure_settings(settings) # to set LOCALE, etc.
  56. cls = settings['PELICAN_CLASS']
  57. if isinstance(cls, six.string_types):
  58. module, cls_name = cls.rsplit('.', 1)
  59. module = __import__(module)
  60. cls = getattr(module, cls_name)
  61. pelican_obj = cls(settings)
  62. logger.debug("Generating i18n subsite for lang '{}' using class '{}'".format(lang, str(cls)))
  63. pelican_obj.run()
  64. _main_site_generated = False # for autoreload mode
  65. def move_translations_links(content_object):
  66. """This function points translations links to the sub-sites
  67. by prepending their location with the language code
  68. or directs an original DEFAULT_LANG translation back to top level site
  69. """
  70. for translation in content_object.translations:
  71. if translation.lang == _main_site_lang:
  72. # cannot prepend, must take to top level
  73. lang_prepend = '../'
  74. else:
  75. lang_prepend = translation.lang + '/'
  76. translation.override_url = lang_prepend + translation.url
  77. def update_generator_contents(generator, *args):
  78. """Update the contents lists of a generator
  79. Empty the (hidden_)translation attribute of article and pages generators
  80. to prevent generating the translations as they will be generated in the lang sub-site
  81. and point the content translations links to the sub-sites
  82. Hide content without a translation for current DEFAULT_LANG
  83. if HIDE_UNTRANSLATED_CONTENT is True
  84. """
  85. generator.translations = []
  86. is_pages_gen = hasattr(generator, 'pages')
  87. if is_pages_gen:
  88. generator.hidden_translations = []
  89. for page in chain(generator.pages, generator.hidden_pages):
  90. move_translations_links(page)
  91. else: # is an article generator
  92. for article in chain(generator.articles, generator.drafts):
  93. move_translations_links(article)
  94. if not generator.settings.get('HIDE_UNTRANSLATED_CONTENT', True):
  95. return
  96. contents = generator.pages if is_pages_gen else generator.articles
  97. hidden_contents = generator.hidden_pages if is_pages_gen else generator.drafts
  98. default_lang = generator.settings['DEFAULT_LANG']
  99. for content_object in contents[:]: # loop over copy for removing
  100. if content_object.lang != default_lang:
  101. if isinstance(content_object, Article):
  102. content_object.status = 'draft'
  103. elif isinstance(content_object, Page):
  104. content_object.status = 'hidden'
  105. contents.remove(content_object)
  106. hidden_contents.append(content_object)
  107. if not is_pages_gen: # regenerate categories, tags, etc. for articles
  108. if hasattr(generator, '_generate_context_aggregate'): # if implemented
  109. # Simulate __init__ for fields that need it
  110. generator.dates = {}
  111. generator.tags = defaultdict(list)
  112. generator.categories = defaultdict(list)
  113. generator.authors = defaultdict(list)
  114. generator._generate_context_aggregate()
  115. else: # fallback for Pelican 3.3.0
  116. regenerate_context_articles(generator)
  117. def install_templates_translations(generator):
  118. """Install gettext translations for current DEFAULT_LANG in the jinja2.Environment
  119. if the 'jinja2.ext.i18n' jinja2 extension is enabled
  120. adds some useful variables into the template context
  121. """
  122. generator.context['main_siteurl'] = _main_siteurl
  123. generator.context['main_lang'] = _main_site_lang
  124. generator.context['lang_siteurls'] = _lang_siteurls
  125. current_def_lang = generator.settings['DEFAULT_LANG']
  126. extra_siteurls = _lang_siteurls.copy()
  127. extra_siteurls.pop(current_def_lang)
  128. generator.context['extra_siteurls'] = extra_siteurls
  129. if 'jinja2.ext.i18n' not in generator.settings['JINJA_EXTENSIONS']:
  130. return
  131. domain = generator.settings.get('I18N_GETTEXT_DOMAIN', 'messages')
  132. localedir = generator.settings.get('I18N_GETTEXT_LOCALEDIR')
  133. if localedir is None:
  134. localedir = os.path.join(generator.theme, 'translations')
  135. if current_def_lang == generator.settings.get('I18N_TEMPLATES_LANG', _main_site_lang):
  136. translations = gettext.NullTranslations()
  137. else:
  138. languages = [current_def_lang]
  139. try:
  140. translations = gettext.translation(domain, localedir, languages)
  141. except (IOError, OSError):
  142. logger.error("Cannot find translations for language '{}' in '{}' with domain '{}'. Installing NullTranslations.".format(languages[0], localedir, domain))
  143. translations = gettext.NullTranslations()
  144. newstyle = generator.settings.get('I18N_GETTEXT_NEWSTYLE', True)
  145. generator.env.install_gettext_translations(translations, newstyle)
  146. def register():
  147. signals.initialized.connect(disable_lang_vars)
  148. signals.generator_init.connect(install_templates_translations)
  149. signals.article_generator_finalized.connect(update_generator_contents)
  150. signals.page_generator_finalized.connect(update_generator_contents)
  151. signals.finalized.connect(create_lang_subsites)