Skip to content

Commit

Permalink
Implemented/overhauled template tags, plus docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
spookylukey committed Jun 10, 2018
1 parent c01f416 commit 72c551e
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 56 deletions.
6 changes: 3 additions & 3 deletions docs/api/bundle.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
====================
django_ftl.bundles
====================
=========
Bundles
=========

.. currentmodule:: django_ftl.bundles

Expand Down
9 changes: 9 additions & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
=================
API documentation
=================

.. toctree::
:maxdepth: 2

activator
bundle
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Contents:

installation
usage
api/index
contributing
authors
history
151 changes: 143 additions & 8 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ That is:
* Within that ``myapp`` directory, you can add any number of further
sub-directories, and can split your FTL files up into as many files as you
want. For the remainder of this guide we will assume a single
``myapp/main.ftl`` file.
``myapp/main.ftl`` file for each locale.


The contents of these files must be valid Fluent syntax. For the sake of this
Expand All @@ -126,9 +126,9 @@ Bundles
To use ``.ftl`` files with django-ftl, you must first define a
:class:`~django_ftl.bundles.Bundle`. They represent a collection of ``.ftl``
files that you want to use, and are responsible for finding and loading these
files. The definition of a ``Bundle`` can go anywhere in your project, but by
convention you should create a ``ftl_bundles.py`` file inside your Python
``myapp`` package, i.e. a ``myapp.ftl_bundles`` module.
files. The definition of a ``Bundle`` can go anywhere in your project, but we
recommend the convention of creating a ``ftl_bundles.py`` file inside your
Python ``myapp`` package, i.e. a ``myapp.ftl_bundles`` module.

Our ``ftl_bundles.py`` file will look like this:

Expand Down Expand Up @@ -271,9 +271,9 @@ If you do not do this, then the ``help_text`` attribute will end up having
the text translated into the default language.

To prevent this from happening, you can also pass ``require_activate=True``
parameter to :meth:`~django_ftl.bundles.Bundle.__init__`. As long as you do not
put a ``activate`` call at module level in your project, this will cause
the ``Bundle`` to raise an exception if attempt to use the ``format`` method at
parameter to :class:`~django_ftl.bundles.Bundle`. As long as you do not
put an ``activate`` call at module level in your project, this will cause the
``Bundle`` to raise an exception if attempt to use the ``format`` method at
module level.


Expand Down Expand Up @@ -309,7 +309,142 @@ your ``INSTALLED_APPS`` like this:
...
)
TODO - the rest
Put ``{% load ftl %}`` at the top of your template to load the template tag
library. It provides 3 template tags, at least two of which you will need:

``ftlconf``
~~~~~~~~~~~

This is used to set up the configuration needed by ``ftlmsg``, namely the bundle
to be used and the rendering mode. It should be used once near the top of a
template (before any translations are needed), and should be used in the
situation where most of the template will use the same bundle. For setting the
configuration for just part of a template, use ``withftl``.

The bundle is either a bundle object (passed in via the template context),
or a string that is a dotted path to a bundle.

The mode is currently limited to a single string value ``'server'``. In the
future further options will be added (to enable support for client-side
rendering/Pontoon), so it is recommended to use a context processor to add this
value into template context, so that this single context processor can be
changed in future to make use of those features.

Example:

.. code-block:: html+django

{% load ftl %}
{% ftlconf mode='server' bundle='myapp.ftl_bundles.main' %}


Example where we use a context processor to set the mode, and pass in the bundle
object from the view:

.. code-block:: python
# In settings.py
TEMPLATE = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'context_processors': [
# ...
'myapp.context_processors.ftl_mode',
]
},
}
]
.. code-block:: python
# myapp/context_processors.py
def ftl_mode(request):
return {'ftl_mode': 'server'}
.. code-block:: python
# myapp.views
from myapp.ftl_bundles import main as main_bundle
def my_view(request):
# ...
return render(request, 'myapp/mypage.html',
{'ftl_bundle': main_bundle})
.. code-block:: html+django

{# myapp/events.html #}

{% load ftl %}
{% ftlconf mode=ftl_mode bundle=ftl_bundle %}


``mode`` and ``bundle`` are both optional and can be set on different calls to
``ftlconf``, but both must be set before using ``ftlmsg``.

withftl
~~~~~~~

``withftl`` is similar to ``ftlconf`` in that its purpose is to set
configuration data for generating messages. It differs in that:

1. It sets the data only for the contained template nodes, up to a closing
``endwithftl`` node, which is required.

2. It also takes a ``language`` parameter that can be used to override the
language, in addition to the ``mode`` and ``bundle`` parameters that
``ftlconf`` take.

Multiple nested ``withftl`` tags can be used, and they can be nested into a
template that has ``ftlconf`` at the top, and their scope will be limited to the
contained template nodes as you would expect.

Example:

.. code-block:: html+django

{% load ftl %}
{% ftlconf mode='server' %}

{% withftl bundle='myapp.ftl_bundles.main' %}
{% ftlmsg 'events-title' %}
{% endwithftl %}

{% withftl bundle='myapp.ftl_bundles.other' language='fr' %}
{% ftlmsg 'other-message' %}
{% endwithftl %}


As with ``ftlconf``, the parameters do not have to be just literal strings, they
can refer to values in the context as most template tags can. You must supply
one or more of ``mode``, ``bundle`` or ``language``.

ftlmsg
~~~~~~

Finally, to actually render a message, you need to use ``ftlmsg``. It takes one
required parameter, the message ID, and any number of keyword arguments, which
correspond to the parameters you would pass in the arguments dictionary when
calling :meth:`~django_ftl.bundles.Bundle.format` in Python code.

Example:

.. code-block:: html+django

{% load ftl %}
{% ftlconf mode='server' bundle='myapp.ftl_bundles.main' %}

<body>
<h1>{% ftlmsg 'events-title' %}</h1>

<p>{% ftlmsg 'events-greeting' username=request.user.username %}</p>
</body>



.. _setting-user-language:
Expand Down
95 changes: 79 additions & 16 deletions src/django_ftl/templatetags/ftl.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,110 @@
from __future__ import absolute_import, print_function, unicode_literals

import contextlib
import six

from django import template
from django.template.base import token_kwargs
from django.utils.module_loading import import_string

import django_ftl

register = template.Library()

MODE_SERVER = 'server'
MODES = [
MODE_SERVER
]


@register.simple_tag
def ftl(mode, bundle_id, message_id, **kwargs):
bundle = import_string(bundle_id)
validate_mode(mode)
return do_render(mode, bundle, message_id, kwargs)
_MODE_VAR_NAME = '__ftl_mode'
_BUNDLE_VAR_NAME = '__ftl_bundle'


@register.simple_tag(takes_context=True)
def ftl_conf(context, mode=None, bundle=None):
def ftlconf(context, mode=None, bundle=None):
if mode is not None:
validate_mode(mode)
context['__ftl_mode'] = mode
context[_MODE_VAR_NAME] = mode
if bundle is not None:
context['__ftl_bundle'] = import_string(bundle)
context[_BUNDLE_VAR_NAME] = resolve_bundle(bundle)
return ''


@register.simple_tag(takes_context=True)
def ftl_message(context, message_id, **kwargs):
return do_render(context['__ftl_mode'],
context['__ftl_bundle'],
message_id,
kwargs)
def resolve_bundle(bundle):
if isinstance(bundle, six.text_type):
return import_string(bundle)
else:
return bundle


def do_render(mode, bundle, message_id, kwargs):
@register.simple_tag(takes_context=True)
def ftlmsg(context, message_id, **kwargs):
try:
mode = context[_MODE_VAR_NAME]
except KeyError:
raise ValueError("No mode set for ftl - using ftlconf/withftl have been used to set mode")
try:
bundle = context[_BUNDLE_VAR_NAME]
except KeyError:
raise ValueError("No bundle set for ftl - using ftlconf/withftl have been used to set bundle")
if mode == MODE_SERVER:
return bundle.format(message_id, kwargs)
raise AssertionError("Not reached")


def validate_mode(mode):
if mode not in MODES:
raise ValueError("mode '{0}' not understood, must be one of {2}"
.format(mode, MODES))


class WithFtlNode(template.Node):
def __init__(self, nodelist, language=None, mode=None, bundle=None):
self.nodelist = nodelist
self.language = language
self.mode = mode
self.bundle = bundle

def __repr__(self):
return '<%s>' % self.__class__.__name__

def render(self, context):
language = None if self.language is None else self.language.resolve(context)
mode = None if self.mode is None else self.mode.resolve(context)
bundle = None if self.bundle is None else resolve_bundle(self.bundle.resolve(context))
new_context = {}
if mode is not None:
new_context[_MODE_VAR_NAME] = mode
if bundle is not None:
new_context[_BUNDLE_VAR_NAME] = bundle
if language is not None:
lang_ctx = django_ftl.override(language)
else:
lang_ctx = null_ctx()

with context.push(**new_context):
with lang_ctx:
return self.nodelist.render(context)


@register.tag('withftl')
def withftl(parser, token):
conf = token_kwargs(token.split_contents()[1:], parser, support_legacy=False)
language = conf.pop('language', None)
mode = conf.pop('mode', None)
bundle = conf.pop('bundle', None)
if conf:
raise ValueError("withftl tag received unexpected keyword arguments: {0}"
.format(", ".join(conf.keys())))
nodelist = parser.parse(('endwithftl',))
parser.delete_first_token()

return WithFtlNode(nodelist,
language=language,
mode=mode,
bundle=bundle)


@contextlib.contextmanager
def null_ctx():
yield
1 change: 1 addition & 0 deletions tests/locales/en/tests/other.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
other-message = Other-message
1 change: 1 addition & 0 deletions tests/locales/tr/tests/other.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
other-message = Başka-mesaj

0 comments on commit 72c551e

Please sign in to comment.