Для наделения вашего Django проекта возможностью представлять контент на разных языках потребуется немного доработать код и шаблоны. Под доработкой имеется в виду добавление переводимых строк. Они говорят Django: “Этот текст должен быть переведён на язык конечного пользователя, если для этого языка предоставлен перевод.” Вашей задачей является маркировка соответствующих строк, как подлежащих переводу. Система может обеспечивать перевод только тех строк, на которые вы ей указали.
Django предоставляет утилиты для извлечения переводимых строк в файл сообщений. Этот файл является удобным средством, которое позволяет переводчикам делать свою работу. После того, как перевод строк этого файла завершён, файл должен быть скомпилирован. Этот процесс обеспечивает набор средств GNU gettext.
При наличии скомпилированного ресурса с переводом строк, Django обеспечивает автоматический перевод веб приложений для каждого доступного языка, в соответствии с языковыми настройками пользователя.
Механизм интернационализации Django включен по умолчанию, т.е. в определённых местах фреймворка всегда присутствует небольшая трата ресурсов на этот механизм. Если вы не используете интернационализацию, то вам следует потратить пару секунд на установку USE_I18N = False в файле конфигурации. Это позволит Django выполнять некоторую оптимизация, не подгружая библиотеки интернационализации. Возможно, вы также уберёте 'django.core.context_processors.i18n' из параметра конфигурации TEMPLATE_CONTEXT_PROCESSORS.
Примечание
Есть также независимый, но связанный параметр USE_L10N, который управляет применением локального форматирования для данных. Обратитесь к Формат локализации для получения подробностей.
Укажите переводимую строку с помощью функции ugettext(). Удобно импортировать её с помощью краткого псевдонима, _, чтобы сократить затраты на ввод.
Примечание
Модуль gettext стандартной библиотеки языка Python определяет _() в качестве псевдонима для gettext() в глобальном пространстве имён. В Django мы решили не следовать этой практике по следующим причинам:
Для поддержки интернационального набора символов (Unicode), функция ugettext() гораздо более полезна, чем gettext(). Иногда вам потребуется использовать функцию ugettext_lazy() в качестве стандартного метода выделения переводимой строки в определённом файле. При отсутствии _() в глобальном пространстве имён разработчик сам решает какая функция будет ему наиболее полезна в каждом конкретном случае.
Символ подчёркивания (_) используется в интерактивном интерпретаторе Python и в доктестах в качестве “результата предыдущей операции”. Определение глобальной функции _() приведёт к путанице. Явное импортирование ugettext() в виде _() решает эту проблему.
В данном примере, текст "Welcome to my site." помечается как переводимая строка:
from django.utils.translation import ugettext as _
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
Очевидно, что вы можете делать тоже самое без использования псевдонима. Этот пример идентичен предыдущему:
from django.utils.translation import ugettext
def my_view(request):
output = ugettext("Welcome to my site.")
return HttpResponse(output)
Перевод работает с вычисляемыми значениями. Этот пример идентичен предыдущим двум:
def my_view(request):
words = ['Welcome', 'to', 'my', 'site.']
output = _(' '.join(words))
return HttpResponse(output)
Перевод работает с переменными. И снова, аналогичный пример:
def my_view(request):
sentence = 'Welcome to my site.'
output = _(sentence)
return HttpResponse(output)
(Проблема при использовании переменных или вычисляемых значений, как в предыдущих двух примерах, в том, что утилита для поиска переводимых строк, django-admin.py makemessages, не сможет найти эти строки. Далее мы подробно рассмотрим makemessages.)
Строка, передаваемая в _() или ugettext(), может принимать заполнители (placeholders), определённые стандартом языка Python для строк. Пример:
def my_view(request, m, d):
output = _('Today is %(month)s %(day)s.') % {'month': m, 'day': d}
return HttpResponse(output)
Такой подход позволяет при необходимости менять порядок слов при переводе. Например, английский текст "Today is November 26." может быть переведён на испанский как "Hoy es 26 de Noviembre.". Как видно, заполнители для месяца и дня поменялись местами.
По этой причине, вы должны использовать именованные заполнители (т.е., %(day)s) вместо позиционных (т.е., %s или %d), в случае, если в строку подставляется больше одного параметра. При использовании позиционных заполнителей переводчики не будут иметь возможность изменять оригинальный порядок слов.
Если необходимо дать переводчикам подсказку по переводимой строке, вы можете добавить комментарий с префиксом Translators в строке предшествующей переводимой, например:
def my_view(request):
# Translators: This message appears on the home page only
output = ugettext("Welcome to my site.")
Этот подход также работает в шаблонах при использовании тага comment:
{% comment %}Translators: This is a text of the base template {% endcomment %}
Комментарий появится в результирующем .po файле и должен быть отображён большинством средств перевода.
Используйте функцию django.utils.translation.ugettext_noop() для пометки строки как переводимой, но не переводя её. Такая строка будет переведена позже с помощью переменной.
Используйте этот подход в случае, когда у вас есть строковые константы, которые должны быть сохранены в исходном коде по причине того, что они изменяются системой или пользователями Примером могут служить строки из базы данных, которые следует переводить в самый последний момент, перед непосредственным их отображением пользователю.
Используйте функцию django.utils.translation.ungettext() для указания сообщений во множественном числе.
Функция ungettext принимает три аргумента: строка в единственном числе, строка во множественном числе и количество объектов.
Эта функция очень полезна, когда требуется локализовать Django приложение на языки, в которых количество и сложность множественных форм превышает два варианта как в английском языке (‘object’ для единственного числа и ‘objects’ для всех остальных случаем, когда count отличается от единицы, независимо от своего значения.)
Например:
from django.utils.translation import ungettext
def hello_world(request, count):
page = ungettext(
'there is %(count)d object',
'there are %(count)d objects',
count) % {
'count': count,
}
return HttpResponse(page)
В этом примере количество объектов передаётся в перевод в переменной count.
Давайте рассмотрим более сложный пример:
from django.utils.translation import ungettext
count = Report.objects.count()
if count == 1:
name = Report._meta.verbose_name
else:
name = Report._meta.verbose_name_plural
text = ungettext(
'There is %(count)d %(name)s available.',
'There are %(count)d %(name)s available.',
count
) % {
'count': count,
'name': name
}
Здесь мы используем локализованные и, надеюсь, уже переведённые строки (описанные в полях verbose_name и verbose_name_plural модели Meta) для других частей предложения.
Примечание
При использовании данного подхода, проверьте, что вы используете уникальные имена для каждой переменной, указанной в строке. В вышеприведённом примере, обратите внимание на то, как мы используем переменную name в обоих строках. Следующий пример выдаст ошибку:
from django.utils.translation import ungettext
from myapp.models import Report
count = Report.objects.count()
d = {
'count': count,
'name': Report._meta.verbose_name,
'plural_name': Report._meta.verbose_name_plural
}
text = ungettext(
'There is %(count)d %(name)s available.',
'There are %(count)d %(plural_name)s available.',
count
) % d
Вы получите ошибку при запуске django-admin.py compilemessages:
a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid'
Некоторые слова имеют множество значений, такие как "May" в английском языке, которое может означать название месяца или глагол. Для упрощения жизни переводчиков вы можете использовать функцию django.utils.translation.pgettext() или функцию django.utils.translation.npgettext(), для множественного числа. Обе функции принимают описание контекста в качестве первого аргумента.
В результате, в файле .po, переводимая строка появится столько раз, сколько есть различных контекстов для неё (контекстная информация будет указана в строке msgctxt), позволяя перевести каждую из них в соответствии со смыслом.
Например:
from django.utils.translation import pgettext
month = pgettext("month name", "May")
или:
from django.utils.translation import pgettext_lazy
class MyThing(models.Model):
name = models.CharField(help_text=pgettext_lazy(
'help text for MyThing model', 'This is the help text'))
появится в файле .po в виде:
msgctxt "month name"
msgid "May"
msgstr ""
Контекстные маркеры также поддерживаются шаблонными тагами trans и blocktrans.
Используйте ленивые версии функций перевода из django.utils.translation (их легко опознать по суффиксу lazy в их именах) для отложенного перевода строк – перевод производится во время обращения к строке, а не когда вызывается функция.
Эти функции хранят ленивую ссылку на строку, не на её перевод. Сам перевод будет выполнен во время использования строки в строковом контексте, например, во время обработки шаблона.
This is essential when calls to these functions are located in code paths that are executed at module load time.
Такое может легко произойти во время определения моделей, форм или модельных форм, так как в Django их поля реализованы в виде атрибутов класса. По этой причине, надо использовать ленивый перевод в следующих случаях:
Например, для перевода подсказки для поля name в следующей модели, действуйте так:
from django.utils.translation import ugettext_lazy
class MyThing(models.Model):
name = models.CharField(help_text=ugettext_lazy('This is the help text'))
Вы можете перевести метки для ForeignKey, ManyTomanyField или OneToOneField с помощью их атрибута verbose_name:
from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
kind = models.ForeignKey(ThingKind, related_name='kinds',
verbose_name=_('kind'))
Подобно тому, как вы делаете для verbose_name, вы должны предоставить текст метки (строчными буквами) для связи, а Django автоматически преобразует первую букву в прописную когда это необходимо.
Рекомендуется всегда предоставлять явные значения для verbose_name и verbose_name_plural, а не надеяться на механизм их автоматического определения через имя класса:
from django.utils.translation import ugettext_lazy
class MyThing(models.Model):
name = models.CharField(_('name'), help_text=ugettext_lazy('This is the help text'))
class Meta:
verbose_name = ugettext_lazy('my thing')
verbose_name_plural = ugettext_lazy('my things')
Для методов модели вы можете с помощью атрибута short_description предоставить перевод для Django и интерфейса администратора:
from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
kind = models.ForeignKey(ThingKind, related_name='kinds',
verbose_name=_('kind'))
def is_mouse(self):
return self.kind.type == MOUSE_TYPE
is_mouse.short_description = _('Is it a mouse?')
Результат вызова ugettext_lazy() может быть использован везде, где требуется юникодная строка (объект типа unicode). Если вы попытаетесь использовать её там, где ожидается обычная строка (объект типа str), то не получите ожидаемый результат, так как объект ugettext_lazy() не знает как преобразовать себя в обычную строку. Вы не можете использовать юникодную строку внутри обычной, что, в общем, является обычным поведением в Python. Например:
# This is fine: putting a unicode proxy into a unicode string.
u"Hello %s" % ugettext_lazy("people")
# This will not work, since you cannot insert a unicode object
# into a bytestring (nor can you insert our unicode proxy there)
"Hello %s" % ugettext_lazy("people")
Если вы встретили нечто подобное "hello <django.utils.functional...>", значит вы попытались встать результат ugettext_lazy() в обычную строку. Это ошибка в вашем коде.
Если вам не нравится писать такое длинное имя как ugettext_lazy, в можете создать для него псевдоним _ (символ подчеркивания), вот так:
from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
Использование ugettext_lazy() и ungettext_lazy() для пометки строк в моделях и в прикладных функциях является обычной операцией. Работая с этими объектами в вашем коде, вы должны быть уверены, что вы не преобразоваете их случайно в строки, так как это преобразование должно происходить как можно позже (и будет приниматься во внимание правильная локаль). Это потребует использования вспомогательной функции, описанной далее.
Стандартное для Python объединение строк (''.join([...])) не будет работать со списками, которые содержат объекты отложенного перевода. Для этого вы можете использовать функцию django.utils.translation.string_concat(), создающую ленивый объект, который объединяет своё содержимое и преобразует его в строку только когда результат функции включается в строку. Например:
from django.utils.translation import string_concat
...
name = ugettext_lazy(u'John Lennon')
instrument = ugettext_lazy(u'guitar')
result = string_concat(name, ': ', instrument)
В данном случае, отложенный перевод в result будет преобразован в строку только когда сам result будет использован в строке (обычно это происходит во время обработки шаблона).
Функция get_language_info() предоставляет детальную информацию о языках:
>>> from django.utils.translation import get_language_info
>>> li = get_language_info('de')
>>> print li['name'], li['name_local'], li['bidi']
German Deutsch False
Атрибуты name и name_local содержат содержат название языка на английском и на этом самом языке соответственно. Атрибут bidi установлен в True только для двунаправленных языков.
Источником информации о языках является модуль django.conf.locale. Аналогичный доступ к информации о языках есть и на уровне шаблонов. См. далее.
Для перевода текста в шаблонах Django используют два шаблонах тега и немного отличающийся синтаксис. Чтобы воспользоваться этими тегами, поместите {% load i18n %} в начало шаблона.
Шаблонный тег {% trans %} может переводить как обычную строку, заключенную в одинарные или двойные кавычки, так и содержимое переменой:
<title>{% trans "This is the title." %}</title>
<title>{% trans myvar %}</title>
При использовании опции noop, обращение к переменной происходит, но перевод не выполняется. Это удобно когда надо пометить контент для перевода в будущем:
<title>{% trans "myvar" noop %}</title>
Перевод подстроку выполняется с помощью функции ugettext().
В случае передачи шаблонной переменой (см. выше myvar) в тег, тег сначала преобразовывает её в строку, а затем ищет для неё перевод в каталогах сообщений.
Невозможно использовать шаблонные переменные внутри строки для тега {% trans %}. Если же ваш перевод требует наличия переменой в строке, используйте шаблонный тег {% blocktrans %}.
Если требуется получать переведённые строки без их отображения, то вы можете использовать следующий синтаксис:
{% trans "This is the title" as the_title %}
<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">
На практике вы будете применять это для получения строк, которые используются во множестве мест шаблона или должны быть использованы в качестве аргументов для других шаблонных тегов или фильтров:
{% trans "starting point" as start %}
{% trans "end point" as end %}
{% trans "La Grande Boucle" as race %}
<h1>
<a href="/" title="{% blocktrans %}Back to '{{ race }}' homepage{% endblocktrans %}">{{ race }}</a>
</h1>
<p>
{% for stage in tour_stages %}
{% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}<br />{% else %}, {% endif %}
{% endfor %}
</p>
Тег {% trans %} также поддерживает контекстные маркеры с помощью атрибута context:
{% trans "May" context "month name" %}
В отличит от тега trans, тег blocktrans позволяет отмечать сложные предложения, состоящие из строк и переменных, обеспечивая перевод с помощью подстановок:
{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}
Для перевода шаблонных выражений, скажем, с доступом к атрибутам объекта или с использованием шаблонных фильтров, потребуется связать выражение с локальной переменной для использования внутри переводимого блока. Примеры:
{% blocktrans with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktrans %}
{% blocktrans with myvar=value|filter %}
This will have {{ myvar }} inside.
{% endblocktrans %}
Вы можете использовать несколько выражений внутри одного тега blocktrans:
{% blocktrans with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }}
{% endblocktrans %}
Примечание
Как и прежде тег поддерживает старое форматирование: {% blocktrans with book|title as book_t and author|title as author_t %}
Если невозможно вычисление хотя бы одного из аргументов блока, тогда тег переключается на язык по умолчанию с помощью функции deactivate_all().
Этот тег также поддерживает склонение, например:
Определите переменную count и свяжите с ней значение счётчика. По этому значению будет выбираться форма склонения.
Укажите единственную и множественную формы, разделив их с помощью тега {% plural %} внутри тегов {% blocktrans %} и {% endblocktrans %}.
Пример:
{% blocktrans count counter=list|length %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktrans %}
Более сложный пример:
{% blocktrans with amount=article.price count years=i.length %}
That will cost $ {{ amount }} per year.
{% plural %}
That will cost $ {{ amount }} per {{ years }} years.
{% endblocktrans %}
Когда используете и возможности склонения и присвоение значения локальным переменным в дополнение к значению счетчика помните, что конструкция blocktrans на низком уровне преобразовывается в вызов ungettext. Это означает, что к ней применяются замечания для переменных ungettext.
Обратное разрешение URL не можно быть выполнено внутри blocktrans и должно выполняться заранее:
{% url path.to.view arg arg2 as the_url %}
{% blocktrans %}
This is a URL: {{ the_url }}
{% endblocktrans %}
Тег {% blocktrans %} также поддерживает контекстные маркеры с помощью атрибута context:
{% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}
Каждый объект RequestContext имеет доступ к трём переменным, относящимся к механизму перевода:
LANGUAGES является списком кортежей, в которых первый элемент является кодом языка, а второй — названием языка , переведённым в текущую локаль.
LANGUAGE_CODE является предпочитаемым языком текущего позвонила, в виде строки. Например: en-us. Обратитесь к How Django discovers language preference.
LANGUAGE_BIDI является текущим направлением локали. Если равно True, буквы в словах в этом языке идут справа налево, т.е.: иврит, арабский. Если False — соответственно, наоборот, т.е.: английский, французский, немецкий и так далее.
Если вы не используете расширение RequestContext, вы можете получить эти значения с помощью тегов:
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
Эти теги также требуют наличия {% load i18n %} в шаблоне.
Сокращение для функции перевода также доступно внутри любого шаблонного тега, который работает с неизменяемыми строками. В этих случаях просто используйте синтаксис _() для указания переводимой строки:
{% some_special_tag _("Page not found") value|yesno:_("yes,no") %}
В этом случае, и тег, и фильтр увидят заранее переведённую строку, т.е. им не надо будет беспокоиться о переводе.
Примечание
В этом примере в модуль перевода будет передана строка `` “yes,no” ``, а не отдельные строки ``“yes”, `` и ``“no” ``. Перевод этой строки должен содержать запятую так, чтобы код парсера фильтра знал как разбить эту строку. Например, немецкий переводчик может переводить строку `` “yes,no” `` как ``“ja, nein” ``, сохраняя нетронутой запятую.
Вы также можете получить информацию о любом доступном языке, используя предоставленные шаблонные теги и фильтры. Для получения информации об одном языке используйте тег {% get_language_info %}:
{% get_language_info for LANGUAGE_CODE as lang %}
{% get_language_info for "pl" as lang %}
Затем вы можете узнать следующее:
Language code: {{ lang.code }}<br />
Name of language: {{ lang.name_local }}<br />
Name in English: {{ lang.name }}<br />
Bi-directional: {{ lang.bidi }}
You can also use the {% get_language_info_list %} template tag to retrieve information for a list of languages (e.g. active languages as specified in LANGUAGES). See the section about the set_language redirect view for an example of how to display a language selector using {% get_language_info_list %}.
In addition to LANGUAGES style nested tuples, {% get_language_info_list %} supports simple lists of language codes. If you do this in your view:
return render_to_response('mytemplate.html', {
'available_languages': ['en', 'es', 'fr'],
}, RequestContext(request))
вы можете по очереди пройтись по этим языкам в шаблоне:
{% get_language_info_list for available_languages as langs %}
{% for lang in langs %} ... {% endfor %}
There are also simple filters available for convenience:
Adding translations to JavaScript poses some problems:
Django provides an integrated solution for these problems: It passes the translations into JavaScript, so you can call gettext, etc., from within JavaScript.
The main solution to these problems is the django.views.i18n.javascript_catalog() view, which sends out a JavaScript code library with functions that mimic the gettext interface, plus an array of translation strings. Those translation strings are taken from applications or Django core, according to what you specify in either the info_dict or the URL. Paths listed in LOCALE_PATHS are also included.
You hook it up like this:
js_info_dict = {
'packages': ('your.app.package',),
}
urlpatterns = patterns('',
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
)
Each string in packages should be in Python dotted-package syntax (the same format as the strings in INSTALLED_APPS) and should refer to a package that contains a locale directory. If you specify multiple packages, all those catalogs are merged into one catalog. This is useful if you have JavaScript that uses strings from different applications.
The precedence of translations is such that the packages appearing later in the packages argument have higher precedence than the ones appearing at the beginning, this is important in the case of clashing translations for the same literal.
By default, the view uses the djangojs gettext domain. This can be changed by altering the domain argument.
You can make the view dynamic by putting the packages into the URL pattern:
urlpatterns = patterns('',
(r'^jsi18n/(?P<packages>\S+?)/$', 'django.views.i18n.javascript_catalog'),
)
With this, you specify the packages as a list of package names delimited by ‘+’ signs in the URL. This is especially useful if your pages use code from different apps and this changes often and you don’t want to pull in one big catalog file. As a security measure, these values can only be either django.conf or any package from the INSTALLED_APPS setting.
The JavaScript translations found in the paths listed in the LOCALE_PATHS setting are also always included. To keep consistency with the translations lookup order algorithm used for Python and templates, the directories listed in LOCALE_PATHS have the highest precedence with the ones appearing first having higher precedence than the ones appearing later.
To use the catalog, just pull in the dynamically generated script like this:
<script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>
This uses reverse URL lookup to find the URL of the JavaScript catalog view. When the catalog is loaded, your JavaScript code can use the standard gettext interface to access it:
document.write(gettext('this is to be translated'));
There is also an ngettext interface:
var object_cnt = 1 // or 0, or 2, or 3, ...
s = ngettext('literal for the singular case',
'literal for the plural case', object_cnt);
and even a string interpolation function:
function interpolate(fmt, obj, named);
The interpolation syntax is borrowed from Python, so the interpolate function supports both positional and named interpolation:
Positional interpolation: obj contains a JavaScript Array object whose elements values are then sequentially interpolated in their corresponding fmt placeholders in the same order they appear. For example:
fmts = ngettext('There is %s object. Remaining: %s',
'There are %s objects. Remaining: %s', 11);
s = interpolate(fmts, [11, 20]);
// s is 'There are 11 objects. Remaining: 20'
Named interpolation: This mode is selected by passing the optional boolean named parameter as true. obj contains a JavaScript object or associative array. For example:
d = {
count: 10,
total: 50
};
fmts = ngettext('Total: %(total)s, there is %(count)s object',
'there are %(count)s of a total of %(total)s objects', d.count);
s = interpolate(fmts, d, true);
You shouldn’t go over the top with string interpolation, though: this is still JavaScript, so the code has to make repeated regular-expression substitutions. This isn’t as fast as string interpolation in Python, so keep it to those cases where you really need it (for example, in conjunction with ngettext to produce proper pluralizations).
Django provides two mechanisms to internationalize URL patterns:
Предупреждение
Using either one of these features requires that an active language be set for each request; in other words, you need to have django.middleware.locale.LocaleMiddleware in your MIDDLEWARE_CLASSES setting.
This function can be used in your root URLconf as a replacement for the normal django.conf.urls.patterns() function. Django will automatically prepend the current active language code to all url patterns defined within i18n_patterns(). Example URL patterns:
from django.conf.urls import patterns, include, url
from django.conf.urls.i18n import i18n_patterns
urlpatterns = patterns(''
url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
)
news_patterns = patterns(''
url(r'^$', 'news.views.index', name='index'),
url(r'^category/(?P<slug>[\w-]+)/$', 'news.views.category', name='category'),
url(r'^(?P<slug>[\w-]+)/$', 'news.views.details', name='detail'),
)
urlpatterns += i18n_patterns('',
url(r'^about/$', 'about.view', name='about'),
url(r'^news/$', include(news_patterns, namespace='news')),
)
After defining these URL patterns, Django will automatically add the language prefix to the URL patterns that were added by the i18n_patterns function. Example:
from django.core.urlresolvers import reverse
from django.utils.translation import activate
>>> activate('en')
>>> reverse('sitemap_xml')
'/sitemap.xml'
>>> reverse('news:index')
'/en/news/'
>>> activate('nl')
>>> reverse('news:detail', kwargs={'slug': 'news-slug'})
'/nl/news/news-slug/'
Предупреждение
i18n_patterns() is only allowed in your root URLconf. Using it within an included URLconf will throw an ImproperlyConfigured exception.
Предупреждение
Ensure that you don’t have non-prefixed URL patterns that might collide with an automatically-added language prefix.
URL patterns can also be marked translatable using the ugettext_lazy() function. Example:
from django.conf.urls import patterns, include, url
from django.conf.urls.i18n import i18n_patterns
from django.utils.translation import ugettext_lazy as _
urlpatterns = patterns(''
url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
)
news_patterns = patterns(''
url(r'^$', 'news.views.index', name='index'),
url(_(r'^category/(?P<slug>[\w-]+)/$'), 'news.views.category', name='category'),
url(r'^(?P<slug>[\w-]+)/$', 'news.views.details', name='detail'),
)
urlpatterns += i18n_patterns('',
url(_(r'^about/$'), 'about.view', name='about'),
url(_(r'^news/$'), include(news_patterns, namespace='news')),
)
After you’ve created the translations, the reverse() function will return the URL in the active language. Example:
from django.core.urlresolvers import reverse
from django.utils.translation import activate
>>> activate('en')
>>> reverse('news:category', kwargs={'slug': 'recent'})
'/en/news/category/recent/'
>>> activate('nl')
>>> reverse('news:category', kwargs={'slug': 'recent'})
'/nl/nieuws/categorie/recent/'
Предупреждение
In most cases, it’s best to use translated URLs only within a language-code-prefixed block of patterns (using i18n_patterns()), to avoid the possibility that a carelessly translated URL causes a collision with a non-translated URL pattern.
If localized URLs get reversed in templates they always use the current language. To link to a URL in another language use the language template tag. It enables the given language in the enclosed template section:
{% load i18n %}
{% get_available_languages as languages %}
{% trans "View this category in:" %}
{% for lang_code, lang_name in languages %}
{% language lang_code %}
<a href="{% url category slug=category.slug %}">{{ lang_name }}</a>
{% endlanguage %}
{% endfor %}
The language tag expects the language code as the only argument.
Once the string literals of an application have been tagged for later translation, the translation themselves need to be written (or obtained). Here’s how that works.
Locale restrictions
Django does not support localizing your application into a locale for which Django itself has not been translated. In this case, it will ignore your translation files. If you were to try this and Django supported it, you would inevitably see a mixture of translated strings (from your application) and English strings (from Django itself). If you want to support a locale for your application that is not already part of Django, you’ll need to make at least a minimal translation of the Django core.
A good starting point is to copy the Django English .po file and to translate at least some translation strings.
The first step is to create a message file for a new language. A message file is a plain-text file, representing a single language, that contains all available translation strings and how they should be represented in the given language. Message files have a .po file extension.
Django comes with a tool, django-admin.py makemessages, that automates the creation and upkeep of these files.
Gettext utilities
The makemessages command (and compilemessages discussed later) use commands from the GNU gettext toolset: xgettext, msgfmt, msgmerge and msguniq.
The minimum version of the gettext utilities supported is 0.15.
To create or update a message file, run this command:
django-admin.py makemessages -l de
...where de is the language code for the message file you want to create. The language code, in this case, is in locale format. For example, it’s pt_BR for Brazilian Portuguese and de_AT for Austrian German.
The script should be run from one of two places:
The script runs over your project source tree or your application source tree and pulls out all strings marked for translation. It creates (or updates) a message file in the directory locale/LANG/LC_MESSAGES. In the de example, the file will be locale/de/LC_MESSAGES/django.po.
By default django-admin.py makemessages examines every file that has the .html or .txt file extension. In case you want to override that default, use the --extension or -e option to specify the file extensions to examine:
django-admin.py makemessages -l de -e txt
Separate multiple extensions with commas and/or use -e or --extension multiple times:
django-admin.py makemessages -l de -e html,txt -e xml
Предупреждение
When creating message files from JavaScript source code you need to use the special ‘djangojs’ domain, not -e js.
No gettext?
If you don’t have the gettext utilities installed, makemessages will create empty files. If that’s the case, either install the gettext utilities or just copy the English message file (locale/en/LC_MESSAGES/django.po) if available and use it as a starting point; it’s just an empty translation file.
Working on Windows?
If you’re using Windows and need to install the GNU gettext utilities so makemessages works, see gettext on Windows for more information.
The format of .po files is straightforward. Each .po file contains a small bit of metadata, such as the translation maintainer’s contact information, but the bulk of the file is a list of messages – simple mappings between translation strings and the actual translated text for the particular language.
For example, if your Django app contained a translation string for the text "Welcome to my site.", like so:
_("Welcome to my site.")
...then django-admin.py makemessages will have created a .po file containing the following snippet – a message:
#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""
A quick explanation:
Long messages are a special case. There, the first string directly after the msgstr (or msgid) is an empty string. Then the content itself will be written over the next few lines as one string per line. Those strings are directly concatenated. Don’t forget trailing spaces within the strings; otherwise, they’ll be tacked together without whitespace!
Mind your charset
When creating a PO file with your favorite text editor, first edit the charset line (search for "CHARSET") and set it to the charset you’ll be using to edit the content. Due to the way the gettext tools work internally and because we want to allow non-ASCII source strings in Django’s core and your applications, you must use UTF-8 as the encoding for your PO file. This means that everybody will be using the same encoding, which is important when Django processes the PO files.
To reexamine all source code and templates for new translation strings and update all message files for all languages, run this:
django-admin.py makemessages -a
After you create your message file – and each time you make changes to it – you’ll need to compile it into a more efficient form, for use by gettext. Do this with the django-admin.py compilemessages utility.
This tool runs over all available .po files and creates .mo files, which are binary files optimized for use by gettext. In the same directory from which you ran django-admin.py makemessages, run django-admin.py compilemessages like this:
django-admin.py compilemessages
That’s it. Your translations are ready for use.
Working on Windows?
If you’re using Windows and need to install the GNU gettext utilities so django-admin.py compilemessages works see gettext on Windows for more information.
.po files: Encoding and BOM usage.
Django only supports .po files encoded in UTF-8 and without any BOM (Byte Order Mark) so if your text editor adds such marks to the beginning of files by default then you will need to reconfigure it.
You create and update the message files the same way as the other Django message files – with the django-admin.py makemessages tool. The only difference is you need to explicitly specify what in gettext parlance is known as a domain in this case the djangojs domain, by providing a -d djangojs parameter, like this:
django-admin.py makemessages -d djangojs -l de
This would create or update the message file for JavaScript for German. After updating message files, just run django-admin.py compilemessages the same way as you do with normal Django message files.
This is only needed for people who either want to extract message IDs or compile message files (.po). Translation work itself just involves editing existing files of this type, but if you want to create your own message files, or want to test or compile a changed message file, you will need the gettext utilities:
Download the following zip files from the GNOME servers http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/ or from one of its mirrors
X is the version number, we are requiring 0.15 or higher.
Extract the contents of the bin\ directories in both files to the same folder on your system (i.e. C:\Program Files\gettext-utils)
Update the system PATH:
You may also use gettext binaries you have obtained elsewhere, so long as the xgettext --version command works properly. Do not attempt to use Django translation utilities with a gettext package if the command xgettext --version entered at a Windows command prompt causes a popup window saying “xgettext.exe has generated errors and will be closed by Windows”.
As a convenience, Django comes with a view, django.views.i18n.set_language(), that sets a user’s language preference and redirects to a given URL or, by default, back to the previous page.
Activate this view by adding the following line to your URLconf:
(r'^i18n/', include('django.conf.urls.i18n')),
(Note that this example makes the view available at /i18n/setlang/.)
The view expects to be called via the POST method, with a language parameter set in request. If session support is enabled, the view saves the language choice in the user’s session. Otherwise, it saves the language choice in a cookie that is by default named django_language. (The name can be changed through the LANGUAGE_COOKIE_NAME setting.)
After setting the language choice, Django redirects the user, following this algorithm:
Here’s example HTML template code:
<form action="/i18n/setlang/" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}" />
<select name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}">{{ language.name_local }} ({{ language.code }})</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
In this example, Django looks up the URL of the page to which the user will be redirected in the redirect_to context variable.
While Django provides a rich set of i18n tools for use in views and templates, it does not restrict the usage to Django-specific code. The Django translation mechanisms can be used to translate arbitrary texts to any language that is supported by Django (as long as an appropriate translation catalog exists, of course). You can load a translation catalog, activate it and translate text to language of your choice, but remember to switch back to original language, as activating a translation catalog is done on per-thread basis and such change will affect code running in the same thread.
Например:
from django.utils import translation
def welcome_translated(language):
cur_language = translation.get_language()
try:
translation.activate(language)
text = translation.ugettext('welcome')
finally:
translation.activate(cur_language)
return text
Calling this function with the value ‘de’ will give you "Willkommen", regardless of LANGUAGE_CODE and language set by middleware.
Functions of particular interest are django.utils.translation.get_language() which returns the language used in the current thread, django.utils.translation.activate() which activates a translation catalog for the current thread, and django.utils.translation.check_for_language() which checks if the given language is supported by Django.
Django’s translation machinery uses the standard gettext module that comes with Python. If you know gettext, you might note these specialties in the way Django does translation:
Once you’ve prepared your translations – or, if you just want to use the translations that come with Django – you’ll just need to activate translation for your app.
Behind the scenes, Django has a very flexible model of deciding which language should be used – installation-wide, for a particular user, or both.
To set an installation-wide language preference, set LANGUAGE_CODE. Django uses this language as the default translation – the final attempt if no other translator finds a translation.
If all you want to do is run Django with your native language, and a language file is available for it, all you need to do is set LANGUAGE_CODE.
If you want to let each individual user specify which language he or she prefers, use LocaleMiddleware. LocaleMiddleware enables language selection based on data from the request. It customizes content for each user.
To use LocaleMiddleware, add 'django.middleware.locale.LocaleMiddleware' to your MIDDLEWARE_CLASSES setting. Because middleware order matters, you should follow these guidelines:
For example, your MIDDLEWARE_CLASSES might look like this:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
)
(For more on middleware, see the middleware documentation.)
LocaleMiddleware tries to determine the user’s language preference by following this algorithm:
First, it looks for the language prefix in the requested URL. This is only performed when you are using the i18n_patterns function in your root URLconf. See Internationalization: in URL patterns for more information about the language prefix and how to internationalize URL patterns.
Failing that, it looks for a django_language key in the current user’s session.
Failing that, it looks for a cookie.
The name of the cookie used is set by the LANGUAGE_COOKIE_NAME setting. (The default name is django_language.)
Failing that, it looks at the Accept-Language HTTP header. This header is sent by your browser and tells the server which language(s) you prefer, in order by priority. Django tries each language in the header until it finds one with available translations.
Failing that, it uses the global LANGUAGE_CODE setting.
Notes:
In each of these places, the language preference is expected to be in the standard language format, as a string. For example, Brazilian Portuguese is pt-br.
If a base language is available but the sublanguage specified is not, Django uses the base language. For example, if a user specifies de-at (Austrian German) but Django only has de available, Django uses de.
Only languages listed in the LANGUAGES setting can be selected. If you want to restrict the language selection to a subset of provided languages (because your application doesn’t provide all those languages), set LANGUAGES to a list of languages. For example:
LANGUAGES = (
('de', _('German')),
('en', _('English')),
)
This example restricts languages that are available for automatic selection to German and English (and any sublanguage, like de-ch or en-us).
If you define a custom LANGUAGES setting, as explained in the previous bullet, it’s OK to mark the languages as translation strings – but use a “dummy” ugettext() function, not the one in django.utils.translation. You should never import django.utils.translation from within your settings file, because that module in itself depends on the settings, and that would cause a circular import.
The solution is to use a “dummy” ugettext() function. Here’s a sample settings file:
ugettext = lambda s: s
LANGUAGES = (
('de', ugettext('German')),
('en', ugettext('English')),
)
With this arrangement, django-admin.py makemessages will still find and mark these strings for translation, but the translation won’t happen at runtime – so you’ll have to remember to wrap the languages in the real ugettext() in any code that uses LANGUAGES at runtime.
The LocaleMiddleware can only select languages for which there is a Django-provided base translation. If you want to provide translations for your application that aren’t already in the set of translations in Django’s source tree, you’ll want to provide at least a basic one as described in the Locale restrictions note.
Once LocaleMiddleware determines the user’s preference, it makes this preference available as request.LANGUAGE_CODE for each HttpRequest. Feel free to read this value in your view code. Here’s a simple example:
def hello_world(request, count):
if request.LANGUAGE_CODE == 'de-at':
return HttpResponse("You prefer to read Austrian German.")
else:
return HttpResponse("You prefer to read another language.")
Note that, with static (middleware-less) translation, the language is in settings.LANGUAGE_CODE, while with dynamic (middleware) translation, it’s in request.LANGUAGE_CODE.
At runtime, Django builds an in-memory unified catalog of literals-translations. To achieve this it looks for translations by following this algorithm regarding the order in which it examines the different file paths to load the compiled message files (.mo) and the precedence of multiple translations for the same literal:
Не рекомендуется, начиная с версии 1.3: Lookup in the locale subdirectory of the directory containing your settings file (item 3 above) is deprecated since the 1.3 release and will be removed in Django 1.5. You can use the LOCALE_PATHS setting instead, by listing the absolute filesystem path of such locale directory in the setting value.
См.также
The translations for literals included in JavaScript assets are looked up following a similar but not identical algorithm. See the javascript_catalog view documentation for more details.
In all cases the name of the directory containing the translation is expected to be named using locale name notation. E.g. de, pt_BR, es_AR, etc.
This way, you can write applications that include their own translations, and you can override base translations in your project path. Or, you can just build a big project out of several apps and put all translations into one big common message file specific to the project you are composing. The choice is yours.
Примечание
If you’re using manually configured settings, as described in Using settings without setting DJANGO_SETTINGS_MODULE, the locale directory in the project directory will not be examined, since Django loses the ability to work out the location of the project directory. (Django normally uses the location of the settings file to determine this, and a settings file doesn’t exist if you’re manually configuring your settings.)
All message file repositories are structured the same way. They are:
To create message files, you use the django-admin.py makemessages tool. You only need to be in the same directory where the locale/ directory is located. And you use django-admin.py compilemessages to produce the binary .mo files that are used by gettext.
You can also run django-admin.py compilemessages --settings=path.to.settings to make the compiler process all the directories in your LOCALE_PATHS setting.
Finally, you should give some thought to the structure of your translation files. If your applications need to be delivered to other users and will be used in other projects, you might want to use app-specific translations. But using app-specific translations and project-specific translations could produce weird problems with makemessages: it will traverse all directories below the current path and so might put message IDs into a unified, common message file for the current project that are already in application message files.
The easiest way out is to store applications that are not part of the project (and so carry their own translations) outside the project tree. That way, django-admin.py makemessages, when ran on a project level will only extract strings that are connected to your explicit project and not strings that are distributed independently.
Mar 30, 2016