{"id":8626,"date":"2016-05-23T09:58:48","date_gmt":"2016-05-23T07:58:48","guid":{"rendered":"http:\/\/phraseapp.com\/blog\/?p=218"},"modified":"2023-08-21T14:51:50","modified_gmt":"2023-08-21T12:51:50","slug":"python-localization-flask-applications","status":"publish","type":"post","link":"https:\/\/phrase.com\/blog\/posts\/python-localization-flask-applications\/","title":{"rendered":"Flask App Tutorial on Localization"},"content":{"rendered":"<p>Flask, the lightweight Python microframework for creating web apps, and Phrase, the translation management platform, work great together. This tutorial will guide you through the process of localizing Flask applications using Flask-Babel and Phrase.<br \/>\nFlask-Babel is a Flask extension that adds internationalization (i18n) and localization (l10n) support to any Flask application. Phrase, on the other hand, is a translation management tool that features a powerful in-context editor that makes translating more convenient.<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_69_1 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Overview<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/phrase.com\/blog\/posts\/python-localization-flask-applications\/#get-started-with-flask-babel\" title=\"Get Started With Flask-Babel\">Get Started With Flask-Babel<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/phrase.com\/blog\/posts\/python-localization-flask-applications\/#tagging-strings\" title=\"Tagging Strings\">Tagging Strings<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/phrase.com\/blog\/posts\/python-localization-flask-applications\/#building-locales\" title=\"Building Locales\">Building Locales<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/phrase.com\/blog\/posts\/python-localization-flask-applications\/#get-translations-with-phrase\" title=\"Get Translations With Phrase\">Get Translations With Phrase<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/phrase.com\/blog\/posts\/python-localization-flask-applications\/#further-reading\" title=\"Further Reading\">Further Reading<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h2 id=\"get-started-with-flask-babel\"><span class=\"ez-toc-section\" id=\"get-started-with-flask-babel\"><\/span>Get Started With Flask-Babel<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>Let\u2019s begin by installing the required dependencies:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\"><code class=\"language-shell highlight\">pip install Flask-Babel<\/code><\/pre>\n<p>This will install Flask-Babel, aswell as the <strong>pybabel<\/strong> command-line tool.<br \/>\nNext, import Flask-Babel and hook it to your app like so:<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">from flask import Flask, [...]\nfrom flask.ext.babel import Babel, gettext\napp = Flask(__name__)\nbabel = Babel(app)<\/pre>\n<p>In your configuration, add a dictionary named LANGUAGES. In this example, we add two locales, english (\u2018en\u2019) and german (\u2018de\u2019).<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\"># add to your app.config or config.py file\nLANGUAGES = {\n    'en': 'English',\n    'de': 'Deutsch'\n}<\/pre>\n<p>We will use this dictionary for a little helper function that Babel offers:<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\"># add to you main app code\n@babel.localeselector\ndef get_locale():\n    return request.accept_languages.best_match(app.config['LANGUAGES'].keys())<\/pre>\n<p>This convenient tool will automatically choose the best matching locale, based on the Accept-Language header from the incoming request.<br \/>\n<em>Hint: for testing purposes, you can directly return a language code, for example: return \u2018de\u2019<\/em><br \/>\nOne more thing! Create a config file for Babel. We will chew through the details later on, but for now, simply create a file named <strong>babel.cfg<\/strong> in the top-level directory of you app:<\/p>\n<pre class=\"lang:default highlight:0 decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">[python: **.py]\n[jinja2: **\/templates\/**.html]\nextensions=jinja2.ext.autoescape,jinja2.ext.with_<\/pre>\n<h3 id=\"tagging-strings\"><span class=\"ez-toc-section\" id=\"tagging-strings\"><\/span>Tagging Strings<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Now it\u2019s time to tag all the Strings you want to translate. In a typical Flask app, there will be two types of Strings that require translating. One being hard-coded Strings in your .py files, the other being Strings in your .html Jinja2 templates.<br \/>\nTag them by adding a gettext(\u2018String\u2019) call to them, like so:<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">slogan = 'This app is awesome.'\nflash('Login failed')<\/pre>\n<p>becomes<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">slogan = gettext('This app is awesome.')\nflash(gettext('Login failed'))<\/pre>\n<p>For your Jinja2 templates, this is also very straightforward, for example:<\/p>\n<pre class=\"lang:xhtml decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;b&gt;Free Trial&lt;\/b&gt;\n&lt;input type=\"submit\" value=\"Sign up\"\/&gt;<\/pre>\n<p>becomes<\/p>\n<pre class=\"lang:xhtml decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;b&gt;{{ gettext('Free Trial') }}&lt;\/b&gt;\n&lt;input type=\"submit\" value=\"{{ gettext('Sign up') }}\"\/&gt;<\/pre>\n<p><em>Hint: you can use _() as a shortcut for gettext().<\/em><br \/>\nNote that the english string is also the key (msgid) that gettext will use for when looking up the corosponding Translation value (msgstr).<\/p>\n<h3 id=\"building-locales\"><span class=\"ez-toc-section\" id=\"building-locales\"><\/span>Building Locales<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>When all strings are tagged, it is time to build a catalog for the Locales we want to create. Run:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\"><code class=\"language-shell highlight\">pybabel extract -F babel.cfg -o messages.pot<\/code><\/pre>\n<p>This checks all files specified in babel.cfg and searches thru them to find tagged strings and outputs them to messages.pot.<br \/>\nNext, run:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\"><code class=\"language-shell highlight\">pybabel init -i messages.pot -d translations -l de<\/code><\/pre>\n<p>This will use the index from messages.pot to build a german (\u2018de\u2019) locale in our translations directory. Don\u2019t worry, if the directory doesn\u2019t exist yet, pybabel will create it for you.<br \/>\nFinally it is time to translate the Strings.<br \/>\nWith your favorite text editor, open \u2018translations\/de\/LC_MESSAGES\/messages.po\u2019. You can now start translating the <strong>msgstr<\/strong> values. When you are done editing, there is only one step left, compile all .po files in your translation directory:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\"><code class=\"language-shell highlight\">pybabel compile -d translations<\/code><\/pre>\n<p>Done! Start playing around with your app. Remember, the locale is chosen based on the Accept-Language Header that is being sent by your Browser.<br \/>\nCheck out our <a href=\"http:\/\/goo.gl\/nMhIrl\">full Flask-Babel example app<\/a>, a modified version of Flaskr, the official Flask demo app.<\/p>\n<style type=\"text\/css\"><!--td {border: 1px solid #ccc;}br {mso-data-placement:same-cell;}--><\/style>\n<h2 id=\"get-translations-with-phraseapp\"><span class=\"ez-toc-section\" id=\"get-translations-with-phrase\"><\/span>Get Translations With Phrase<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><a href=\"https:\/\/phrase.com\/\">Phrase<\/a> provides tools for software translation management. Its WYSIWYG <a href=\"https:\/\/phrase.com\/demo\/\">In-Context Editor (Demo)<\/a> enables you and your copywriters or <a href=\"https:\/\/phrase.com\/blog\/posts\/a-day-in-the-life-of-a-translator\/\">translators<\/a> to change translations on your website in any web browser.<br \/>\nLet\u2019s integrate the In-Context Editor in our example from above.<br \/>\nIn order to expose your tagged Strings to the In-Context-Editor, we will be using the\u00a0<a href=\"http:\/\/goo.gl\/Wxin61\">Flask-Phrase<\/a>, a package that you can install via:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\"><code class=\"language-shell highlight\">pip install Flask-Phrase<\/code><\/pre>\n<p>Next, we hook our app to Flask-Phrase and import the gettext provided by Flask-Phrase. Extending the example from above, this would look like this:<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">from flask import Flask, [...]\nfrom flask.ext.babel import Babel\nfrom flask_phrase import Phrase, gettext\napp = Flask(__name__)\nbabel = Babel(app)\nphrase = Phrase(app)<\/pre>\n<p><em>Hint: the gettext provided by flask_phrase will simply proxy the call to Flask-Babel when not in editing mode.<\/em><br \/>\nNext, add the following to your Flask app config:<\/p>\n<pre class=\"lang:python decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\"># add to your app.config or config.py file\nPHRASEAPP_ENABLED = True\nPHRASEAPP_PREFIX = '{{__'\nPHRASEAPP_SUFFIX = '__}}'<\/pre>\n<p>Almost done. In your .html Jinja2 templates, add this JavaScript snippet along with the Project-ID that can be found in the Phrase Translation Center:<\/p>\n<pre class=\"lang:js decode:true EnlighterJSRAW\" data-enlighter-language=\"generic\">&lt;script&gt;\n    window.PHRASEAPP_CONFIG = {\n        projectId: \"YOUR-PROJECT-ID\"\n    };\n    (function() {\n        var phraseapp = document.createElement('script'); phraseapp.type = 'text\/javascript'; phraseapp.async = true;\n        phraseapp.src = ['https:\/\/', 'phraseapp.com\/assets\/in-context-editor\/2.0\/app.js?', new Date().getTime()].join('');\n        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(phraseapp, s);\n    })();\n&lt;\/script&gt;<\/pre>\n<p>That\u2019s it. Make sure PHRASEAPP_ENABLED is set to \u2018True\u2019 so that your strings will be rendered in a special format for the Phrase editor. Check out our <a href=\"https:\/\/github.com\/phrase\/flask-demo-application\/\">full example app<\/a> with Flask-Babel and Phrase built-in.<\/p>\n<h3 id=\"further-reading\"><span class=\"ez-toc-section\" id=\"further-reading\"><\/span>Further Reading<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li><a href=\"https:\/\/support.phrase.com\/hc\/en-us\/\">Phrase documentation<\/a><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Be sure to subscribe<\/span> and receive all updates from the Phrase blog straight to your inbox. You\u2019ll receive localization best practices, about cultural aspects of breaking into new markets, guides and tutorials for optimizing software translation, and other industry insights and information. Don\u2019t miss out!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Flask-Babel extends the Python micro-framework of Flask through i18n and l10n support. Learn how to make the most of it with this tutorial.<\/p>\n","protected":false},"author":61,"featured_media":2612,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_stopmodifiedupdate":true,"_modified_date":"","_searchwp_excluded":"","footnotes":""},"categories":[40],"class_list":["post-8626","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software-localization"],"acf":[],"_links":{"self":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/8626"}],"collection":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/users\/61"}],"replies":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/comments?post=8626"}],"version-history":[{"count":8,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/8626\/revisions"}],"predecessor-version":[{"id":94526,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/posts\/8626\/revisions\/94526"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media\/2612"}],"wp:attachment":[{"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/media?parent=8626"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phrase.com\/wp-json\/wp\/v2\/categories?post=8626"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}