Skip to content

Commit

Permalink
Redirect the native chameleon i18n support through Nikola's language …
Browse files Browse the repository at this point in the history
…dict. This is prettier and.
  • Loading branch information
jamadden committed Oct 9, 2017
1 parent c46ecee commit d5d8845
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 19 deletions.
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
0.0.1a2 (unreleased)
====================

- Nothing changed yet.
- Map the Nikola ``messages`` function onto the native ``i18n``
functionality of Chamleon. Attributes like ``i18n:translate`` are
now preferred to explicit calls to ``options/messages`` when
possible.


0.0.1a1 (2017-10-09)
Expand Down
13 changes: 13 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,17 @@
API Reference
===============

.. caution:: Unless otherwise explicitly documented, the objects
referenced here are documented solely for exploration
purposes and do not feature stable APIs for public use.

Interfaces
==========

.. automodule:: nti.nikola_chameleon.interfaces


Templating
==========

.. automodule:: nti.nikola_chameleon.template
40 changes: 29 additions & 11 deletions src/nti/nikola_chameleon/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from nti.nikola_chameleon import interfaces
from .request import Request
from .interfaces import ITemplate
from .template import ViewPageTemplateFileWithLoad
from .template import NikolaPageFileTemplate
from .template import TemplateFactory

logger = __import__('logging').getLogger(__name__)
Expand All @@ -46,6 +46,11 @@ class Context(object):

class ChameleonTemplates(TemplateSystem):

# pylint:disable=abstract-method
# get_deps, get_string_deps, and get_template_path
# don't seem to be called? Leave them as unimplemented.


name = 'nti.nikola_chameleon'

def __init__(self):
Expand Down Expand Up @@ -87,6 +92,7 @@ def set_directories(self, directories, cache_folder):
template_mod = dottedname.resolve('chameleon.template')
if template_mod.CACHE_DIRECTORY != conf_mod.CACHE_DIRECTORY:
template_mod.CACHE_DIRECTORY = conf_mod.CACHE_DIRECTORY
# pylint:disable=protected-access
template_mod.BaseTemplate.loader = template_mod._make_module_loader()

# Creating these guys with debug or autoreload, as Pyramid does when its
Expand All @@ -100,13 +106,27 @@ def template_deps(self, template_name):
# when changed, may affect the template's output.
# usually this involves template inheritance and
# inclusion.
#
# XXX: I don't know how to implement this for Chameleon,
# especially not if we enable macros.
return []

get_deps = template_deps
get_string_deps = template_deps
# Of the three methods that provide dependencies, this
# seems to be the only one that gets called.

# Just the name isn't very much to go on, because we can
# potentially be rendering different things based on the
# kind of context we have, if that's customized in theme.zcml.
self._provide_templates()
try:
template = component.getMultiAdapter((object(), object()),
ITemplate,
name=template_name)
except LookupError:
return []

# This doesn't really get us very far, because template
# inclusion/extension is implemented via macros and traversal,
# which aren't directly recorded in the source. But at least we can
# get direct template file modifications.
return [template.filename]


def render_template(self, template_name, output_name, context):
"""Renders template to a file using context.
Expand Down Expand Up @@ -159,10 +179,8 @@ def render_template_to_string(self, template, context):
# Make the context available.
# zope.browserpage, assumes it's on the view object.
# chameleon/z3c.pt will use the kwargs

options['context'] = context
# The ViewPageTemplate likes to have a request. We provide
# a new object so that it can have access to the context variables.

return template(view, request=request, **options)

def inject_directory(self, directory):
Expand Down Expand Up @@ -197,7 +215,7 @@ def _provide_templates_from_directory(self, directory):
# file. This doesn't deal with naming conflicts.
gsm = component.getGlobalSiteManager()
for macro_file in sorted(glob.glob(os.path.join(directory, "*.macro.pt"))):
template = ViewPageTemplateFileWithLoad(macro_file)
template = NikolaPageFileTemplate(macro_file)
for name in template.macros.names:
factory = MacroFactory(macro_file, name, 'text/html')
if name in seen_macros:
Expand Down
71 changes: 64 additions & 7 deletions src/nti/nikola_chameleon/template.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
The Chameleon ZPT plugin for nikola.
Templating support for Chameleon under Nikola.
"""
from __future__ import absolute_import
Expand All @@ -17,11 +17,20 @@
import zope.browserpage.viewpagetemplatefile
import zope.pagetemplate.pagetemplatefile

from nikola.utils import LocaleBorg

logger = __import__('logging').getLogger(__name__)

class ViewPageTemplateFileWithLoad(ViewPageTemplateFile):
class NikolaPageFileTemplate(ViewPageTemplateFile):
"""
Enables the load: expression type for convenience.
ZPT file templates for use with Nikola.
This does a few things:
1. Sets up the ``load:`` expression to work.
2. Provides a ``translate`` function based on Nikola's
``messages`` dictionary that will enable Chameleon's default
I18N support.
"""
# NOTE: We cannot do the rational thing and copy this
# and modify our local value. This is because
Expand All @@ -32,14 +41,35 @@ class ViewPageTemplateFileWithLoad(ViewPageTemplateFile):

@property
def builtins(self):
d = super(ViewPageTemplateFileWithLoad, self).builtins
d = super(NikolaPageFileTemplate, self).builtins
d['__loader'] = self._loader
return d

def _pt_get_context(self, instance, request, kwargs): # pylint:disable=arguments-differ
context = super(NikolaPageFileTemplate, self)._pt_get_context(
instance, request, kwargs)
# Set up translation
context['translate'] = MessagesTranslate(kwargs['messages'])
return context

def render(self, target_language=None, **context):
# We bypass BaseTemplate.render because it wants to setup
# a bunch of translation stuff that's only applicable to
# zope.i18n, and it overrides the translate function we pass
# in which came from Nikola.
assert 'request' in context
assert 'translate' in context
if target_language is None:
target_language = LocaleBorg().current_lang

context['target_language'] = target_language
# pylint:disable=bad-super-call
return super(BaseTemplate, self).render(**context)

BaseTemplate.expression_types['structure'] = PageTemplateFile.expression_types['structure']
BaseTemplate.expression_types['load'] = PageTemplateFile.expression_types['load']
z3c.macro.zcml.ViewPageTemplateFile = ViewPageTemplateFileWithLoad
zope.browserpage.simpleviewclass.ViewPageTemplateFile = ViewPageTemplateFileWithLoad
z3c.macro.zcml.ViewPageTemplateFile = NikolaPageFileTemplate
zope.browserpage.simpleviewclass.ViewPageTemplateFile = NikolaPageFileTemplate


class TemplateFactory(object):
Expand All @@ -52,7 +82,7 @@ def __init__(self, path):
# running time.
# Using z.p.p.PTF seems to disable caching.
#self.template = zope.browserpage.viewpagetemplatefile.ViewPageTemplateFile(self.path)
self.template = ViewPageTemplateFileWithLoad(self.path)
self.template = NikolaPageFileTemplate(self.path)

def __call__(self, context, request):
return self.template
Expand All @@ -61,3 +91,30 @@ def __call__(self, context, request):
# real namespace object.
# See https://github.com/zopefoundation/z3c.pt/issues/3
z3c.pt.namespaces.function_namespaces = zope.pagetemplate.engine.Engine.namespaces

class MessagesTranslate(object):
"""
Implements the `translation function`_ for Chameleon
in terms of the ``messages`` object provided by Nikola.
The Nikola messages object is a dictionary from language to a dictionary
of message ids to strings. Note that it doesn't support domains or
mapping substitutions (mappings are included in the msgid as a
format string, but nikola itself is expected to do the formatting).
If it is called, it accepts the message id key as the first
argument, and a 'lang' parameter as the optional second argument.
If the lang is not given or is None, then it is defaulted to the
"current language".
.. versionadded:: 0.0.1a2
.. _translation function: https://chameleon.readthedocs.io/en/latest/reference.html#translation-function
"""

def __init__(self, messages):
self._messages = messages

def __call__(self, msgid, domain=None, mapping=None, context=None,
target_language=None, default=None):
return self._messages(msgid, target_language)

0 comments on commit d5d8845

Please sign in to comment.