В прошлом мы давали подробные руководства по инструментам Gettext, а также по интеграции Gettext с Python. Мы собираемся расширить наши знания с помощью Babel и увидеть несколько практических примеров его использования в python i18n. Мы также увидим, как интегрировать его с шаблонами Jinja2 и как интегрировать Контекстный редактор фраз в приложение Flask, чтобы облегчить процесс перевода, просто просматривая веб-сайт и редактируя тексты. таким образом, делая ваш Python процесс локализации намного проще. Вы также можете найти код, описанный в этом руководстве, на Github.
О Бабеле
Babel предоставляет помощники и инструменты интернационализации (python i18n) и локализации (python l10n), которые работают в двух областях. Первый — это модуль gettext, который использует gettext для обновления, извлечения и компиляции каталогов сообщений и управления файлами PO. Второй — использование CLDR (Common Locale Data Repository) для предоставления методов форматирования валют, дат, чисел и т. д. на основе параметра локали. Оба аспекта направлены на то, чтобы помочь автоматизировать процесс интернационализации приложений Python, а также предоставить удобные методы доступа и использования этих данных.
Babel, по сути, работает как механизм абстракции для более крупной среды извлечения сообщений, поскольку вы можете расширять ее своими собственными экстракторами и стратегиями, не привязанными к конкретной платформе.
Установка и использование Babel
Установить Babel очень просто, используя pip
$ pip install Babel
Если у вас не установлен pip, вы можете получить его с помощью easy_install
$ sudo easy_install pip
Работа с данными локали
(CLDR) Общий репозиторий данных локали Unicode — это стандартизированный репозиторий данных локали, используемый для форматирования, анализа и отображения информации, относящейся к локали. Вместо перевода, например, названий дней или месяцев для определенного языка или сценария вы можете использовать переводы, предоставленные данными локали, включенными в Babel, на основе данных CLDR.
Давайте посмотрим на некоторые примеры…
Создайте имя файла loc.py
и добавьте следующий код:
from babel import Locale # Parsing l = Locale.parse('de-DE', sep='-') print("Locale name: {0}".format(l.display_name)) l = Locale.parse('und_GR', sep='_') print("Locale name: {0}".format(l.display_name)) # Detecting l = Locale.negotiate(['de_DE', 'en_AU'], ['de_DE', 'de_AT']) print("Locale negociated: {0}".format(l.display_name)) print(Locale('it').english_name) print(Locale('it').get_display_name('fr_FR')) print(Locale('it').get_language_name('de_DE')) print(Locale('de', 'DE').languages['zh']) print(Locale('el', 'GR').scripts['Copt']) # Calendar locale = Locale('it') month_names = locale.days['format']['wide'].items() print(list(month_names))
Мы показываем несколько примеров класса Locale. Он используется для печати, согласования или идентификации языковых тегов. Если вы запустите этот пример, вы увидите следующий вывод:
$ python loc.py Locale name: Deutsch (Deutschland) Locale name: Ελληνικά (Ελλάδα) Locale negociated: Deutsch (Deutschland) Italian italien Italienisch Chinesisch Κοπτικό [(6, 'domenica'), (0, 'lunedì'), (1, 'martedì'), (2, 'mercoledì'), (3, 'giovedì'), (4, 'venerdì'), (5, 'sabato')]
Они полезны, поскольку они предоставляются набором данных (CLDR) и не нуждаются в переводе.
Кроме того, есть несколько функций, которые форматируют дату, время, валюту, единицы измерения и т. д. Давайте посмотрим на несколько примеров:
from babel.dates import format_date, format_datetime, format_time from babel.numbers import format_number, format_decimal, format_percent, parse_decimal from babel.units import format_unit from datetime import date, datetime, time # Date, time d = date(2010, 3, 10) print(format_date(d, format='short', locale='it')) print(format_date(d, format='full', locale='it')) print(format_date(d, "EEEE, d.M.yyyy", locale='de')) dt = datetime.now() print(format_datetime(dt, "yyyy.MMMM.dd GGG hh:mm a", locale='en')) # Numbers/ Units print(format_decimal(123.45123, locale='en_US')) print(format_decimal(123.45123, locale='de')) print(format_unit(12, 'length-meter', locale='en_GB')) print(format_unit(12, 'length-meter', locale='en_US')) parse_decimal('2.029,98', locale='de')
Результат:
$ python loc.py 10/03/10 mercoledì 10 marzo 2010 Mittwoch, 10.3.2010 2018.August.28 AD 10:24 AM 123.451 123,451 12 metres 12 meters 2029.98
Извлечение сообщения
Babel имеет механизм извлечения, аналогичный gettext. Он работает, просматривая указанные каталоги, и на основе правил конфигурации применяет функции извлечения к этим файлам. Этот способ является более гибким, чем gettext, поскольку вы можете использовать мощь выражений Python для расширения инструмента.
Babel поставляется с несколькими встроенными экстракторами, такими как python, javascript и ignore (который ничего не извлекает), и вы можете создавать свои собственные экстракторы, и есть два разных интерфейса для доступа к этой функции:
- Интерфейс командной строки
- Интеграция Distutils/Setuptools
В этом уроке мы будем использовать интерфейс командной строки.
Чтобы использовать его, просто вызовите инструмент pybabel
, например, чтобы распечатать все известные локали.
$ pybabel --list-locales:
Чтобы на самом деле использовать инструменты, давайте пройдемся по процессу извлечения сообщений с помощью pybabel
:
Создайте файл с именем main.py
и добавьте следующий код:
import gettext _ = gettext.gettext print(_('This is a translatable string.'))
Обратите внимание, что использование gettext
удобно только потому, что экстрактор по умолчанию использует gettext
за кулисами, но Babel, в общем, не привязан к этому.
Используйте команду paybabel extract
для создания начального каталога сообщений:
$ mkdir locale $ pybabel extract . -o locale/base.pot
Это создаст базовый файл pot, который будет содержать следующие сообщения:
#: main.py:4 msgid "This is a translatable string." msgstr ""
Теперь вам не нужно редактировать этот файл.
Используя команду init
, мы можем создать новый каталог переводов на основе этого файла шаблона POT
:
$ pybabel init -l el_GR de_DE en_US -i locale/base.pot -d locale creating catalog locale/el_GR/LC_MESSAGES/messages.po based on locale/base.pot $ pybabel init -l de_DE en_US -i locale/base.pot -d locale creating catalog locale/de_DE/LC_MESSAGES/messages.po based on locale/base.pot $ pybabel init -l en_US -i locale/base.pot -d locale creating catalog locale/en_US/LC_MESSAGES/messages.po based on locale/base.pot
Эти файлы готовы к переводу. Когда переводы будут выполнены, вы можете использовать команду компиляции, чтобы превратить их в MO
файлов:
$ pybabel compile -d locale compiling catalog locale/en_US/LC_MESSAGES/messages.po to locale/en_US/LC_MESSAGES/messages.mo compiling catalog locale/el_GR/LC_MESSAGES/messages.po to locale/el_GR/LC_MESSAGES/messages.mo compiling catalog locale/de_DE/LC_MESSAGES/messages.po to locale/de_DE/LC_MESSAGES/
Теперь, если вы внесете изменения в файл base.pot, вы сможете обновить остальные с помощью команды update
. Например, добавьте следующую строку в base.pot
msgid "Hello world." msgstr ""
Интеграция с шаблонами Jinja2
Babel может интегрироваться с шаблонами Jinja, используя расширение jinja2.ext.i18n
.
Затем его можно использовать для маркировки и перевода сообщений из шаблонов, а также для интернационализации HTML-страниц.
Чтобы интегрировать оба этих инструмента вместе, вам необходимо предоставить некоторую конфигурацию.
Сначала установите jinja с помощью pip:
$ pip install Jinja2
Вам нужно указать babel анализировать шаблоны jinja при извлечении сообщений, и для этого вам нужно добавить файл конфигурации.
Создайте файл с именем babel-mapping.ini
и добавьте следующий текст:
[python: **.py] [jinja2: **/templates/**.html] extensions=jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_
Итак, теперь, когда вы вызываете pypabel
команд, ссылающихся на этот файл, он также будет извлекать сообщения из шаблонов Jinja.
Давайте посмотрим, как мы можем загрузить шаблоны Babel и Jinja вместе:
Создайте шаблон с именемindex.html
, который будет использоваться для извлечения наших сообщений:
<title>{% trans %}{{title}}{% endtrans %}</title> <p>{% trans count=mailservers|length %} There is {{ count }} {{ name }} server. {% pluralize %} There are {{ count }} {{ name }} servers. {% endtrans %} </p>
Вызовите следующую команду для извлечения базовых сообщений:
¢ pybabel extract -F babel-mapping.ini -o locale/messages.pot ./
Это создаст следующий каталог:
# Translations template for PROJECT. # Copyright (C) 2018 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR <EMAIL@ADDRESS>, 2018. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2018-08-29 10:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <[email protected]>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" #: templates/index.html:1 #, python-format msgid "%(title)s" msgstr "" #: templates/index.html:2 #, python-format msgid "" "\n" " There is %(count)s %(name)s server.\n" " " msgid_plural "" "\n" " There are %(count)s %(name)s servers.\n" " " msgstr[0] "" msgstr[1] ""# Translations template for PROJECT. # Copyright (C) 2018 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR <EMAIL@ADDRESS>, 2018. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2018-08-29 10:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <[email protected]>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.6.0\n" #: templates/index.html:1 #, python-format msgid "%(title)s" msgstr "" #: templates/index.html:2 #, python-format msgid "" "\n" " There is %(count)s %(name)s server.\n" " " msgid_plural "" "\n" " There are %(count)s %(name)s servers.\n" " " msgstr[0] "" msgstr[1] ""
Теперь инициализируйте итальянские переводы с помощью команды init и укажите переводы:
$ pybabel init -d locale -l it -i locale/messages.pot creating catalog locale/it/LC_MESSAGES/messages.po based on locale/messages.pot
Запустите команду компиляции, чтобы сгенерировать файлы MO:
$ pybabel compile -d locale -l it compiling catalog locale/it/LC_MESSAGES/messages.po to locale/it/LC_MESSAGES/messages.mo
Создайте файл с именем app.py
, чтобы соединить все вместе:
from jinja2 import Environment, FileSystemLoader, select_autoescape from babel.support import Translations templateLoader = FileSystemLoader( searchpath="templates" ) env = Environment( loader=templateLoader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], autoescape=select_autoescape(['html', 'xml']) ) translations = Translations.load('locale', ['it']) env.install_gettext_translations(translations) template = env.get_template('index.html') print(template.render(mailservers=range(10), name='mail'))
Мы используем компонент Translations
для загрузки каталогов сообщений, которые мы скомпилировали ранее. Затем мы загружаем их в среду Jinja с помощью метода install_gettext_translations
. Затем мы визуализируем шаблон.
Если вы запустите эту программу, вы увидите следующий вывод:
$ python app.py <title>Titolo</title> <p>Esistono 10 mail servers. </p>
Если мы хотим изменить локаль, нам нужно проделать ту же процедуру. Например:
translations = Translations.load('locale', ['en_US']) env.install_gettext_translations(translations) template = env.get_template('index.html') print(template.render())
Добавление контекстного редактора фраз с помощью Flask и Babel
Мы также можем ввести Flask в картину и интегрировать Контекстный редактор фраз, используя технику замены вызовов gettext для среды jinja.
Во-первых, нам нужно установить необходимые пакеты:
$ pip install flask, flask-babel
Создайте файл с именем web.py
и добавьте следующий код:
from flask import Flask, render_template, request from flask_babel import Babel from phrase import Phrase, gettext, ngettext class Config(object): LANGUAGES = { 'it': 'Italian', 'de_DE': 'Deutsch' }, BABEL_DEFAULT_LOCALE= 'it' PHRASEAPP_ENABLED = True PHRASEAPP_PREFIX = '{{__' PHRASEAPP_SUFFIX = '__}}' app = Flask(__name__) app.config.from_object(Config) babel = Babel(app) phrase = Phrase(app) @babel.localeselector def get_locale(): return request.accept_languages.best_match(app.config['LANGUAGES'][0].keys()) @app.route('/') def index(): return render_template('index.html', locale=get_locale() or babel.default_locale)
Сначала мы настраиваем некоторую конфигурацию, чтобы определить список поддерживаемых языков и специальные ключи PHRASEAPP_*. Что нужно контекстному редактору, так это обернуть переводимые строки определенными тегами '{{__' и'__}}' .
Теперь давайте посмотрим содержимое файла phrase.py
'{{__
и __}}'
.
from __future__ import print_function try: from flask_babel import gettext as gettext_original, ngettext as ngettext_original from flask import current_app except ImportError: print("Flask-Babel is required.") class Phrase(object): def __init__(self, app=None): self.app = app app.jinja_env.install_gettext_callables( gettext, ngettext, newstyle=True ) def phrase_enabled(): return current_app.config['PHRASEAPP_ENABLED'] def phrase_key(msgid): return current_app.config['PHRASEAPP_PREFIX'] + 'phrase_' + msgid + current_app.config['PHRASEAPP_SUFFIX'] def gettext(msgid): if phrase_enabled(): return phrase_key(msgid) else: return gettext_original(msgid) def ngettext(msgid1, msgid2, n, **kwargs): if phrase_enabled(): return phrase_key(msgid1) else: return ngettext_original(msgid1, msgid2, n, **kwargs)
Нам нужно убедиться, что мы передаем исходные параметры исходным функциям gettext на случай, если мы отключим редактор.
Обновите index.html
file, чтобы включить скрипт для загрузки редактора:
<!DOCTYPE html> <html lang="{{locale}}"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>{% trans %}title{% endtrans %}</title> </head> <body> <p>{% trans %}title{% endtrans %}</p> window.PHRASEAPP_CONFIG = { projectId: "YOUR-PROJECT-ID" }; (function() { var phraseapp = document.createElement('script'); phraseapp.type = 'text/javascript'; phraseapp.async = true; phraseapp.src = ['https://', 'phraseapp.com/assets/in-context-editor/2.0/app.js?', new Date().getTime()].join(''); var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(phraseapp, s); })(); </body> </html>
Если вы еще этого не сделали, перейдите на https://phrase.com/ и зарегистрируйтесь, чтобы попробовать его бесплатно.
После настройки учетной записи вы можете создать проект и перейти к настройкам проекта, чтобы найти ключ projectId.
Используйте это, чтобы назначить переменную среды PHRASE_APP_TOKEN
перед запуском сервера.
Когда вы перейдете на страницу, вы увидите модальное окно входа в систему, и после аутентификации вы увидите, что переведенные строки меняются, и рядом с ними появляются кнопки редактирования. Панель редактора In-Context также будет отображаться.
$ export FLASK_APP=web.py $ flask run
Вывод
В этой статье мы увидели, как выполнить локализацию Python с помощью библиотеки Babel Python. Мы также увидели, как мы можем интегрировать его с шаблонами Jinja и контекстным редактором фраз в наш рабочий процесс. Если у вас остались другие вопросы, не стесняйтесь оставлять комментарии или напишите мне. Спасибо за прочтение и до новых встреч в следующий раз!
Первоначально опубликовано в The Phrase Blog.