В миналото дадохме подробни уроци за инструменти за Gettext, както и за интегриране на Gettext с Python. Ще разширим знанията си, като използваме Babel и ще видим някои практически примери за използването му в python i18n. Също така ще видим как да го интегрираме с шаблони Jinja2 и как да интегрираме Phrase's In-Context Editor в приложение на 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 Common Locale Data Repository е стандартизирано хранилище на локални данни, използвани за форматиране, анализиране и показване на специфична за локала информация. Вместо да превеждате, например, имена на дни или имена на месеци за конкретен език или писменост, можете да използвате преводите, предоставени от локалните данни, включени в 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
Това ще създаде основен пот файл, който ще съдържа следните съобщения:
#: 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 в картината и да интегрираме Phrase in-text editor чрез използване на техника за замяна на gettext callables за средата на 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
, преди да стартирате сървъра.
Когато навигирате до страницата, ще видите модал за влизане и след като бъдете удостоверени, ще видите преведените низове да се променят, за да включват бутони за редактиране до тях. Панелът за редактор в контекст също ще се покаже.
$ export FLASK_APP=web.py $ flask run
Заключение
В тази статия видяхме как да направим локализация на Python с помощта на библиотеката babel на Python. Видяхме също как можем да го интегрираме с шаблони на Jinja и редактора в контекста на Phrase в нашия работен процес. Ако имате други въпроси, не се колебайте да публикувате коментар или да ми пишете. Благодарим ви за четенето и до нови срещи следващия път!
Първоначално публикувано в The Phrase Blog.