diff src/tools/common/template.py @ 2249:e572482f6cbd

core (tools/common/template): i18n support - babel has been added as a new dependencies, and should replace gettext in core in the future - added i18n support in template rendered - current locale is available as babel Locale in templates through "locale" variable - locales can be changed before rendering using setLocale - for now, all locales translations are loaded on init, and stay in cache. A more complex cache system may be needed in the future (e.g. keeping only most used and load others from files when needed).
author Goffi <goffi@goffi.org>
date Sun, 21 May 2017 15:59:47 +0200
parents e09048cb7595
children 322694543225
line wrap: on
line diff
--- a/src/tools/common/template.py	Sun May 21 15:52:23 2017 +0200
+++ b/src/tools/common/template.py	Sun May 21 15:59:47 2017 +0200
@@ -28,6 +28,9 @@
 from collections import OrderedDict
 from xml.sax.saxutils import quoteattr
 import time
+from babel import support
+from babel import Locale
+from babel.core import UnknownLocaleError
 try:
     import sat_templates
 except ImportError:
@@ -41,6 +44,7 @@
     raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2')
 
 HTML_EXT = ('html', 'xhtml')
+DEFAULT_LOCALE = u'en'
 # TODO: handle external path (an additional search path for templates should be settable by user
 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason
 
@@ -172,7 +176,11 @@
             autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']),
             trim_blocks=True,
             lstrip_blocks=True,
+            extensions=['jinja2.ext.i18n'],
             )
+        self._locale_str = DEFAULT_LOCALE
+        self._locale = Locale.parse(self._locale_str)
+        self.installTranslations()
         # we want to have access to SàT constants in templates
         self.env.globals[u'C'] = C
         # custom filters
@@ -180,6 +188,47 @@
         self.env.filters['cur_gidx'] = self._cur_gidx
         self.env.filters['blog_date'] = self._blog_date
 
+    def installTranslations(self):
+        i18n_dir = os.path.join(self.base_dir, 'i18n')
+        self.translations = {}
+        for lang_dir in os.listdir(i18n_dir):
+            lang_path = os.path.join(i18n_dir, lang_dir)
+            if not os.path.isdir(lang_path):
+                continue
+            po_path = os.path.join(lang_path, 'LC_MESSAGES/sat.mo')
+            try:
+                with open(po_path, 'rb') as f:
+                    self.translations[Locale.parse(lang_dir)] = support.Translations(f, 'sat')
+            except EnvironmentError:
+                log.error(_(u"Can't find template translation at {path}").format(path = po_path))
+            except UnknownLocaleError as e:
+                log.error(_(u"Invalid locale name: {msg}").format(msg=e))
+            else:
+                log.info(_(u'loaded {lang} templates translations').format(lang=lang_dir))
+        self.env.install_null_translations(True)
+
+    def setLocale(self, locale_str):
+        if locale_str == self._locale_str:
+            return
+        locale = Locale.parse(locale_str)
+        locale_str = unicode(locale)
+        if locale_str != DEFAULT_LOCALE:
+            try:
+                translations = self.translations[locale]
+            except KeyError:
+                log.warning(_(u"Can't find locale {locale}".format(locale=locale)))
+                locale_str = DEFAULT_LOCALE
+                locale = Locale.parse(self._locale_str)
+            else:
+                self.env.install_gettext_translations(translations, True)
+                log.debug(_(u'Switched to {lang}').format(lang=locale.english_name))
+
+        if locale_str == DEFAULT_LOCALE:
+            self.env.install_null_translations(True)
+
+        self._locale = locale
+        self._locale_str = locale_str
+
     def getThemeAndRoot(self, template):
         """retrieve theme and root dir of a given tempalte
 
@@ -265,7 +314,7 @@
         # FIXME: Q&D, need to be done properly
         return unicode(int(time.time() - int(timestamp))/(3600*24)) + u" days ago"
 
-    def render(self, template, theme=None, root_path=u'', css_files=None, css_inline=False, **kwargs):
+    def render(self, template, theme=None, locale=DEFAULT_LOCALE, root_path=u'', css_files=None, css_inline=False, **kwargs):
         """render a template
 
         @param template(unicode): template to render (e.g. blog/articles.html)
@@ -309,6 +358,7 @@
                 kwargs['css_content'] = '\n'.join(css_contents)
 
         scripts_handler = ScriptsHandler(self, template_path, template_root_dir)
+        self.setLocale(locale)
         # XXX: theme used in template arguments is the requested theme, which may differ from actual theme
         #      if the template doesn't exist in the requested theme.
-        return template_source.render(theme=theme, root_path=root_path, css_files=css_files, gidx=Indexer(), scripts_handler=scripts_handler, **kwargs)
+        return template_source.render(theme=theme, root_path=root_path, css_files=css_files, locale=locale, gidx=Indexer(), scripts_handler=scripts_handler, **kwargs)