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
<https://docs.djangoproject.com/en/dev/ref/settings/#languages>`__
is properly defined in your settings file.
If you don't have
`LANGUAGE_CODE
<https://docs.djangoproject.com/en/dev/ref/settings/#language-code>`__
set in your settings file it will default to 'en-us' (U.S. English). It
is recommended you manually set
`LANGUAGE_CODE
<https://docs.djangoproject.com/en/dev/ref/settings/#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
<https://docs.djangoproject.com/en/dev/ref/settings/#languages>`__.
Here's an example:
LANGUAGE_CODE = 'en'
LANGUAGES = [
('en', 'English'),
('es', 'Español') # See note below note
]
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
<https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MIDDLEWARE_CLASSES>`__
setting and 'django.core.context_processors.i18n'
added to your
`TEMPLATE_CONTEXT_PROCESSORS
<https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATE_CONTEXT_PROCESSORS>`__
setting:
MIDDLEWARE_CLASSES += (
'django.middleware.locale.LocaleMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.i18n',
)
NOTE
^^^^
django-multilingualfield uses
```django.utils.translation.get_language`` <https://docs.djangoproject.com/en/dev/ref/utils/#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' <https://docs.djangoproject.com/en/dev/topics/i18n/translation/#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>
NOTE
''''
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
<https://docs.djangoproject.com/en/dev/ref/utils/#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
<https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.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>
NOTE
^^^^
For more information about the ``trans`` templatetag used in the
example above check out the `django
docs <https://docs.djangoproject.com/en/dev/topics/i18n/translation/#trans-template-tag>`__.
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>