i18n_subsites.py 7.3 KB

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