Software localization

Flask App Tutorial on Internationalization

Let your Python Flask app say hi to the world in any language! Our Flask app tutorial will walk you through the process from start to finish!
Software localization blog category featured image | Phrase

Here comes another great i18n guide in Python! Our complete Flask app tutorial will show you how to implement internationalization and localization in your Flask application. Thereby, we will be making full use of the locale (via the glob and json modules) to create a multilingual analytic dashboard. All of them are part of Python's standard library, except for Flask, and the language files are stored in the nested JSON format. Ready to start?

The Setup

First and foremost, check out our GitHub repository for the complete code used in this tutorial. You can clone it via Git or download it directly as a zip file.

It is highly recommended to create a virtual environment before you continue with the installation. Run the following command in the terminal.

pip install flask

Language Files

German (de_DE)

In your project directory, create a new folder called language. Inside the folder, create a new JSON file called de_DE.json. Each JSON file represents a language that contains all the translations for your application. In this case, I have the translation for German. Append the following data inside it.

{

  "last_updated": "Zuletzt aktualisiert am",

  "dashboard_title": "Mein Dashboard",

  "message": "Nachrichten",

  "view": "Views",

  "share": "Aktien",

  "user": "Benutzer",

  "general_stat": "Allgemeine Statistiken",

  "new_visitor": "Neue Besucher",

  "new_user": "Neue Benutzer",

  "country": "Länder",

  "country_list": {"germany": "Deutschland", "united_kingdom": "Britien", "russia": "Russland", "spain": "Spanien", "india": "Indien", "france": "Frankreich"}

}

English/Singapore (en_SG)

Repeat the same process, and this time, I am going to use the following data and name it en_SG.json.

{

  "last_updated": "Last updated on",

  "dashboard_title": "My Dashboard",

  "message": "Messages",

  "view": "Views",

  "share": "Shares",

  "user": "Users",

  "general_stat": "General Stats",

  "new_visitor": "New Visitors",

  "new_user": "New Users",

  "country": "Countries",

  "country_list": {"germany": "Germany", "united_kingdom": "UK", "russia": "Russia", "spain": "Spain", "india": "India", "france": "France"}

}

The HTML File

To keep things simple, this tutorial will use a modified version of an analytical dashboard HTML template made by W3.CSS. This is how it looks like in full view. It will resize on its own depending on the screen resolution of your device.

Analytical dashboard HTML template made by W3.CSS | Phrase

We are going to make a lite version of it, displaying just the dashboard, without the navigation menu. In the root directory, create a new folder called templates. Inside the folder, create a new file called index.html. Append the following code to it.

<!DOCTYPE html>

<html>

  <title>APP</title>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">

  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

  <style>

    html,body,h1,h2,h3,h4,h5 {font-family: "Raleway", sans-serif}

  </style>

  <body class="w3-light-grey">

    <!-- Top container -->

    <div class="w3-bar w3-top w3-black w3-large" style="z-index:4">

      <button class="w3-bar-item w3-button w3-hide-large w3-hover-none w3-hover-text-light-grey">APP</button>

      <span class="w3-bar-item w3-right">{{ last_updated }} {{ update_time }}</span>

    </div>

    <!-- !PAGE CONTENT! -->

    <div class="w3-main" style="margin-top:43px;">

      <!-- Header -->

      <header class="w3-container" style="padding-top:22px">

        <h5><b><i class="fa fa-dashboard"></i> {{ dashboard_title }}</b></h5>

      </header>

      <div class="w3-row-padding w3-margin-bottom">

        <div class="w3-quarter">

          <div class="w3-container w3-red w3-padding-16">

            <div class="w3-left"><i class="fa fa-comment w3-xxxlarge"></i></div>

            <div class="w3-right">

              <h3>{{ stats.message }}</h3>

            </div>

            <div class="w3-clear"></div>

            <h4>{{ message }}</h4>

          </div>

        </div>

        <div class="w3-quarter">

          <div class="w3-container w3-blue w3-padding-16">

            <div class="w3-left"><i class="fa fa-eye w3-xxxlarge"></i></div>

            <div class="w3-right">

              <h3>{{ stats.view }}</h3>

            </div>

            <div class="w3-clear"></div>

            <h4>{{ view }}</h4>

          </div>

        </div>

        <div class="w3-quarter">

          <div class="w3-container w3-teal w3-padding-16">

            <div class="w3-left"><i class="fa fa-share-alt w3-xxxlarge"></i></div>

            <div class="w3-right">

              <h3>{{ stats.share }}</h3>

            </div>

            <div class="w3-clear"></div>

            <h4>{{ share }}</h4>

          </div>

        </div>

        <div class="w3-quarter">

          <div class="w3-container w3-orange w3-text-white w3-padding-16">

            <div class="w3-left"><i class="fa fa-users w3-xxxlarge"></i></div>

            <div class="w3-right">

              <h3>{{ stats.user }}</h3>

            </div>

            <div class="w3-clear"></div>

            <h4>{{ user }}</h4>

          </div>

        </div>

      </div>

      <div class="w3-container">

        <h5>{{ general_stat }}</h5>

        <p>{{ new_visitor }}</p>

        <div class="w3-grey">

          <div class="w3-container w3-center w3-padding w3-green" style="width:25%">+25%</div>

        </div>

        <p>{{ new_user }}</p>

        <div class="w3-grey">

          <div class="w3-container w3-center w3-padding w3-orange" style="width:50%">50%</div>

        </div>

      </div>

      <hr>

      <div class="w3-container">

        <h5>{{ country }}</h5>

        <table class="w3-table w3-striped w3-bordered w3-border w3-hoverable w3-white">

          <tr>

            <td>{{ country_list.germany }}</td>

            <td>{{ currencies.germany }}</td>

          </tr>

          <tr>

            <td>{{ country_list.united_kingdom }}</td>

            <td>{{ currencies.united_kingdom }}</td>

          </tr>

          <tr>

            <td>{{ country_list.russia }}</td>

            <td>{{ currencies.russia }}</td>

          </tr>

          <tr>

            <td>{{ country_list.spain }}</td>

            <td>{{ currencies.spain }}</td>

          </tr>

          <tr>

            <td>{{ country_list.india }}</td>

            <td>{{ currencies.india }}</td>

          </tr>

          <tr>

            <td>{{ country_list.france }}</td>

            <td>{{ currencies.france }}</td>

          </tr>

        </table>

        <br>

      </div>

    </div>

  </body>

</html>

The Flask Server

Import

Once you are done with it, create a new Python file called mydashboard.py in the root directory. Add the following import declaration at the top of your Python file.

from flask import Flask, render_template

import locale

import datetime

import glob

import json

Main

Next, declare the variable for Flask and the main function. Feel free to modify the port based on your preferences.

app = Flask(__name__)

if __name__ == '__main__':

  app.run('0.0.0.0', port=5000)

Inside the main function, declare and initialize the locale for our project. Set it to empty string if you intend to use the current locale of your machine.

app_language = 'en_SG'

locale.setlocale(locale.LC_ALL, app_language)

Variable Initialization

The next step is to initialize the following variables:

languages = {}

stats = {}

currencies = {}

date_format = "%d %b %Y %H:%M:%S %Z"

last_updated_time = ""

Let us explain their functions in greater detail:

  • languages – a dictionary to hold all the translated data from language files,
  • stats  – a dictionary to store relevant information, such as number of views, number of messages, etc.,
  • currencies – a dictionary that will contain information related to currency,
  • date_format – format for displaying the last updated datetime at the top of the web page,
  • last_updated_time – a datetime string that represent the last updated datetime.

Loading Language Files via glob and json

We are going to use glob to search the language folder for all the language files that we have created earlier. After that, we will use the json module to load the JSON data for all of the languages automatically. You just need to create a new JSON file in the language folder whenever you intend to add a new language to the application.

language_list = glob.glob("language/*.json")

  for lang in language_list:

    filename = lang.split('\\')

    lang_code = filename[1].split('.')[0]

    with open(lang, 'r', encoding='utf8') as file:

      languages[lang_code] = json.loads(file.read())

Data and Mapping

In the actual use case, you should have your own data for the application. For this tutorial, I am going to use some randomly generated data. Beforehand, you should create the following functions to help parsing our data based on the locale that we have set previously.

def get_stats(input):

  return locale.format_string('%d', input)

def get_currencies(input):

  return locale.currency(input, international=True)

Subsequently, add the following function that will parse the data and store them in their respective global variables.

def fetch_data():

  global stats

  global currencies

  global data_updated_time

  stat = {"message": 22450, "view": 578902, "share": 442, "user": 1824}

  currency = {"germany": 32.7, "united_kingdom": 16.5, "russia": 14.3, "spain": 10.8, "india": 7.6, "france": 4.9}

  stats = {k: get_stats(v) for k, v in stat.items()}

  currencies = {k: get_currencies(v) for k, v in currency.items()}

  data_updated_time = datetime.datetime.now().strftime(date_format)

Head over to the main function and call the fetch_data() function just right before the app.run() function:

if __name__ == '__main__':

  #... contains the rest of the code that we have defined earlier

  fetch_data()

  app.run('0.0.0.0', port=5000)

Route

Next, specify a new route for our dashboard with the respective global variables as parameters. If the user specified a non-supported language, we would default it to the language used by our app. In this case, it is en_SG.

@app.route('/dashboard/<language>')

def dashboard(language):

  if(language not in language_dict):

    language = app_language

  return render_template('index.html', **languages[language], stats = stats, currencies = currencies, update_time = data_updated_time)

One thing to note is that the variables are passed in two different ways:

  1. **languages[app_language] helps to unpack the dictionary into key = value pairs; given dashboard_title = "My Dashboard", you can access them via {{ dashboard_title }} in the HTML file.
  2. stats = stats is a variable passed as a dictionary. Given message = "Message", you need to access it via {{ stats.message }} in the HTML file.

The final code for mydashboard.py is as follows:

from flask import Flask, render_template

import locale

import datetime

import glob

import json

app = Flask(__name__)

def get_stats(input):

  return locale.format_string('%d', input)

def get_currencies(input):

  return locale.currency(input, international=True)

def fetch_data():

  global stats

  global currencies

  global last_updated_time

  stat = {"message": 22450, "view": 578902, "share": 442, "user": 1824}

  currency = {"germany": 32.7, "united_kingdom": 16.5, "russia": 14.3, "spain": 10.8, "india": 7.6, "france": 4.9}

  stats = {k: get_stats(v) for k, v in stat.items()}

  currencies = {k: get_currencies(v) for k, v in currency.items()}

  last_updated_time = datetime.datetime.now().strftime(date_format)

@app.route('/dashboard/<language>')

def dashboard(language):

  if(language not in language_dict):

    language = app_language

  return render_template('index.html', **languages[language], stats = stats, currencies = currencies, update_time = last_updated_time)

if __name__ == '__main__':

  app_language = 'en_SG'

  locale.setlocale(locale.LC_ALL, app_language)

  languages = {}

  stats = {}

  currencies = {}

  date_format = "%d %b %Y %H:%M:%S %Z"

  last_updated_time = ""

  language_list = glob.glob("language/*.json")

  for lang in language_list:

    filename = lang.split('\\')

    lang_code = filename[1].split('.')[0]

    with open(lang, 'r', encoding='utf8') as file:

      languages[lang_code] = json.loads(file.read())

  fetch_data()

  app.run('0.0.0.0', port=5000)

Once you are done with it, run the following command in the terminal to start your Flask application. Make sure that you are calling from the root directory.

python mydashboard.py

Open a browser and go to the URL below. Modify it accordingly if you had specified a different port.

localhost:5000/dashboard/en_SG

You should see the following web page:

analytical dashboard HTML template in English | Phrase

Let's change the URL as follows:

localhost:5000/dashboard/de_DE

Here is the German version of our web page:

Analytical dashboard HTML template in German | Phrase

And This Is the End! Still Not Enough?

We are happy you were able to complete our Flask tutorial on internationalization. You should now be able to build a multilingual Flask application on your own.

As soon as you are ready to add even more languages to your app, think about streamlining your i18n process by signing up for Phrase, the leanest and most reliable translation management platform on the market.

Made for developers by developers, Phrase will equip you with everything you need to:

  • Build production-ready integrations with your development workflow,
  • Invite as many users as you wish to collaborate on your projects,
  • Edit and convert localization files with more context for higher translation quality.

Last but not least, feel free to give the following Python guides a read as well: