Kansainvälistäminen ja lokalisointi

Web-sovelluksen käyttäjät puhuvat eri kieliä, asuvat eri aikavyöhykkeillä ja ovat tottuneet erilaisiin päivämäärien, numeroiden ja nimien esitystapoihin. Tällä luennolla käydään läpi, miten Flask-sovellus rakennetaan niin, että se toimii oikein kaikille. Painopiste on aikojen käsittelyssä, koska siinä virheitä syntyy eniten. Luennon asioita voi kokeilla esimerkkisovelluksessa.

Käsitteet

Kansainvälistäminen (engl. internationalization, lyhennettynä i18n: i, 18 kirjainta, n) tarkoittaa sovelluksen rakentamista niin, että sen voi sovittaa eri kielille ja alueille muuttamatta ohjelmakoodia. Lokalisointi eli kotoistus (localization, l10n) on itse sovittaminen: käännökset, päivämäärien ja numeroiden muotoilut sekä kieli- ja aluekohtainen sisältö. Ensin sovellus kansainvälistetään, minkä jälkeen se voidaan lokalisoida mille tahansa kielelle ilman ohjelmointityötä.

Kieli ilmaistaan BCP 47 -kielitunnisteella. W3C:n ohjeen kultainen sääntö on pitää tunniste mahdollisimman lyhyenä: useimmiten riittää pelkkä kielikoodi, kuten fi, sv tai en. Alue tai muu tarkenne lisätään vain, kun se erottaa jotakin olennaista. Esimerkiksi brittienglannissa (en-GB) päivämäärä kirjoitetaan 22/09/2026, amerikanenglannissa (en-US) 9/22/26. Jos sovellus ei tee tällaista eroa, en riittää.

Pythonin kirjastot käyttävät kielen ja alueen välissä usein alaviivaa (en_GB), HTML ja HTTP yhdysmerkkiä (en-GB).

Ajat ja aikavyöhykkeet

Aikaleima, paikallinen aika ja päivämäärä

Sana "aika" tarkoittaa ohjelmoinnissa kolmea eri asiaa, ja ne on pidettävä erillään:

Pythonissa aikaleimaa vastaa aikavyöhykkeellinen datetime, paikallista aikaa aikavyöhykkeetön datetime ja päivämäärää date. Pythonin englanninkielisessä dokumentaatiossa niistä käytetään nimityksiä aware ja naive. Perussääntö on:

Tallenna UTC, näytä paikallisena

Tallenna aikaleimat tietokantaan UTC-aikana aikavyöhyketiedon kanssa ja muunna ne käyttäjän tai tapahtuman aikavyöhykkeeseen vasta näytettäessä. Näin vertailut ja laskutoimitukset ovat aina oikein, eikä kesäaika sotke niitä.

UTC ja GMT

UTC (koordinoitu yleisaika, engl. Coordinated Universal Time) on atomikelloihin perustuva maailmanlaajuinen aikastandardi. Siinä ei ole kesäaikaa, ja kaikki aikavyöhykkeet ilmaistaan poikkeamana siitä: Suomen aika on talvella UTC+2 ja kesällä UTC+3.

GMT (Greenwichin aika, Greenwich Mean Time) on UTC:n edeltäjä: Lontoon Greenwichin observatorion kautta kulkevan pituuspiirin aurinkoon perustuva aika. Käytännössä se on sama kuin UTC, ja sitä käytetään edelleen yleisesti UTC:n synonyymina — esimerkiksi HTTP-otsakkeissa, joissa merkintä GMT tarkoittaa UTC-aikaa. Nykyään GMT on myös Britannian talviajan nimi.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

SUOMI = ZoneInfo("Europe/Helsinki")

aikaleima = datetime.now(timezone.utc)                # tallennetaan tietokantaan
print(aikaleima.astimezone(SUOMI).isoformat(timespec="seconds"))
# 2026-09-22T11:05:31+03:00

Aikavyöhykkeettömään aikaan liittyy ansoja. Esimerkiksi Pythonin Firestore-kirjasto tulkitsee aikavyöhykkeettömän ajan UTC-ajaksi ja palauttaa sen UTC-merkittynä. Suomen aikana tallennettu "klo 9.00" siirtyy kahdella tai kolmella tunnilla heti, kun sitä yrittää muuntaa Suomen aikaan. Toinen ansa on palvelimen kello: datetime.now() ilman aikavyöhykettä palauttaa palvelimen paikallisen ajan, ja Cloud Runin palvelimet toimivat UTC-ajassa.

Kenen aikavyöhyke?

Suunnistuskilpailun alkuaika kuuluu kilpailupaikan aikavyöhykkeeseen, ei käyttäjän. Jos kilpailu pidetään Norjassa, se alkaa Norjan aikaa, katsoi sitä kuka tahansa ja mistä tahansa. Tallenna siksi tapahtumaan sen aikavyöhyke nimenä, esimerkiksi Europe/Helsinki, eikä pelkkää poikkeamaa +03:00: poikkeama vaihtuu kesäajan mukana, nimi ei.

Aikavyöhykkeiden säännöt tulevat IANA:n aikavyöhyketietokannasta, ja niitä muutetaan useita kertoja vuodessa jossain päin maailmaa. zoneinfo käyttää käyttöjärjestelmän tietokantaa tai tzdata-pakettia. Lisää tzdata sovelluksen riippuvuuksiin, jotta säännöt löytyvät myös kontista ja pysyvät ajan tasalla.

Kesäaika

Kesäajan vaihtuminen tekee paikallisesta ajasta hankalan. Suomessa kellot siirretään keväällä klo 3.00 tunnilla eteenpäin ja syksyllä klo 4.00 tunnilla taaksepäin. Vuonna 2026 tästä seuraa:

Kesäajan erikoistapauksia Suomessa vuonna 2026
Paikallinen aikaMitä tapahtuu
29.3.2026 klo 3.30 Aikaa ei ole olemassa: kello hyppää 3.00:sta 4.00:aan. Python hyväksyy sen silti varoittamatta.
25.10.2026 klo 3.30 Aika esiintyy kahdesti, ensin kesäaikana (+03:00) ja sitten talviaikana (+02:00). Pythonissa kerrat erotetaan fold-attribuutilla.
24.10.2026 klo 12 + 1 vuorokausi timedelta(days=1) tuottaa 25.10. klo 12, mutta todellisuudessa väliin jää 25 tuntia.

Kun käyttäjä syöttää paikallisen ajan, sen kelvollisuuden voi tarkistaa muuntamalla ajan UTC:ksi ja takaisin:

from datetime import datetime, timezone


def tarkista_paikallinen_aika(teksti, vyohyke):
    paikallinen = datetime.fromisoformat(teksti)      # aikavyöhykkeetön
    aika = paikallinen.replace(tzinfo=vyohyke)
    # Olematon aika ei säily muunnoksessa UTC:ksi ja takaisin.
    if aika.astimezone(timezone.utc).astimezone(vyohyke).replace(tzinfo=None) != paikallinen:
        return "ei ole olemassa"
    # Kahdesti esiintyvällä ajalla on kaksi eri UTC-poikkeamaa.
    if aika.utcoffset() != aika.replace(fold=1).utcoffset():
        return "esiintyy kahdesti"
    return "kelvollinen"


tarkista_paikallinen_aika("2026-03-29T03:30", SUOMI)   # 'ei ole olemassa'
tarkista_paikallinen_aika("2026-10-25T03:30", SUOMI)   # 'esiintyy kahdesti'

ISO 8601 ja ajat HTML:ssä

Koneiden välillä ajat välitetään ISO 8601 -muodossa tai sen tiukemmassa RFC 3339 -profiilissa: 2026-09-22T11:05:31+03:00. Muoto on yksiselitteinen, lajittuu merkkijonona oikeaan järjestykseen ja on jokaisen ohjelmointikielen jäsennettävissä.

HTML:ssä time-elementti yhdistää ihmiselle näytettävän ja koneelle tarkoitetun muodon. Selain, hakukone ja ruudunlukija lukevat datetime-attribuutin, ja käyttäjä näkee sisällön:

<p>Päivitetty <time datetime="2026-09-22T11:05:31+03:00">22.9.2026 klo 11.05</time></p>

HTTP-otsakkeissa, kuten Last-Modified ja Expires, ajat ovat aina GMT-aikaa omassa muodossaan: Tue, 22 Sep 2026 08:05:31 GMT. Werkzeugin http_date tuottaa oikean muodon.

Ajat lomakkeilla

Kenttätyypit date ja datetime-local näyttävät ajan käyttäjän selaimen asetusten mukaan, mutta lähettävät arvon aina ISO-muodossa (2026-09-22 tai 2026-09-22T09:00). Palvelimen ei siis tarvitse tulkita aluekohtaisia muotoja. Sekunnit saa mukaan valitsimella step="1".

datetime-local-kentässä ei ole aikavyöhykettä. Palvelimen on päätettävä, minkä aikavyöhykkeen ajasta on kyse — yleensä tapahtuman:

paikallinen = datetime.fromisoformat(request.form["alkuaika"])
alkuaika = paikallinen.replace(tzinfo=ZoneInfo(kilpailu["aikavyohyke"]))
tallennettava = alkuaika.astimezone(timezone.utc)

Tekstikenttään syötettyä päivämäärää ei voi tulkita tietämättä käyttäjän aluetta: 1.2.2026 on Suomessa 1. helmikuuta, ja Yhdysvalloissa 1/2/2026 on 2. tammikuuta. Babel-kirjaston parse_date tulkitsee päivämäärän annetun alueen mukaan, mutta varmin ratkaisu on käyttää date-kenttää.

Viikot ja kalenteri

Suomessa viikko alkaa maanantaina ja viikot numeroidaan ISO 8601:n mukaan. Vuoden ensimmäinen viikko on se, jolla on vuoden ensimmäinen torstai, joten vuoden vaihteessa viikko voi kuulua edelliseen vuoteen: date(2027, 1, 1).isocalendar() on vuoden 2026 viikko 53. Monessa maassa viikko alkaa sunnuntaina eikä viikkonumeroita käytetä lainkaan, joten kalenterinäkymät on sovitettava alueen mukaan.

Unicode, vertailu ja lajittelu

Normalisointi

Sama näkyvä merkkijono voidaan esittää Unicodessa usealla tavalla. "Mäki" voi olla neljä koodipistettä, joista yksi on ä, tai viisi, joissa a ja sen päälle tulevat pisteet (treema) ovat erillisiä merkkejä. Ne näyttävät samalta, mutta eivät ole Pythonissa yhtä suuria. Ennen vertailua ja tallentamista merkkijonot kannattaa normalisoida NFC-muotoon.

Unicode määrittelee neljä normaalimuotoa. Tärkeimmät ovat NFC (Normalization Form Canonical Composition), jossa merkit yhdistetään valmiiksi kootuiksi merkeiksi aina kun mahdollista (a + treema → ä), ja NFD, jossa ne päinvastoin puretaan osiin. Näppäimistöltä kirjoitettu teksti on yleensä NFC-muodossa, ja W3C suosittelee sitä webiin. Hajotettua muotoa tulee vastaan esimerkiksi tekstistä, joka on kopioitu PDF-tiedostosta tai joissakin järjestelmissä tiedostonimistä.

Alla olevassa esimerkissä toisen merkkijonon ä on kirjoitettu kahdella koodipisteellä: kirjaimena a ja sen perään yhdistyvänä treemana (U+0308). Yhdistyvä merkki (engl. combining character) ei vie omaa paikkaa rivillä, vaan se piirretään edeltävän merkin päälle. Näin mikä tahansa kirjain voidaan varustaa millä tahansa tarkkeella, myös sellaisella, jolle Unicodessa ei ole valmista yhdistelmämerkkiä. Yhdistyvää treemaa ei pidä sekoittaa itsenäiseen treemaan ¨ (U+00A8), joka on tavallinen, oman paikkansa vievä merkki eikä yhdisty mihinkään.

import unicodedata

a = "M\u00e4ki"            # ä yhtenä merkkinä
b = "Ma\u0308ki"           # a + yhdistyvä treema
print(a == b, len(a), len(b))                         # False 4 5
print(unicodedata.normalize("NFC", b) == a)           # True

Tarkkeet

Tarke eli diakriittinen merkki on kirjaimeen liitettävä pieni merkki, joka muuttaa kirjaimen äännettä tai erottaa sen toisesta kirjaimesta. Tarkkeita ovat esimerkiksi treema (ä, ö, ü), rengas (å), akuutti (é), gravis (è), sirkumfleksi (ê), hattu (š, ž), tilde (ñ) ja sedilji (ç). Unicodessa tarkkeet esitetään yhdistyvinä merkkeinä, joten tarkkeellinen kirjain voidaan purkaa NFD-muotoon perusmerkiksi ja tarkkeeksi.

Se, onko tarkkeellinen kirjain oma kirjaimensa vai toisen kirjaimen muunnelma, riippuu kielestä. Suomessa ja ruotsissa å, ä ja ö ovat aakkoston omia kirjaimia, jotka lajitellaan z:n jälkeen. Saksassa ä on a:n muunnelma, ja se lajitellaan a:n kohdalle. Siksi myös tarkkeiden ohittaminen haussa on kielikysymys: englanninkieliselle käyttäjälle on luontevaa, että haku "aland" löytää Ålandin, mutta suomalaiselle se on yhtä outoa kuin se, että "sana" löytäisi sanan "sänä".

Jos tarkkeet halutaan ohittaa, merkkijono puretaan NFD-muotoon ja yhdistyvät merkit pudotetaan pois:

def ilman_tarkkeita(teksti):
    hajotettu = unicodedata.normalize("NFD", teksti)
    return "".join(m for m in hajotettu if not unicodedata.combining(m))


ilman_tarkkeita("Åland")          # 'Aland'
ilman_tarkkeita("crème brûlée")   # 'creme brulee'
ilman_tarkkeita("Øresund")        # 'Øresund' — ei muutu
ilman_tarkkeita("Straße")         # 'Straße' — ei muutu

Kaksi viimeistä esimerkkiä näyttävät tavan rajat. Ø:n vinoviiva näyttää tarkkeelta, mutta Unicode määrittelee Ø:n omaksi kirjaimekseen, jota ei voi purkaa o-kirjaimeksi ja yhdistyväksi merkiksi. Saksan ß puolestaan ei ole tarkkeellinen kirjain lainkaan, vaan alun perin kahden s-kirjaimen yhdistelmä. Tarkkeiden poistaminen ei siis tee hausta yleispätevästi "väljää"; siihen tarvittaisiin kielikohtaiset säännöt, jotka ICU-kirjasto tuntee.

Kirjainkoosta riippumaton vertailu

Kirjainkoosta riippumattomaan vertailuun kuuluu casefold(), ei lower(). Ero näkyy kirjaimissa, joiden kirjainkoosta riippumaton vastine eroaa pienestä kirjaimesta: "Straße".lower() on "straße", mutta "Straße".casefold() on "strasse", jolloin se vastaa kirjoitusasua "STRASSE". Sähköpostiosoitteiden, käyttäjänimien ja hakujen vertailuun tarvitaan sekä normalisointi että casefold().

Kielikohtainen lajittelu

Aakkosjärjestys riippuu kielestä. Pythonin sorted() lajittelee merkkien Unicode-koodipisteiden mukaan, mikä ei yleensä vastaa minkään kielen aakkosjärjestystä:

Samat nimet eri tavoin lajiteltuina
TapaJärjestys
sorted(nimet) Zulu, aalto, zeta, Ähtäri, Ångström, Öljy
sorted(nimet, key=str.casefold) aalto, zeta, Zulu, Ähtäri, Ångström, Öljy
suomi (ICU) aalto, zeta, Zulu, Ångström, Ähtäri, Öljy
saksa (ICU) aalto, Ähtäri, Ångström, Öljy, zeta, Zulu

casefold() korjaa kirjainkoon mutta ei kielen sääntöjä: suomessa å tulee ennen ä:tä, ja saksassa ä lajitellaan a:n kohdalle. Oikean kielikohtaisen lajittelun tekee Unicoden lajittelualgoritmin toteuttava ICU-kirjasto, jota Pythonissa käytetään PyICU-paketilla:

from icu import Collator, Locale

suomi = Collator.createInstance(Locale("fi_FI"))
nimet.sort(key=suomi.getSortKey)

Pythonin oma locale.strxfrm tekee saman, mutta se käyttää koko prosessin yhteistä aluekohtaista asetusta. Web-palvelimessa, joka palvelee samanaikaisesti erikielisiä käyttäjiä useissa säikeissä, sitä ei voi käyttää. SQLiten omat vertailufunktiot käsiteltiin tietokantaluennolla, ja Firestore lajittelee aina koodipisteiden mukaan.

Lajittelusääntö on määriteltävä

Nimet "Iltarastit 2024" ja "Iltarastit [peruttu]" lajittuvat ICU:n ja Pythonin mukaan eri järjestykseen, koska ICU asettaa välimerkit numeroiden edelle ja Python päinvastoin. Kumpikaan ei ole väärin. Jos järjestyksellä on väliä, se on määriteltävä vaatimuksissa tarkasti — "aakkosjärjestys" ei riitä.

Numeroiden ja päivämäärien muotoilu

Numerot, rahamäärät ja päivämäärät kirjoitetaan eri alueilla eri tavoin. Babel-kirjastossa ovat Unicoden CLDR-tietokannan muotoilusäännöt kaikille alueille. CLDR (Common Locale Data Repository) on Unicode-konsortion ylläpitämä avoin tietokanta, johon on koottu kielten ja alueiden käytännöt: päivämäärien, numeroiden ja rahamäärien muodot, kuukausien ja viikonpäivien nimet, monikkosäännöt ja kielten nimet eri kielillä. Samaa tietokantaa käyttävät muun muassa ICU, selainten Intl-rajapinta ja käyttöjärjestelmät, joten muotoilut ovat samat ohjelmointikielestä riippumatta.

from datetime import date
from babel.dates import format_date
from babel.numbers import format_currency, format_decimal

format_decimal(1234.5, locale="fi")                 # '1 234,5'
format_currency(1234.5, "EUR", locale="fi")         # '1 234,50 €'
format_date(date(2026, 9, 22), format="long", locale="fi")   # '22. syyskuuta 2026'
Samat arvot eri alueiden muotoilusäännöillä
Alue Luku Rahamäärä Pitkä päivämäärä Lyhyt päivämäärä
fi1 234,51 234,50 € 22. syyskuuta 202622.9.2026
sv_FI1 234,51 234,50 € 22 september 20262026-09-22
en_GB1,234.5€1,234.50 22 September 202622/09/2026
en_US1,234.5€1,234.50 September 22, 20269/22/26
de1.234,51.234,50 € 22. September 202622.09.26

Suomalaisissa muodoissa tuhaterotin ja euromerkin edessä oleva väli ovat sitovia välilyöntejä (U+00A0), jotta luku ei katkea kahdelle riville. Päivämäärän ja kellonajan voi muotoilla samalla kertaa suoraan halutulle aikavyöhykkeelle: format_datetime(aikaleima, tzinfo=SUOMI, locale="fi").

Älä muuta prosessin aluekohtaista asetusta

strftime("%A") ja locale.setlocale() käyttävät koko prosessin yhteistä asetusta. Jos yksi pyyntö vaihtaa sen ruotsiksi, samaan aikaan käsiteltävä suomenkielinen pyyntö saa ruotsinkieliset viikonpäivät. Babelin funktioille alue annetaan jokaisella kutsulla erikseen.

Käännökset

gettext ja Flask-Babel

Käyttöliittymän tekstit käännetään GNU gettext -järjestelmällä. Se on GNU-projektin 1990-luvulta peräisin oleva käännösjärjestelmä, jota käyttävät useimmat avoimen lähdekoodin ohjelmat. Käännökset kirjoitetaan tekstimuotoisiin .po-tiedostoihin (Portable Object), jotka käännetään ohjelman luettavaan binäärimuotoon .mo (Machine Object). Pythonin vakiokirjastossa on gettext-moduuli, ja Flaskissa sitä käytetään Flask-Babel-laajennuksella. Käännettävät merkkijonot kirjoitetaan koodiin alkuperäisellä kielellä ja merkitään funktiolla _():

from flask import Flask, request, session
from flask_babel import Babel, gettext as _

KIELET = ["fi", "en"]


def valitse_kieli():
    kieli = session.get("kieli")          # käyttäjän oma valinta ensin
    if kieli in KIELET:
        return kieli
    return request.accept_languages.best_match(KIELET) or "fi"


app = Flask(__name__)
app.config["BABEL_DEFAULT_LOCALE"] = "fi"
app.config["BABEL_DEFAULT_TIMEZONE"] = "Europe/Helsinki"
babel = Babel(app, locale_selector=valitse_kieli)

Templateissa samat funktiot ovat valmiina käytössä. Muuttujat annetaan nimettyinä paikkamerkkeinä:

<h1>{{ _('Tulokset') }}</h1>
<p>{{ _('Kilpailu %(nimi)s alkaa %(aika)s.', nimi=k.nimi, aika=k.alkuaika) }}</p>
<p>{{ ngettext('%(num)d joukkue', '%(num)d joukkuetta', maara) }}</p>

Käännösten työnkulku

Käännökset kerätään ja käännetään pybabel-työkalulla. Tiedosto babel.cfg kertoo, mistä merkkijonoja etsitään; XHTML-templatet on lisättävä siihen erikseen:

# babel.cfg
[python: **.py]
[jinja2: **/templates/**.xhtml]
pybabel extract -F babel.cfg -k _l -o messages.pot .   # kerää merkkijonot
pybabel init -i messages.pot -d translations -l en     # uusi kieli (kerran)
pybabel update -i messages.pot -d translations         # päivitä muutosten jälkeen
pybabel compile -d translations                        # käännä .po-tiedostot .mo-muotoon

Kääntäjä kirjoittaa käännökset .po-tiedostoon. Tiedosto on tavallista tekstiä, jota voi muokata tekstieditorilla tai käännöstyökalulla:

msgid "Kilpailu %(nimi)s alkaa %(aika)s."
msgstr "The %(nimi)s competition starts at %(aika)s."

msgid "%(num)d joukkue"
msgid_plural "%(num)d joukkuetta"
msgstr[0] "%(num)d team"
msgstr[1] "%(num)d teams"

Hyvin käännettävä merkkijono

Käyttöliittymä ja data

gettext kääntää vain ohjelmaan kirjoitetut tekstit. Tietokannan sisältö, kuten sarjojen nimet, kilpailujen kuvaukset tai tuotteiden nimet, tarvitsee oman ratkaisunsa: esimerkiksi Firestore-dokumentissa kentän {"fi": "Pääruoka", "en": "Main course"} tai relaatiotietokannassa erillisen käännöstaulun. Joukkueiden ja henkilöiden nimiä ei käännetä lainkaan.

Kielen valinta

Selaimen toive ja käyttäjän valinta

Selain kertoo käyttäjän kielitoiveet Accept-Language-otsakkeessa laatuarvoineen, esimerkiksi sv-FI,sv;q=0.9,en;q=0.8,fi;q=0.7. Werkzeug valitsee niistä parhaan tarjolla olevan: jos sovellus tarjoaa suomea ja englantia, request.accept_languages.best_match(["fi", "en"]) palauttaa tässä "en", koska englannin laatuarvo on suomea suurempi.

Otsake on hyvä oletus, mutta käyttäjän oma valinta on aina ensisijainen. Selaimen kieliasetus voi olla jonkun toisen tekemä, tai käyttäjä voi haluta lukea sivua toisella kielellä. Kielivalinta näytetään sivulla kunkin kielen omalla nimellä: "Suomi", "English", "Svenska" — ei lippuina, koska lippu kuvaa valtiota eikä kieltä.

Kieli osoitteessa

Paras paikka kielelle on osoite: /fi/kilpailu/42 ja /en/kilpailu/42. Silloin jaettu linkki näyttää saman kielen, jolla se jaettiin, hakukoneet löytävät kieliversiot ja välimuisti toimii ilman lisätoimia. Flaskissa kieli poimitaan osoitteesta URL-prosessoreilla, jolloin sitä ei tarvitse käsitellä jokaisessa näkymässä erikseen:

from flask import abort, g, redirect, request, url_for


@app.url_value_preprocessor
def poimi_kieli(endpoint, arvot):
    if arvot and "kieli" in arvot:
        g.kieli = arvot.pop("kieli")
        if g.kieli not in KIELET:
            abort(404)


@app.url_defaults
def lisaa_kieli(endpoint, arvot):
    # url_for lisää nykyisen kielen automaattisesti kaikkiin osoitteisiin
    if "kieli" not in arvot and app.url_map.is_endpoint_expecting(endpoint, "kieli"):
        arvot["kieli"] = g.get("kieli", "fi")


@app.route("/")
def juuri():
    kieli = request.accept_languages.best_match(KIELET) or "fi"
    return redirect(url_for("etusivu", kieli=kieli), code=303)


@app.route("/<kieli>/kilpailu/<kid>")
def kilpailu(kid):
    ...

Flask-Babelin kielivalinta palauttaa silloin yksinkertaisesti g.get("kieli", "fi").

Kieli ja välimuisti

Jos kieli valitaan Accept-Language-otsakkeen perusteella, sama osoite palauttaa eri käyttäjille erikielisen sivun. Silloin:

Kun kieli on osoitteessa, kumpaakaan ongelmaa ei synny. Vielä parempi on tallentaa välimuistiin kielestä riippumattomat tiedot, kuten tulosten laskennan tulos, ja muodostaa sivu kielen mukaan jokaisella pyynnöllä.

Kieli merkkauksessa

XHTML-sivulla kieli merkitään sekä lang- että xml:lang-attribuuttiin. Kielimerkintä ei ole vain muodollisuus: ruudunlukija valitsee sen perusteella ääntämyksen, ja selain tavutuksen ja lainausmerkit. Flask-Babelin get_locale() palauttaa muodon en_GB, joten alaviiva on muutettava yhdysmerkiksi. Sen voi tehdä kerran kaikille templateille:

from flask_babel import get_locale


@app.context_processor
def sivun_kieli():
    return {"kieli": str(get_locale()).replace("_", "-")}
<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ kieli }}" xml:lang="{{ kieli }}">
...
<link rel="alternate" hreflang="en" href="https://…/en/kilpailu/42" />
...
<p>Joukkueen nimi on <span lang="en" xml:lang="en">Dynamic Duo</span>.</p>

Tekstin sisäiset kielivaihdokset merkitään samoilla attribuuteilla, ja hreflang-linkit kertovat hakukoneille sivun muista kieliversioista. Kts. W3C:n ohje Declaring language in HTML.

Käyttöliittymä ja syötteet

Testaaminen

Esimerkkisovellus

i18n-esimerkki.zip

Kansainvälinen suunnistuskalenteri on yhden sivun Flask-sovellus, joka kokoaa luennon tärkeimmät asiat. Se luettelee kuvitteellisia suunnistustapahtumia eri maista ja aikavyöhykkeiltä. Käyttöliittymä on suomeksi, ruotsiksi, englanniksi ja saksaksi, ja juuriosoite valitsee kielen selaimen asetusten mukaan. Kokeile esimerkiksi:

Palvelimella ei ole PyICU-kirjastoa, joten sovellus lajittelee yksinkertaistetuilla säännöillä, jotka kattavat sovelluksen neljä kieltä. Sivu kertoo tämän lajitteluosiossa.

Lisätietoa

Käyttäjien kommentit

Kommentoi Lisää kommentti