A south-compatible suite of django fields that make it easy to manage multiple translations of text-based content (including files/images).
0.3
Installation is easy with pip:
$ pip install django-multilingualfielddjango-multilingualfield will install the following dependencies:
django-classy-tags>= 0.3.4.1lxml>= 3.1.2
To use django-multilingualfield, first add multilingualfield to INSTALLED_APPS:
INSTALLED_APPS += ('multilingualfield')Secondly, make sure that LANGUAGES is properly defined in your settings file.
If you don't have LANGUAGE_CODE set in your settings file it will default to 'en-us' (U.S. English). It is recommended you manually set LANGUAGE_CODE (even if you are keeping the default value of 'en-us') IN ADDITION TO adding an entry for that language code (as the first language) in LANGUAGES. Here's an example:
LANGUAGE_CODE = 'en'
LANGUAGES = [
('en', 'English'),
('es', 'Español') # See note below note
]
ñis the UTF8 encoding for 'ñ'
If you'd like to use MultiLingual* fields in templates you'll need django's 'django.middleware.locale.LocalMiddleware' added to your MIDDLEWARE_CLASSES setting and 'django.core.context_processors.i18n' added to your TEMPLATE_CONTEXT_PROCESSORS setting:
MIDDLEWARE_CLASSES += (
'django.middleware.locale.LocaleMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.i18n',
)django-multilingualfield uses
django.utils.translation.get_languageto determine which translation to serve by default. To better understand how Django determines language preference read the aptly titled 'How Django discovers language preference' section from the i18n topic page within the official django documentation.
django has excellent translation tools but a recent project at WGBH required manually-written translations for nearly all text & image content served by the site.
I didn't want to create multiple CharField, TextField or ImageField attributes for each field that needed translations (i.e. 'title_en' and 'title_es') for multiple reasons:
- They'd be a giant pain to keep track of.
- Templates and/or views would be polluted with alot of crufty if/else statements.
- The site needed to launch with support for English and Spanish but I figured new languages would be added down the road and wanted to make any future additions as smooth as possible.
django-multilingualfield contains three fields ready-for-use in your django project.
multilingualfield.fields.MultiLingualCharField: Functionality mirrors that of django'sdjango.db.models.CharFieldmultilingualfield.fields.MultiLingualTextField: Functionality mirrors that of django'sdjango.db.models.TextFieldmultilingualfield.fields.MultiLingualFileField: Functionality mirrors that of django'sdjango.db.models.FileField
At the database level, MultiLingualCharField, MultiLingualTextField and MultiLingualFileField are essentially identical in that their content is stored within 'text' columns (as opposed to either 'varchar' or 'text'); they diverge only in the widgets/forms they use.
Any options you would pass to a CharField, TextField or FileField (i.e. blank=True, max_length=50, upload_to='path/', storage=StorageClass()) will work as expected but max_length will not be enforced at a database level (only during form creation and input validation).
Use MultiLingualCharField, MultiLingualTextField and MultiLingualFileField just like you would any django field:
from django.db import models
from multilingualfield import fields as mlf_fields
class TestModel(models.Model):
title = mlf_fields.MultiLingualCharField(
max_length=180
)
short_description = mlf_fields.MultiLingualCharField(
max_length=300
)
long_description = mlf_fields.MultiLingualTextField(
blank=True,
null=True
)
image = mlf_fields.MultiLingualFileField(
upload_to='images/',
blank=True,
null=True
)django-multilingualfield is fully integrated with south so migrate to your heart's content!
If LANGUAGES is set in your project's settings like this...
LANGUAGES = [
('en', 'English'),
('es', 'Español')
]...then django-multilingualfield will store translations for a piece of text in a single 'text' db column as XML in the following structure:
<languages>
<language code="en">
Hello
</language>
<language code="es">
Hola
</language>
</languages>The example above includes whitespace for readability, the final value stored in the database will have all between-tag whitespace removed.
Even though MultiLingualCharField and MultiLingualTextField instances are stored in the database as XML they are served to the application as a python object. The above block of XML would return an instance of multilingualfield.fields.MultiLingualText with two attributes:
en(with a value ofu'Hello')es(with a value ofu'Hola')
The translation corresponding to the current language of the active thread (as determined by calling django.utils.translation.get_language) will be returned by directly accessing the field.
Let's create an instance of our above example model (TestModel) in the python shell:
$ python manage.py shell>>> from testapp.models import TestModel
>>> from multilingualfield.datastructures import MultiLingualText
>>> title = MultiLingualText()
>>> title.en = 'Hello'
>>> title.es = 'Hola'
>>> x = TestModel(title=title)
>>> x.save()
>>> x.title.en
u'Hello'
>>> x.title.es
u'Hola'
>>> x.title
u'Hello'
>>> from django.utils.translation import get_language, activate
>>> get_language()
'en-us'
# NOTE: 'en-us' will ALWAYS be the current language in the active
# thread when you load the python shell via manage.py. To learn why
# visit: https://code.djangoproject.com/ticket/12131#comment:6
>>> activate('en')
>>> get_language()
'en'
>>> activate('es')
>>> get_language()
'es'
>>> x.title
u'Hola'
>>> activate('en')
>>> x.title
'en'Both MultiLingualCharField and MultiLingualTextField are admin-ready and will provide either a TextInput (for MultiLingualCharField instances) or Textarea (for MultiLingualTextField instances) field for each language listed in settings.LANGUAGES.
The default formfields for MultiLingual* fields do not include admin-friendly styling so if you want them to look pretty within the admin you have a few options:
-
Swap-out
admin.ModelAdminforMultiLingualFieldModelAdminin your admin configs for models that have MultiLingual* fields:# testapp.admin from django.contrib import admin from multilingualfield.admin import MultiLingualFieldModelAdmin from .models import TestModel class TestModelAdmin(MultiLingualFieldModelAdmin): """ Adds admin-friendly styling to all MultiLingual* fields for TestModel within the admin """ list_display = ('title',) admin.site.register(TestModel, TestModelAdmin)
-
Manually specify MultiLingual* widgets with
formfield_overrides:# testapp.admin from django.contrib import admin from multilingualfield import widgets as mlf_widgets from multilingualfield import fields as mlf_fields from .models import TestModel class TestModelAdmin(admin.ModelAdmin): """ Adds admin-friendly styling to all MultiLingual* fields for TestModel via formfield_overrides """ list_display = ('title',) formfield_overrides = { mlf_fields.MultiLingualCharField: { 'widget': mlf_widgets.MultiLingualCharFieldDjangoAdminWidget }, mlf_fields.MultiLingualTextField: { 'widget': mlf_widgets.MultiLingualTextFieldDjangoAdminWidget }, } admin.site.register(TestModel, TestModelAdmin)
-
Manually specify MultiLingual* widgets on a ModelForm subclass:
# testapp.forms from django.forms.models import ModelForm from multilingualfield import widgets as mlf_widgets from .models import TestModel class TestModelForm(ModelForm): class Meta: model = TestModel fields=( 'title', 'short_description', 'long_description' ) widgets = { 'title': mlf_widgets.MultiLingualCharFieldDjangoAdminWidget, 'short_description': mlf_widgets.MultiLingualCharFieldDjangoAdminWidget, 'long_description': mlf_widgets.MultiLingualTextFieldDjangoAdminWidget }
Integrating the custom form into your admin configuration:
# testapp.admin from django.contrib import admin from .forms import TestModelForm from .models import TestModel class TestModelAdmin(admin.ModelAdmin): """ Adds admin-friendly styling to all MultiLingual* fields via a custom form """ form = TestModelForm admin.site.register(TestModel, TestModelAdmin)
Template usage is simple & straight forward, here's an example template for how you might render a instance of TestModel:
{% load i18n %}
<html>
<head>
<title>{{ object.title }}</title>
</head>
<body>
<h1>{{ object.title }}</h2>
<p>{{ object.short_description }}</p>
{% if object.long_description %}
{{ object.long_description }}
{% else %}
{% trans 'No long description provided' %}
{% endif %}
</body>
</html>For more information about the
transtemplatetag used in the example above check out the django docs.
The example above is typical for most use cases (when you want to render values associated with the user's current language thread) but you always have access to the language-specific attributes:
{% load i18n %}
<html>
<head>
<title>{{ object.title }}</title>
</head>
<body>
<h1>{{ object.title }}</h2>
<!-- Forcing the English display of object.title -->
<h2>{% trans 'Title (English)' %}: {{ object.title.en }}</h2>
<!-- Forcing the Spanish display of object.title -->
<h2>{% trans 'Title (Spanish)' %}: {{ object.title.es }}</h2>
<p>{{ object.short_description }}</p>
{% if object.long_description %}
{{ object.long_description }}
{% else %}
{% trans 'No long description provided' %}
{% endif %}
</body>
</html>