Главная » Хабрахабр » Мега-Учебник Flask, Часть XIV: Ajax

Мега-Учебник Flask, Часть XIV: Ajax

(издание 2018)

Miguel Grinberg


Туда Сюда

Это четырнадцатая часть Мега-Учебника Flask,k, в которой я собираюсь добавить функцию перевода текста в реальном времени, используя службу перевода Microsoft и немного JavaScript.

Под спойлером приведен список всех статей этой серии 2018 года.

Оглавление

Примечание 1: Если вы ищете старые версии данного курса, это здесь.

Примечание 2: Если вдруг Вы захотели бы выступить в поддержку моей(Мигеля) работы, или просто не имеете терпения дожидаться статьи неделю, я (Мигель Гринберг)предлагаю полную версию данного руководства(на английском языке) в виде электронной книги или видео. Для получения более подробной информации посетите learn.miguelgrinberg.com.

В этой статье я собираюсь отправиться в «безопасную зону» разработки на стороне сервера и работать над функцией, которая имеет одинаково важные серверные и клиентские компоненты. Вы видели ссылки «Перевести», которые некоторые сайты показывают рядом с созданным пользователем контентом? Это ссылки, которые запускают автоматический перевод контента отличающегося от родного языка пользователя в режиме реального времени. Переведенный контент обычно вводится ниже исходной версии. Google показывает результаты поиска на иностранных языках. Facebook делает это для сообщений. Twitter делает это для твитов. Сегодня я покажу вам, как добавить такую ​​же особенность в Microblog!

Ссылки GitHub для этой главы: Browse, Zip, Diff.

Серверная и клиентская стороны

В традиционной серверной модели, которой я следовал до сих пор, есть клиент (веб-браузер, которым управляет пользователь), выполняющий HTTP-запросы к серверу приложений. Запрос может просто запросить HTML-страницу, например, когда вы нажимаете ссылку "профиль", или он может инициировать действие, например, когда вы нажимаете кнопку Отправить (Submit) после редактирования информации профиля. В обоих типах запросов сервер выполняет запрос путем отправки новой веб-страницы клиенту, либо непосредственно, либо путем перенаправления. Затем клиент заменяет текущую страницу новой. Этот цикл повторяется, пока пользователь остается на веб-сайте приложения. В этой модели сервер делает всю работу, в то время как клиент просто отображает веб-страницы и принимает пользовательский ввод.

Существует другая модель, в которой клиент принимает более активную роль. В этой модели клиент выдает запрос серверу, а сервер отвечает веб-страницей, но в отличие от предыдущего случая, не все данные страницы являются HTML, есть также разделы страницы с кодом, обычно написанные на Javascript. Как только клиент получает страницу, он отображает фрагменты HTML и выполняет код. С этого момента у вас есть активный клиент, который может работать самостоятельно, без какого-либо контакта с сервером. В строгом клиентском приложении всё приложение загружается на клиент с запросом начальной страницы, а затем приложение выполняется полностью на клиенте, только изредка связываясь с сервером для получения или хранения данных и внесения динамических изменений во внешний вид только первой и единственной веб-страницы. Этот тип приложений называется Single Page Applications или SPA.

Большинство приложений являются гибридом между двумя моделями и сочетают технологии обоих. Мое приложение Microblog в основном является серверным приложением, но сегодня я добавлю к нему немного действий на стороне клиента. Чтобы выполнять переводы сообщений в режиме реального времени, клиентский браузер будет отправлять асинхронные запросы на сервер, на которые сервер будет отвечать, не вызывая обновления страницы. Затем клиент будет динамически вставлять переводы в текущую страницу. Этот метод известен как Ajax, что является сокращением для асинхронного JavaScript и XML (хотя в наши дни XML часто заменяется JSON).

Рабочий процесс перевода в реальном времени

Приложение имеет хорошую поддержку иностранных языков благодаря Flask-Babel, что позволит поддерживать столько языков, сколько я смогу найти переводчиков. Но, конечно, один элемент отсутствует. Пользователи будут писать сообщения в блогах на своих родных языках. Поэтому вполне возможно, что пользователь столкнется с постами, которые написаны на неизвестных ему языках. Качество автоматизированных переводов не всегда велико, но в большинстве случаев это достаточно хорошо, если все, что вы хотите, — иметь базовое представление о том, что означает текст на другом языке.

Это идеальная возможность для реализации службы AJAX. Учтите, что страницы index или explore могут показывать несколько сообщений, некоторые из которых могут быть на иностранных языках. Если я реализую перевод с использованием традиционных методов на стороне сервера, запрос на перевод приведет к замене исходной страницы новой страницей. Дело в том, что запрос на перевод одного из многих отображаемых сообщений в блогах не является достаточно большим, чтобы требовать полного обновления страницы, эта функция работает намного лучше, если переведенный текст динамически вставляется под исходным текстом, оставляя остальную часть страницы нетронутой.

Для реализации автоматизированных переводов в реальном времени требуется несколько шагов. Во-первых, мне нужен способ определить исходный язык текста для перевода. Мне также нужно знать предпочтительный язык для каждого пользователя, потому что я хочу показать ссылку «перевести» только для сообщений, написанных на других языках. Когда будет предложена ссылка на перевод и пользователь нажмет на нее, мне нужно будет отправить AJAX-запрос на сервер, и сервер свяжется с сторонним API перевода. После того как сервер отправит ответ с переведенным текстом, клиентский код javascript будет динамически вставлять этот текст в страницу. Как вы наверняка заметили, тут несколько нетривиальных проблем. Рассмотрим их одну за другой.

Language Identification

Первой проблемой является определение того, на каком языке была написана запись. Это не точная наука, поскольку не всегда можно однозначно определить язык, но в большинстве случаев автоматическое обнаружение работает достаточно хорошо. В Python есть хорошая библиотека регистрации языка, называемая guess_language. Оригинальная версия этого пакета довольно старая и никогда не была перенесена на Python 3, поэтому я собираюсь установить производную версию, которая поддерживает Python 2 и 3:

(venv) $ pip install guess-language_spirit

План состоит в том, чтобы скормить каждый пост в блоге этому пакету, чтобы попытаться определить язык. Поскольку этот анализ занимает много времени, я не хочу повторять эту работу каждый раз, когда сообщение отображается на странице. То, что я собираюсь сделать, — это установить исходный язык для сообщения во время его отправки. Обнаруженный язык затем будет храниться в таблице Posts.

Первым шагом является добавление поля language в модель Post:

app/models.py: Add detected language to Post model.

class Post(db.Model): # ... language = db.Column(db.String(5))

Как вы помните, каждый раз, когда происходят изменения в моделях баз данных, необходимо выполнить миграцию базы данных:

(venv) $ flask db migrate -m "add language to posts"
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'post.language' Generating migrations/versions/2b017edaa91f_add_language_to_posts.py ... done

Затем необходимо применить миграцию к базе данных:

(venv) $ flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Upgrade ae346256b650 -> 2b017edaa91f, add language to posts

Теперь я могу обнаружить и сохранить язык при отправке сообщения:

app/routes.py: Save language for new posts.

from guess_language import guess_language @app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
@login_required
def index(): form = PostForm() if form.validate_on_submit(): language = guess_language(form.post.data) if language == 'UNKNOWN' or len(language) > 5: language = '' post = Post(body=form.post.data, author=current_user, language=language) # ...

При этом изменении каждый раз, когда отправляется сообщение, я пропускаю текст через функцию guess_language, чтобы попытаться определить язык. Если язык возвращается как unknown (неизвестно) или я получаю неожиданно длинный результат, я перестраховываюсь и сохраняю пустую строку в базе данных. Я собираюсь принять соглашение о том, что любое сообщение, у которого есть язык, с присвоенным значением пустая строка, то предполагается, что у него неизвестный язык.

Отображение ссылки «Перевести»

Второй шаг очень прост. Я добавлю ссылку «Перевести» рядом с любыми сообщениями, которые отличаются от языка, который активен для текущего пользователя.

app/templates/_post.html: Add a translate link to posts.

{% if post.language and post.language != g.locale %}
<br><br>
<a href="#">{{ _('Translate') }}</a>
{% endif %}

Я делаю это в подшаблоне _post.html, так чтобы эта функциональность отображалась на любой странице, отображающей записи блога. Ссылка на перевод будет отображаться только на сообщениях, для которых был обнаружен язык, и этот язык не соответствует языку, выбранному функцией, декорированной localeselector Flask-Babel. Напомним из главы 13, что выбранная локаль хранится как g.locale. Текст ссылки должен быть добавлен таким образом, что он может быть переведен Flask-Babel, поэтому я использую функцию _(), для её определения.

Обратите внимание, что я еще не связал действие с этой ссылкой. Сначала я хочу выяснить, как выполнять фактические переводы.

Использование сторонней службы перевода

Двумя основными службами перевода являются Google Cloud Translation API и Microsoft Translator Text API. Обе платные услуги, но предложение Microsoft имеет опцию начального уровня для небольшого объема переводов, что является бесплатным. Google предлагал бесплатную услугу перевода в прошлом, но сегодня, даже самый низкий уровень обслуживания оплачивается. Поскольку я хочу иметь возможность экспериментировать с переводами, не неся расходов, я собираюсь реализовать решение Microsoft.

Прим. Перводчика: Есть еще Yandex о котором Мигель, похоже не знает. Не самый плохой API! Есть бесплатный вариант до 1 М символов в день и 10 М в месяц против 2 М в месяц у микрософта.

Прежде чем использовать Microsoft Translator API, необходимо получить учетную запись в Azure, облачной службы Майкрософт. Вы можете выбрать уровень Free, во время предложения предоставить номер кредитной карты в процессе регистрации. Плата с вашей картой не будет взиматься, пока вы остаетесь на этом уровне обслуживания.

После того как у вас появится учетная запись Azure, перейдите на портал Azure и нажмите кнопку "New" в левом верхнем углу, а затем введите или выберите "Translator Text API". При нажатии кнопки "Create" вам будет представлена форма, в которой вы определите новый ресурс транслятора, который будет добавлен в ваш аккаунт. Ниже вы можете увидеть, как я заполнил форму:

При повторном нажатии кнопки "Create" ресурс API-интерфейса переводчика будет добавлен в ваш аккаунт. Если вы подождете несколько секунд, вы получите уведомление в строке сверху панели, что переводчик был развернут. Нажмите кнопку "Go to resource" в уведомлении, а затем на "Keys" опцию на левой боковой панели. Теперь вы увидите два ключа, помеченные как "Key 1" и "Key 2". Скопируйте один из ключей в буфер обмена, а затем введите его в переменную среды в вашем терминале (если вы используете Microsoft Windows, замените export на set):

(venv) $ export MS_TRANSLATOR_KEY=<paste-your-key-here>

Этот ключ используется для аутентификации с помощью службы перевода, поэтому его необходимо добавить в конфигурацию приложения:

config.py: Add Microsoft Translator API key to the configuration.

class Config(object): # ... MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY')

Как всегда со значениями конфигурации, я предпочитаю устанавливать их в переменные окружения и импортировать их в конфигурацию Flask оттуда. Это особенно важно при наличии конфиденциальной информации, такой как ключи или пароли, которые обеспечивают доступ к службам сторонних производителей. Вы определенно не хотите писать их явно в коде.

Microsoft Translator API-это веб-служба, которая принимает запросы HTTP. В Python есть несколько HTTP-клиентов, но самым популярным и простым в использовании является пакет requests. Давайте установим его в виртуальную среду:

(venv) $ pip install requests

Ниже вы можете увидеть функцию, которую я кодировал для перевода текста с помощью API-интерфейса Microsoft Translator. Я добавляю новый модуль app/translate.py:

app/translate.py: Text translation function.

import json
import requests
from flask_babel import _
from app import app def translate(text, source_language, dest_language): if 'MS_TRANSLATOR_KEY' not in app.config or \ not app.config['MS_TRANSLATOR_KEY']: return _('Error: the translation service is not configured.') auth = {'Ocp-Apim-Subscription-Key': app.config['MS_TRANSLATOR_KEY']} r = requests.get('https://api.microsofttranslator.com/v2/Ajax.svc' '/Translate?text={}&from={}&to={}'.format( text, source_language, dest_language), headers=auth) if r.status_code != 200: return _('Error: the translation service failed.') return json.loads(r.content.decode('utf-8-sig'))

Функция принимает текст для перевода, а исходный и целевой языки кодируются как аргументы, и возвращает строку с переведенным текстом. Действие начинается с проверки наличия ключа для службы перевода в конфигурации, и если он не существует, то возвращает ошибку. Ошибка также является строкой, поэтому со стороны это будет выглядеть как переведенный текст. Это гарантирует, что в случае ошибки пользователь увидит сообщение об ошибке.

Метод get() из пакета requests отправляет HTTP-запрос с методом GET на URL-адрес, указанный в качестве первого аргумента. Я использую URL /v2/Ajax.svc/Translate, который является конечной точкой службы перевода, которая возвращает переводы в качестве данных JSON. Текстовые, исходный и целевой языки должны указываться в качестве аргументов строки запроса в URL-адресе с именем text, from и to соответственно. Чтобы выполнить аутентификацию с помощью службы, мне нужно передать ключ, который я добавил в конфигурацию. Этот ключ необходимо указать в пользовательском HTTP-заголовке с именем Ocp-Apim-Subscription-Key. Я создал словарь auth с этим заголовком, а затем передал его запросам в headers аргументе.

Метод requests.get() возвращает объект response, содержащий все сведения, предоставленные службой. Сначала нужно проверить, что код состояния 200, который является кодом успешного запроса. Если я получаю другие коды, значит, произошла ошибка, поэтому в этом случае я возвращаю строку ошибки. Если код состояния 200, то тело ответа имеет строку в кодировке JSON с переводом, поэтому все, что мне нужно сделать, это использовать функцию json.loads() из стандартной библиотеки Python для декодирования JSON в строку Python, которую я мог бы использовать. Атрибут объекта ответа content содержит необработанный текст ответа в качестве байтстроки, который преобразовывается в строку utf-8 и отправил в json.loads().

Ниже вы можете увидеть сеанс консоли Python, в котором я использую новую функцию translate():

>>> from app.translate import translate
>>> translate('Hi, how are you today?', 'en', 'es') # English to Spanish 'Hola, ¿cómo estás hoy?'
>>> translate('Hi, how are you today?', 'en', 'de') # English to German 'Are Hallo, how you heute?'
>>> translate('Hi, how are you today?', 'en', 'it') # English to Italian 'Ciao, come stai oggi?'
>>> translate('Hi, how are you today?', 'en', 'fr') # English to French "Salut, comment allez-vous aujourd'hui ?"

Довольно круто, правда? Теперь пришло время интегрировать эту функциональность с приложением.

Ajax From The Server

Я начну с реализации серверной части. Когда пользователь нажимает ссылку «Перевести», которая появляется под сообщением, на сервер выдается асинхронный HTTP-запрос. Я покажу вам, как это сделать в следующем сеансе, поэтому на данный момент я сосредоточусь на реализации обработки этого запроса сервером.

Асинхронный (или Ajax) запрос похож на маршруты и функции просмотра, которые я создал в приложении, с той лишь разницей, что вместо возврата HTML или перенаправления он просто возвращает данные, отформатированные как XML или чаще JSON. Ниже вы можете увидеть функцию просмотра перевода, которая вызывает API-интерфейс Microsoft Translator, а затем возвращает переведенный текст в формате JSON:

app/routes.py: Text translation view function.

from flask import jsonify
from app.translate import translate @app.route('/translate', methods=['POST'])
@login_required
def translate_text(): return jsonify({'text': translate(request.form['text'], request.form['source_language'], request.form['dest_language'])})

Как вы можете видеть, это просто. Я выполнил этот маршрут как запрос POST. Нет абсолютного правила относительно того, когда следует использовать GET или POST (или другие методы запросов, которые вы еще не видели). Поскольку клиент будет отправлять данные, я решил использовать запрос POST, так как это похоже на запросы, которые представляют данные формы. Атрибут request.form — это словарь, который Flask предоставляет со всеми данными, включенными в представление. Когда я работал с веб-формами, мне не нужно было искать request.form, потому что Flask-WTF делает все, что бы работало как надо, но в этом случае на самом деле нет веб-формы, поэтому мне приходится напрямую обращаться к данным.

Итак, разберем что я делаю в этой функции. Вызывается функция translate() из предыдущего раздела и с ней передаются три аргумента непосредственно из данных, которые были отправлены с запросом. Результат включается в словарь с одним единственным ключом под именем text, а словарь передается как аргумент функции jsonify() Flask, которая преобразует словарь в форматированную полезную нагрузку JSON. Возвращаемое значение из jsonify() — это ответ HTTP, который будет отправлен обратно клиенту.

Например, если клиент хочет перевести строку Hello, World! на испанский язык ответ от этого запроса будет иметь следующие полезные данные:

{ "text": "Hola, Mundo!" }

Ajax From The Client

Итак, теперь, когда сервер может предоставлять переводы через URL /translate, мне нужно вызвать этот URL-адрес, когда пользователь жмакает ссылку «Перевести», которую я добавил выше, передавая текст для перевода, а также исходный и целевой языки. Если вы не знакомы с работой с JavaScript в браузере, это будет хороший опыт обучения.

При работе с JavaScript в браузере отображаемая в данный момент страница внутренне представлена ​​в виде объектной модели документа или только DOM. Это иерархическая структура, ссылающаяся на все элементы, существующие на странице. Код JavaScript, работающий в этом контексте, может вносить изменения в DOM, чтобы инициировать изменения на странице.

Давайте сначала обсудим, как мой JavaScript-код, запущенный в браузере, может получить три аргумента, которые мне нужно отправить в функцию перевода, которая выполняется на сервере. Чтобы получить текст, мне нужно найти узел в DOM, который содержит тело сообщения блога и прочитать его содержимое. Чтобы упростить идентификацию узлов DOM, содержащих записи в блоге, я собираюсь добавить к ним уникальный идентификатор. Если вы посмотрите на шаблон _post.html, строка, отображающая тело сообщения, просто читает {{post.body}}. Я собираюсь обернуть это содержимое в элементе <span>. Это ничего не изменит визуально, но это дает мне место, где я могу вставить идентификатор:

app/templates/_post.html: Add an ID to each blog post.

 <span id="post{{ post.id }}">{{ post.body }}</span>

Это присвоит уникальный идентификатор каждому сообщению в блоге в формате post1, post2 и т.д., где число соответствует идентификатору базы данных для каждого сообщения. Теперь, когда каждый пост в блоге имеет уникальный идентификатор, учитывая значение ID, я могу использовать jQuery для поиска элемента <span> для этого сообщения и извлечения текста в нем. Например, если бы я хотел получить текст для сообщения с ID 123, вот, что я сделал бы:

$('#post123').text()

Здесь знак $ — это имя функции, предоставляемой библиотекой jQuery. Эта библиотека используется Bootstrap, поэтому она уже была включена в Flask-Bootstrap. # является частью синтаксиса «селектор», используемого jQuery, что означает, что следующим является идентификатор элемента.

Я также хочу иметь место, куда я буду вставлять переведенный текст после получения его с сервера. Поскольку я собираюсь заменить ссылку «Перевести» на переведенный текст, то мне также нужно иметь уникальный идентификатор для этого узла:

app/templates/_post.html: Добавляю идентификатор в ссылку перевести.

 <span id="translation{{ post.id }}"> <a href="#">{{ _('Translate') }}</a> </span>

Итак, теперь для данного ID записи у меня есть post<ID> для сообщения в блоге и соответствующий узел translation<ID>, где мне нужно будет заменить ссылку «Перевод» на переведенный текст, как только я его получу.

Следующий шаг — написать функцию, которая может выполнять всю работу по переводу. Эта функция будет принимать входные и выходные узлы DOM, а также исходный и конечный языки, выдавать асинхронный запрос на сервер с тремя необходимыми аргументами и, наконец, заменять ссылку «Перевод» переведенным текстом после ответа сервера. Это звучит как большая работа, но реализация довольно проста:

app/templates/base.html: Функция перевода на стороне клиента.

{% block scripts %} ... <script> function translate(sourceElem, destElem, sourceLang, destLang) { $(destElem).html('<img src="{{ url_for('static', filename='loading.gif') }}">'); $.post('/translate', { text: $(sourceElem).text(), source_language: sourceLang, dest_language: destLang }).done(function(response) { $(destElem).text(response['text']) }).fail(function() { $(destElem).text("{{ _('Error: Could not contact server.') }}"); }); } </script>
{% endblock %}

Первые два аргумента — это уникальные ID для сообщений и узлов перевода. Последние два аргумента — это коды исходного и целевого языков.

Функция начинается с приятного штриха: она добавляет spinner, заменяющий ссылку «Перевод», чтобы пользователь знал, что перевод выполняется. Это делается с помощью jQuery, используя функцию $(destElem).html(), чтобы заменить исходный HTML, который определил ссылку на перевод с новым содержимым HTML на основе ссылки <img>. Для spinner-а я собираюсь использовать небольшой анимированный GIF-чик, который я добавил в каталог app/static/loading.gif, который Flask резервирует для статических файлов. Чтобы сгенерировать URL-адрес, ссылающийся на этот образ, я использую функцию url_for(), передавая специальное имя маршрута static и давая имя файла в качестве аргумента. Вы можете найти изображение load.gif в пакете загрузки для этой главы.

Так что теперь у меня есть приличный таймер-спиннер, который заменил ссылку «Перевести» и пользователь теперь знает, что нужно подождать, пока появится перевод.

Следующий шаг — отправить запрос POST на URL /translate, который я определил в предыдущем разделе. Для этого я также собираюсь использовать jQuery, в этом случае функцию $.post(). Эта функция передает данные на сервер в формате, аналогичном тому, как браузер отправляет веб-форму, что удобно, потому что это позволяет Flask включать эти данные в словарь request.form. Два аргументы $.post() — это, сначала URL для отправки запроса, а затем словарь (или объект, поскольку они вызываются в JavaScript) с тремя элементами данных, которые ожидает сервер.

Вы, наверное, знаете, что JavaScript много работает с функциями обратного вызова или более продвинутой формой обратных вызовов, называемых promises. То, что я хочу сделать сейчас, это побещать, что я сделаю, когда этот запрос завершится и браузер получит ответ. В JavaScript нет такого понятия, как ожидание чего-либо, все асинхронно. Вместо этого необходимо предоставить функцию обратного вызова, которую обозреватель будет вызывать при получении ответа. А также как способ сделать все как можно более надежным, я хочу указать, что делать в случае, если вдруг возникла ошибка, так что второй функцией обратного вызова будет обработка ошибок. Существует несколько способов указать эти обратные вызовы, но в этом случае использование promises делает код достаточно ясным. Синтаксис выглядит следующим образом:

$.post(<url>, <data>).done(function(response) { // success callback
}).fail(function() { // error callback
})

Синтаксис promise позволяет Вам в основном "цеплять" обратные вызовы к возвращаемому значению вызова $.post(). При успешном обратном вызове все, что мне нужно сделать, это вызвать $(destElem).text() с переведенным текстом, который приходит в словарь под ключ text. В случае ошибки я делаю то же самое, но текст, который я покажу, будет общим сообщением об ошибке, которое, я уверен, вводится в базовый шаблон в качестве текста, который можно перевести.

Итак, теперь остается только вызвать функцию translate() с правильными аргументами в результате нажатия пользователем ссылки «Перевести». Есть также несколько способов сделать это. То, что я собираюсь сделатьм- это, просто встроить вызов функции в атрибут href ссылки:

app/templates/_post.html: Translate link handler.

 <span id="translation{{ post.id }}"> <a href="javascript:translate( '#post{{ post.id }}', '#translation{{ post.id }}', '{{ post.language }}', '{{ g.locale }}');">{{ _('Translate') }}</a> </span>

Элемент href ссылки может принимать любой код JavaScript, если он имеет префикс javascript:, так что это удобный способ сделать вызов функции перевода. Поскольку эта ссылка будет отображаться на сервере, когда клиент запрашивает страницу, я могу использовать выражения {{ }} для генерации четырех аргументов функции. Каждое сообщение будет иметь свою собственную переводную ссылку с ее уникальными аргументами. #, который вы видите в качестве префикса для элементов post<ID> и translation<ID>, указывает, что следующим является ID элемента.

Now the live translation feature is complete! If you have set a valid Microsoft Translator API key in your environment, you should now be able to trigger translations. Assuming you have your browser set to prefer English, you will need to write a post in another language to see the "Translate" link. Below you can see an example:

Теперь функция перевода в реальном времени завершена! Если вы установили валидный ключ Microsoft Translator API в своей среде, то теперь вы сможете запускать переводы. Если ваш браузер настроен на предпочтительный английский, вам нужно будет написать сообщение на другом языке, чтобы увидеть ссылку "перевести". Ниже вы можете увидеть пример:

В этой главе я представил несколько новых текстов, которые необходимо перевести на все языки, поддерживаемые приложением, поэтому необходимо обновить каталоги переводов:

(venv) $ flask translate update

Для ваших собственных проектов вам необходимо будет отредактировать файлы messages.po в каждом репозитории языков, чтобы включить переводы для этих новых тестов, но я уже создал испанский перевод в пакете загрузки для этой главы или в репозитории GitHub.

Чтобы опубликовать новые переводы, их необходимо скомпилировать:

(venv) $ flask translate compile

Туда Сюда


x

Ещё Hi-Tech Интересное!

[Из песочницы] Исследователи из Карнеги-Меллона создали пока самые правдоподобные «deepfakes»

Когда-либо слышали о «deepfakes»? ИИ, который накладывает лицо одного человека на тело другого, использовали для замены Харрисона Форда на Николаса Кейджа в бесчисленных видеоклипах, а также и для более гнусных целей: знаменитости без их ведома появились в порно и пропаганде. ...

Очередной сказ о том, как на Мегафоне воруют деньги

upd: уточняю: это не жалоба, а статья-предупреждение, проверьте свои детализации Сидел я значит на диване, никого не трогал, переписывался в контактике (каюсь три раза). Дело было вечером, делать было нечего. Поборов лень, я встал с дивана, подошел к телефону и ...