My first thought was to store the translations as normal string with python string-formatters, something like "Hello %(username)s welcome to the site", but I quickly found some problems with that, there was no easy way to render them from the template system, they syntax is ugly, and it doesn't offer much in the way of formatting the variables. I also wanted a way to group the translations logically, so that I could query for a set of translations relevant to a specific view or app and cache them all at once, and to allow filtering in the admin section. I decided the django template engine was exactly what I was looking for, and what I came up with in the end is this:
from django.db import models
from django.core.cache import cache
class TranslationGroup(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class Meta:
verbose_name = 'Translation Group'
class Admin:
pass
class Translation(models.Model):
groups = models.ManyToManyField(TranslationGroup, filter_interface=models.HORIZONTAL)
key = models.CharField(max_length=255,db_index=True)
template = models.TextField()
description = models.TextField(blank=True)
def __unicode__(self):
return self.key
def save(self):
"""Ensures the translation cache is updated
after editing a translation
"""
super(Translation, self).save()
for c in self.groups.all():
cache.delete('trans_group_%s' % (c.name,))
class Meta:
verbose_name = 'Translation String'
class Admin:
fields = (
(None, {
'fields': ('key', 'groups', ('template', 'description'))
}),
)
list_display = ('key','template')
list_filter = ('groups',)
search_fields = ('key', 'template')
Here we have a Translation model, and a TranslationGroup model. the latter of which simply acts like a tag or label.
I also wrote a small utility method which gets all translations in a group or list of groups, compiles the templates and returns a dictionary mapping keys to compiled django templates which can be merged into a Context.
from models import *
from django.core.cache import cache
from django.template import Template
def get_strings(categories=None):
"""Returns a dictionary of translation keys
to compiled template objects for every Translation
object belonging to the categories specified.
categories -- an string, or iterable of strings for category names
"""
strings = {}
if categories:
if not isinstance(categories,(list,tuple)):
categories = (categories,)
cats = TranslationGroup.objects.select_related('translation').filter(name__in=categories)
if cats:
for c in cats:
_strings = cache.get('trans_group_%s'%(c.name,))
if not _strings:
_strings={}
for s in c.translation_set.all():
if s.key not in _strings:
_strings[s.key] = Template(s.template)
cache.set('trans_group_%s'%(c.name,), _strings,60*60*24*7) #Cache for a really long time.
strings.update(_strings)
return strings
Finally I needed a way to render these templates from within the template system, again I'm not sure if this was the best way to do this, but I created a custom template tag called trans which takes a compiled django template as its only argument and renders it in the current context. This required creating a new subclass of Node and a method which returns this new node type.
from django import template
register = template.Library()
@register.tag('trans')
def do_translation(parser,token):
try:
tag_name, tpl = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, "%s requires exactly 1 argument" % (token.contents.split()[0],)
return TranslatedNode(tpl)
class TranslatedNode(template.Node):
def __init__(self,tpl):
self.tpl = template.Variable(tpl)
def render(self,context):
try:
t = self.tpl.resolve(context)
except template.VariableDoesNotExist:
return ""
if not isinstance(t, template.Template):
return ""
return t.render(context)
Now to use it in a view you would do something like this.
from trans.models import *
from django.shortcuts import render_to_response
from trans.utils import get_strings
def my_view(request):
s = get_strings('example_group')
s.update({'username':'awes0med00d_1337'})
return render_to_response('index.html', Context(s))
and the corresponding index.html
{% load translations %}
<html>
<head>
<title>Homepage</title>
</head>
<body>
<p>{% trans greeting %}</p>
</body>
</html>
In the above example greeting is the Translation key. it was loaded because it belongs to the TranslationGroup called 'example' the load translations loads the custom tags which I've stored in templatetags/translations.py
And that's about it. Any feedback would be greatly appreciated.
No comments:
Post a Comment