i18n_subsites.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. def move_translations_links(content_object):
  58. """This function points translations links to the sub-sites
  59. by prepending their location with the language code
  60. or directs an original DEFAULT_LANG translation back to top level site
  61. """
  62. for translation in content_object.translations:
  63. if translation.lang == _main_site_lang:
  64. # cannot prepend, must take to top level
  65. lang_prepend = '../'
  66. else:
  67. lang_prepend = translation.lang + '/'
  68. translation.override_url = lang_prepend + translation.url
  69. def update_generator_contents(generator, *args):
  70. """Update the contents lists of a generator
  71. Empty the (hidden_)translation attribute of article and pages generators
  72. to prevent generating the translations as they will be generated in the lang sub-site
  73. and point the content translations links to the sub-sites
  74. Hide content without a translation for current DEFAULT_LANG
  75. if HIDE_UNTRANSLATED_CONTENT is True
  76. """
  77. generator.translations = []
  78. is_pages_gen = hasattr(generator, 'pages')
  79. if is_pages_gen:
  80. generator.hidden_translations = []
  81. for page in chain(generator.pages, generator.hidden_pages):
  82. move_translations_links(page)
  83. else: # is an article generator
  84. for article in chain(generator.articles, generator.drafts):
  85. move_translations_links(article)
  86. if not generator.settings.get('HIDE_UNTRANSLATED_CONTENT', True):
  87. return
  88. contents = generator.pages if is_pages_gen else generator.articles
  89. hidden_contents = generator.hidden_pages if is_pages_gen else generator.drafts
  90. default_lang = generator.settings['DEFAULT_LANG']
  91. for content_object in contents:
  92. if content_object.lang != default_lang:
  93. if isinstance(content_object, Page):
  94. content_object.status = 'hidden'
  95. elif isinstance(content_object, Article):
  96. content_object.status = 'draft'
  97. contents.remove(content_object)
  98. hidden_contents.append(content_object)
  99. if not is_pages_gen: # regenerate categories, tags, etc. for articles
  100. if hasattr(generator, '_generate_context_aggregate'): # if implemented
  101. # Simulate __init__ for fields that need it
  102. generator.dates = {}
  103. generator.tags = defaultdict(list)
  104. generator.categories = defaultdict(list)
  105. generator.authors = defaultdict(list)
  106. generator._generate_context_aggregate()
  107. else: # fallback for Pelican 3.3.0
  108. regenerate_context_articles(generator)
  109. def install_templates_translations(generator):
  110. """Install gettext translations for current DEFAULT_LANG in the jinja2.Environment
  111. if the 'jinja2.ext.i18n' jinja2 extension is enabled
  112. adds some useful variables into the template context
  113. """
  114. generator.context['main_siteurl'] = _main_siteurl
  115. generator.context['main_lang'] = _main_site_lang
  116. extra_siteurls = { lang: _main_siteurl + '/' + lang for lang in generator.settings.get('I18N_SUBSITES', {}).keys() }
  117. extra_siteurls[_main_site_lang] = _main_siteurl
  118. extra_siteurls.pop(generator.settings['DEFAULT_LANG'])
  119. generator.context['extra_siteurls'] = extra_siteurls
  120. if 'jinja2.ext.i18n' not in generator.settings['JINJA_EXTENSIONS']:
  121. return
  122. domain = generator.settings.get('I18N_GETTEXT_DOMAIN', 'messages')
  123. localedir = generator.settings.get('I18N_GETTEXT_LOCALEDIR')
  124. if localedir is None:
  125. localedir = os.path.join(generator.theme, 'translations/')
  126. languages = [generator.settings['DEFAULT_LANG']]
  127. try:
  128. translations = gettext.translation(domain, localedir, languages)
  129. except (IOError, OSError):
  130. logger.error("Cannot find translations for language '{}' in '{}' with domain '{}'. Installing NullTranslations.".format(languages[0], localedir, domain))
  131. translations = gettext.NullTranslations()
  132. newstyle = generator.settings.get('I18N_GETTEXT_NEWSTYLE', True)
  133. generator.env.install_gettext_translations(translations, newstyle)
  134. def register():
  135. signals.initialized.connect(disable_lang_vars)
  136. signals.generator_init.connect(install_templates_translations)
  137. signals.article_generator_finalized.connect(update_generator_contents)
  138. signals.page_generator_finalized.connect(update_generator_contents)
  139. signals.finalized.connect(create_lang_subsites)