i18n_subsites.py 7.4 KB

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