Launch a website in several languages

A business website in three languages with a language switcher, translated page titles and URLs that search engines index per language.

20 ساعة intermediate PythonFlaskFlask-BabelJinjaRender

1 Friday evening: decide languages and URLs

  • Pick the languages from real customers (e.g. English, Spanish, French).
  • Use one URL prefix per language: /en/, /es/, /fr/. Search engines index each version separately, and links can be shared in a specific language.
  • List every page and every piece of text on it. Text inside images can't be translated; avoid it.

Done when: you have a table of pages × languages and who will check each translation.

2 Saturday morning: Flask-Babel setup

python -m venv .venv && source .venv/bin/activate
pip install flask flask-babel
# app.py
from flask import Flask, g, request, redirect, render_template, abort
from flask_babel import Babel, _
LANGS = ["en", "es", "fr"]
app = Flask(__name__)
babel = Babel(app, locale_selector=lambda: g.get("lang", "en"))

@app.url_value_preprocessor
def pull_lang(endpoint, values):
    g.lang = (values or {}).pop("lang", "en")
    if g.lang not in LANGS:
        abort(404)

@app.url_defaults
def add_lang(endpoint, values):
    if "lang" not in values and app.url_map.is_endpoint_expecting(endpoint, "lang"):
        values["lang"] = g.get("lang", "en")

@app.route("/")
def root():
    best = request.accept_languages.best_match(LANGS) or "en"
    return redirect(f"/{best}/")

@app.route("/<lang>/")
def home():
    return render_template("home.html", title=_("Web apps for your business"))

In templates wrap every string: {{ _("Contact us") }}.

Done when: /es/ renders the page (still in English) and url_for("home") keeps the current language.

3 Saturday afternoon: translate

pybabel extract -F babel.cfg -o messages.pot .
pybabel init -i messages.pot -d translations -l es
pybabel init -i messages.pot -d translations -l fr
# fill in translations/<lang>/LC_MESSAGES/messages.po, then:
pybabel compile -d translations

Use placeholders, never string concatenation: _("Hello, %(name)s", name=user), so each language can put the name where it belongs. Have a native speaker review the important pages.

Done when: all three languages show translated text, and adding a new string is extract → update → translate → compile.

4 Sunday morning: switcher and SEO

  • A language switcher that links to the same page in the other language (not the homepage).
  • <html lang="{{ g.lang }}">, translated <title> and meta description.
  • hreflang links in <head> so search engines connect the versions:
{% for l in ["en", "es", "fr"] %}
<link rel="alternate" hreflang="{{ l }}" href="{{ url_for(request.endpoint, lang=l, _external=True) }}">
{% endfor %}
<link rel="alternate" hreflang="x-default" href="{{ url_for(request.endpoint, lang='en', _external=True) }}">

Done when: every page shows the right lang, title and hreflang set in the page source.

5 Sunday afternoon: deploy

Deploy to Render with pybabel compile in the build command, add a sitemap.xml that lists every page in every language, and submit it in Google Search Console.

Done when: a search for your business in Spanish shows the /es/ page within a few weeks.

أنهيت المشروع؟ أرسل الروابط للمراجعة. إرسال للمراجعة (50 نقطة)

المزيد من الأدلة