# HG changeset patch # User Goffi # Date 1495375187 -7200 # Node ID e572482f6cbd8536f10fdc8e7e9272eb66f89bc8 # Parent a81261cee29bf0441b95d04bdfb511d07d609d79 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). diff -r a81261cee29b -r e572482f6cbd setup.py --- a/setup.py Sun May 21 15:52:23 2017 +0200 +++ b/setup.py Sun May 21 15:59:47 2017 +0200 @@ -304,6 +304,6 @@ ], scripts=['frontends/src/jp/jp', 'frontends/src/primitivus/primitivus', ], zip_safe=False, - install_requires=['twisted >= 15.2.0', 'wokkel >= 0.7.1', 'progressbar', 'urwid >= 1.2.0', 'urwid-satext >= 0.6.1', 'mutagen', 'pillow', 'lxml >= 3.1.0', 'pyxdg', 'markdown', 'html2text', 'pycrypto >= 2.6.1', 'python-potr', 'PyOpenSSL', 'service_identity', 'shortuuid'], + install_requires=['twisted >= 15.2.0', 'wokkel >= 0.7.1', 'progressbar', 'urwid >= 1.2.0', 'urwid-satext >= 0.6.1', 'mutagen', 'pillow', 'lxml >= 3.1.0', 'pyxdg', 'markdown', 'html2text', 'pycrypto >= 2.6.1', 'python-potr', 'PyOpenSSL', 'service_identity', 'shortuuid', 'babel'], cmdclass={'install': CustomInstall}, ) diff -r a81261cee29b -r e572482f6cbd src/tools/common/template.py --- 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)