языковые файлы Flask, Babel и Javascript
В этой заметке описывается способ генерации языковых файлов Javascript de.js, en.js и т.д., а также способ их добавления в ваше мультиязычное приложение Flask .
Данный веб-сайт Flask является многоязычным. Реализация описана в предыдущих сообщениях. До сих пор все мои переводы были в коде Python и в шаблонах HTML . В нескольких местах мне понадобились некоторые переводы в Javascript и я сделал это, потянув этот Javascript код внутри шаблона HTML . Например, для форм, которые мне понадобились:
e.target.setCustomValidity('Please fill out this field.');
Я вытянул этот Javascript в шаблон HTML и изменил его на HTML :
e.target.setCustomValidity("{{ _('Please fill out this field.') }}");
Это было легко и отлично работает.
Язык в файлах Javascript
Я знал, что однажды мне придется изменить это и реализовать мультиязык для файлов Javascript . В этот день камера появилась раньше, чем ожидалось, потому что я хотел реализовать заголовок Content Security Policy . Минимум, что мы должны сделать, это удалить встроенные сценарии и функцию eval() :
Content-Security-Policy: script-src 'self'
de.js
en.js
es.js
fr.js
nl.js
ru.js
<script src="{{ url_for('static', filename='js/mlmanager.js') }}?v={{ et }}"></script>
<script src="{{ url_for('static', filename='js/locales/' + lang_code + '.js') }}?v={{ et }}"></script>
<script src="{{ url_for('static', filename='js/base.js') }}?v={{ et }}"></script>
// mlmanager.js
var ML = function(params){
this.key2translations = {};
this.keys = []
if(params.hasOwnProperty('key2translations')){
this.key2translations = params.key2translations;
this.keys = Object.keys(this.key2translations);
}
this.t = function(k){
if(this.keys.indexOf(k) === -1){
alert('key = ' + k + ' not found');
return;
}
s = this.key2translations[k];
return s.replace(/"/g,'\"');
};
};
При создании нового объекта ml мы передаем также переводы. Метод t используется для получения перевода. Переведенный языковой файл, например, de.js, выглядит так:
// de.js
var ml = new ML({
'key2translations': {
'Content item': "Inhaltselement",
'Please fill out this field.': "Bitte füllen Sie dieses Feld aus.",
},
});
Наконец, в файле с реальным кодом Javascript , base.js, мы изменяем текст, который должен быть переведен:
e.target.setCustomValidity('Please fill out this field.');
к:
e.target.setCustomValidity( ml.t('Please fill out this field.') );
Проблема: как сгенерировать языковые файлы Javascript de.js, en.js и др.
В стандартной документации Babel упоминаются только такие команды, как init, extract, update, compile. Нам нужен способ:
- извлекать тексты для перевода из файлов javascript
- автоматически генерируют языковые файлы de.js, en.js и др.
Извлечь тексты для перевода из файлов Javascript
Я решил не сканировать файлы Javascript , а вместо этого создать новый файл HTML (шаблон), jsbase.html, содержащий все тексты для файлов Javascript , например:
var ml = new ML({
'key2translations': {
...
'Content item': "{{ _('Content item') }}",
'Please fill out this field.': "{{ _('Please fill out this field.') }}",
...
},
});
Мы поместили этот файл в каталог шаблонов так, чтобы он был просканирован Babel , когда мы выпустим стандартные команды перевода:
pybabel extract -F babel.cfg -k _l -o messages.pot .
pybabel update -i messages.pot -d app/translations
# do yourself: translate all texts in the po files either manual or automated
pybabel compile -d app/translations
Теперь у нас есть переведённые тексты для файлов Javascript где-то в файлах messages.po . Вы можете проверить это, например, дампом файла messages.po :
from babel.messages.pofile import read_po
import os
def show_catalog(lc):
lc_po_file = os.path.join('app_frontend', 'translations', lc, 'LC_MESSAGES', 'messages.po')
# catalog = read_po(open(lc_po_file, mode='r', encoding='utf-8'))
# without encoding parameter works if the default encoding of the platform is utf-8
catalog = read_po(open(lc_po_file, 'r'))
for message in catalog:
print('message.id = {}, message.string = {}'.format(message.id, message.string))
show_catalog('de_DE')
При этом печатается список идентификаторов сообщений и строк:
...
message.id = Sub image, message.string = Unterbild
message.id = Sub image text, message.string = Unterbildtext
message.id = Select image, message.string = Bild auswählen
...
Автоматически генерировать языковые файлы Javascript de.js, en.js и др.
Нам нужен способ перевести этот jsbase.html за пределами Flask на наши языки и сгенерировать файлы de.js, en.js и др. Мы могли бы использовать приведенный выше код для получения текстов из файлов Javascript и генерировать языковые файлы de.js, en.js, и т.д. Но это громоздко и склонно к ошибкам.
Затем я отскочил в способ отрисовки шаблона вне Flask, смотрите ссылки ниже. Идея заключается в том, чтобы отрисовать шаблон jsbase.html, позволяющий Babel помещать в него правильные переводы. Тогда всё, что нам нужно сделать, это записать результат рендеринга в языковые файлы de.js, en.js и др. Неужели это так просто? Вот код, который делает это:
from jinja2 import Environment, FileSystemLoader, select_autoescape
from babel.support import Translations
import os
import sys
def generate_translated_js_file(
app_translations_dir,
language_region_code,
app_templates_dir,
template_file,
js_translation_file):
template_loader = FileSystemLoader(app_templates_dir)
# setup environment
env = Environment(
loader=template_loader,
extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'],
autoescape=select_autoescape(['html', 'xml'])
)
translations = Translations.load(app_translations_dir, language_region_code)
env.install_gettext_translations(translations)
template = env.get_template(template_file)
rendered_template = template.render()
with open(js_translation_file, 'w') as f:
f.write(rendered_template)
Эта функция загружает выбранный язык, использует render() для помещения перевода и записывает результат как de.js, en.js и т.д.. Обратите внимание, что в моей установке я использую несколько приложений, app_frontend, app_admin, используя DispatcherMiddleware. Для генерации всех языковых файлов Javascript для всех приложений и языков я вызываю вышеуказанную функцию в другой функции:
def generate_translated_js_files():
# app translations directory has subdirectories de_DE, en_US, es_ES, ...
# lang_code is language code used in the Flask app
language_region_code2lang_codes = {
'de_DE': 'de',
'en_US': 'en',
'es_ES': 'es',
'fr_FR': 'fr',
'nl_NL': 'nl',
'ru_RU': 'ru',
}
template_file = 'jsbase.html'
for app_name in ['app_frontend', 'app_admin']:
# app/translations
app_translations_dir = os.path.join(app_name, 'translations')
# app/templates
app_templates_dir = os.path.join(app_name, 'templates')
for language_region_code, lang_code in language_region_code2lang_codes.items():
if not os.path.isdir( os.path.join(app_translations_dir, language_region_code)):
print('error: not a directory = {}'.format( os.path.isdir( os.path.join(app_translations_dir, language_region_code) )))
sys.exit()
# shared/static/js/locales is the directory where we write de.js, en.js, etc.
js_translation_file = os.path.join('shared', 'static', 'js', 'locales', lang_code + '.js')
# translate
generate_translated_js_file(
app_translations_dir,
language_region_code,
app_templates_dir,
template_file,
js_translation_file)
# do it
generate_translated_js_files()
Обратите внимание, что на данный момент это немного удваивается, так как фронтенд и админ имеют один и тот же статический каталог.
Проблемы
Конечно, есть проблемы. Когда код Javascript был в шаблоне HTML , я добавил код Jinja :
{% if ... %}
...
{% else %}
...
{% endif %}
использовать определенную часть кода Javascript . Мы больше не можем этого делать... :-(. Если быть более точным, то в моем случае Javascript вызывает другую страницу с url, которая может существовать или не существовать, и url также зависит от языка. Например, ссылка в строчном коде Javascript шаблона HTML выглядит так:
{% if 'Privacy policy' in app_template_slug %}
moreLink: '{{ url_for('pages.page_view', slug=app_template_slug['Privacy policy']['slug']) }}',
{% else %}
moreLink: '',
{% endif %}
То, что я сделал, это полностью переписал согласие на печенье. На данный момент HTML больше не генерируется в Javascript , а в HTML . Ему все еще нужно больше работы.
Резюме
Это первая реализация, но она отлично работает. Магия использует Babel и Jinja API. Возможные улучшения:
Вместо того, чтобы иметь строки в качестве индекса для перевода:
ml.t('Please fill out this field')
мы, возможно, захотим использовать объекты:
ml.t( t.Please_fill_out_this_field )
И вместо того, чтобы иметь переводы Javascript файла с объектами Javascript , мы можем захотеть использовать JSON файл, содержащий только переводы. В любом случае, следующими шагами будет выборочное добавление дополнительных пользовательских файлов Javascript и больше переводов.
Ссылки / кредиты
Analyse your HTTP response headers
https://securityheaders.com
Babel documentation
https://readthedocs.org/projects/python-babel/downloads/pdf/latest/
Best practice for localization and globalization of strings and labels [closed]
https://stackoverflow.com/questions/14358817/best-practice-for-localization-and-globalization-of-strings-and-labels/14359147
Content Security Policy - An Introduction
https://scotthelme.co.uk/content-security-policy-an-introduction/
Explore All i18n Advantages of Babel for Your Python App
https://phrase.com/blog/posts/i18n-advantages-babel-python/
Give your JavaScript the ability to speak many languages
https://github.com/airbnb/polyglot.js
Подробнее
Babel Flask Javascript Jinja2
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу