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-multilingualfield
django-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_language
to 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.CharField
multilingualfield.fields.MultiLingualTextField
: Functionality mirrors that of django'sdjango.db.models.TextField
multilingualfield.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.ModelAdmin
forMultiLingualFieldModelAdmin
in 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
trans
templatetag 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>