Mercurial > libervia-backend
comparison 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 |
comparison
equal
deleted
inserted
replaced
2248:a81261cee29b | 2249:e572482f6cbd |
---|---|
26 log = getLogger(__name__) | 26 log = getLogger(__name__) |
27 import os.path | 27 import os.path |
28 from collections import OrderedDict | 28 from collections import OrderedDict |
29 from xml.sax.saxutils import quoteattr | 29 from xml.sax.saxutils import quoteattr |
30 import time | 30 import time |
31 from babel import support | |
32 from babel import Locale | |
33 from babel.core import UnknownLocaleError | |
31 try: | 34 try: |
32 import sat_templates | 35 import sat_templates |
33 except ImportError: | 36 except ImportError: |
34 raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine') | 37 raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine') |
35 else: | 38 else: |
39 import jinja2 | 42 import jinja2 |
40 except: | 43 except: |
41 raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2') | 44 raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2') |
42 | 45 |
43 HTML_EXT = ('html', 'xhtml') | 46 HTML_EXT = ('html', 'xhtml') |
47 DEFAULT_LOCALE = u'en' | |
44 # TODO: handle external path (an additional search path for templates should be settable by user | 48 # TODO: handle external path (an additional search path for templates should be settable by user |
45 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason | 49 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason |
46 | 50 |
47 | 51 |
48 class TemplateLoader(jinja2.FileSystemLoader): | 52 class TemplateLoader(jinja2.FileSystemLoader): |
170 self.env = jinja2.Environment( | 174 self.env = jinja2.Environment( |
171 loader=TemplateLoader(), | 175 loader=TemplateLoader(), |
172 autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']), | 176 autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']), |
173 trim_blocks=True, | 177 trim_blocks=True, |
174 lstrip_blocks=True, | 178 lstrip_blocks=True, |
179 extensions=['jinja2.ext.i18n'], | |
175 ) | 180 ) |
181 self._locale_str = DEFAULT_LOCALE | |
182 self._locale = Locale.parse(self._locale_str) | |
183 self.installTranslations() | |
176 # we want to have access to SàT constants in templates | 184 # we want to have access to SàT constants in templates |
177 self.env.globals[u'C'] = C | 185 self.env.globals[u'C'] = C |
178 # custom filters | 186 # custom filters |
179 self.env.filters['next_gidx'] = self._next_gidx | 187 self.env.filters['next_gidx'] = self._next_gidx |
180 self.env.filters['cur_gidx'] = self._cur_gidx | 188 self.env.filters['cur_gidx'] = self._cur_gidx |
181 self.env.filters['blog_date'] = self._blog_date | 189 self.env.filters['blog_date'] = self._blog_date |
190 | |
191 def installTranslations(self): | |
192 i18n_dir = os.path.join(self.base_dir, 'i18n') | |
193 self.translations = {} | |
194 for lang_dir in os.listdir(i18n_dir): | |
195 lang_path = os.path.join(i18n_dir, lang_dir) | |
196 if not os.path.isdir(lang_path): | |
197 continue | |
198 po_path = os.path.join(lang_path, 'LC_MESSAGES/sat.mo') | |
199 try: | |
200 with open(po_path, 'rb') as f: | |
201 self.translations[Locale.parse(lang_dir)] = support.Translations(f, 'sat') | |
202 except EnvironmentError: | |
203 log.error(_(u"Can't find template translation at {path}").format(path = po_path)) | |
204 except UnknownLocaleError as e: | |
205 log.error(_(u"Invalid locale name: {msg}").format(msg=e)) | |
206 else: | |
207 log.info(_(u'loaded {lang} templates translations').format(lang=lang_dir)) | |
208 self.env.install_null_translations(True) | |
209 | |
210 def setLocale(self, locale_str): | |
211 if locale_str == self._locale_str: | |
212 return | |
213 locale = Locale.parse(locale_str) | |
214 locale_str = unicode(locale) | |
215 if locale_str != DEFAULT_LOCALE: | |
216 try: | |
217 translations = self.translations[locale] | |
218 except KeyError: | |
219 log.warning(_(u"Can't find locale {locale}".format(locale=locale))) | |
220 locale_str = DEFAULT_LOCALE | |
221 locale = Locale.parse(self._locale_str) | |
222 else: | |
223 self.env.install_gettext_translations(translations, True) | |
224 log.debug(_(u'Switched to {lang}').format(lang=locale.english_name)) | |
225 | |
226 if locale_str == DEFAULT_LOCALE: | |
227 self.env.install_null_translations(True) | |
228 | |
229 self._locale = locale | |
230 self._locale_str = locale_str | |
182 | 231 |
183 def getThemeAndRoot(self, template): | 232 def getThemeAndRoot(self, template): |
184 """retrieve theme and root dir of a given tempalte | 233 """retrieve theme and root dir of a given tempalte |
185 | 234 |
186 @param template(unicode): template to parse | 235 @param template(unicode): template to parse |
263 | 312 |
264 def _blog_date(self, timestamp): | 313 def _blog_date(self, timestamp): |
265 # FIXME: Q&D, need to be done properly | 314 # FIXME: Q&D, need to be done properly |
266 return unicode(int(time.time() - int(timestamp))/(3600*24)) + u" days ago" | 315 return unicode(int(time.time() - int(timestamp))/(3600*24)) + u" days ago" |
267 | 316 |
268 def render(self, template, theme=None, root_path=u'', css_files=None, css_inline=False, **kwargs): | 317 def render(self, template, theme=None, locale=DEFAULT_LOCALE, root_path=u'', css_files=None, css_inline=False, **kwargs): |
269 """render a template | 318 """render a template |
270 | 319 |
271 @param template(unicode): template to render (e.g. blog/articles.html) | 320 @param template(unicode): template to render (e.g. blog/articles.html) |
272 @param theme(unicode): template theme | 321 @param theme(unicode): template theme |
273 @param root_path(unicode): prefix of the path/URL to use for template root | 322 @param root_path(unicode): prefix of the path/URL to use for template root |
307 css_contents.append(f.read()) | 356 css_contents.append(f.read()) |
308 if css_contents: | 357 if css_contents: |
309 kwargs['css_content'] = '\n'.join(css_contents) | 358 kwargs['css_content'] = '\n'.join(css_contents) |
310 | 359 |
311 scripts_handler = ScriptsHandler(self, template_path, template_root_dir) | 360 scripts_handler = ScriptsHandler(self, template_path, template_root_dir) |
361 self.setLocale(locale) | |
312 # XXX: theme used in template arguments is the requested theme, which may differ from actual theme | 362 # XXX: theme used in template arguments is the requested theme, which may differ from actual theme |
313 # if the template doesn't exist in the requested theme. | 363 # if the template doesn't exist in the requested theme. |
314 return template_source.render(theme=theme, root_path=root_path, css_files=css_files, gidx=Indexer(), scripts_handler=scripts_handler, **kwargs) | 364 return template_source.render(theme=theme, root_path=root_path, css_files=css_files, locale=locale, gidx=Indexer(), scripts_handler=scripts_handler, **kwargs) |