gettext — Flerspråkiga internationaliseringstjänster

Källkod: Lib/gettext.py


Modulen gettext tillhandahåller tjänster för internationalisering (I18N) och lokalisering (L10N) för dina Python-moduler och -applikationer. Den stöder både GNU:s gettext API för meddelandekatalog och ett klassbaserat API på högre nivå som kan vara mer lämpligt för Python-filer. Gränssnittet som beskrivs nedan gör att du kan skriva dina modul- och applikationsmeddelanden på ett naturligt språk och tillhandahålla en katalog med översatta meddelanden för körning på olika naturliga språk.

Några tips om hur du lokaliserar dina Python-moduler och applikationer ges också.

GNU gettext API

Modulen gettext definierar följande API, som är mycket likt GNU:s API gettext. Om du använder detta API kommer du att påverka översättningen av hela din applikation globalt. Ofta är detta vad du vill ha om din applikation är enspråkig, med valet av språk beroende på användarens lokala. Om du lokaliserar en Python-modul, eller om din applikation behöver byta språk i farten, vill du förmodligen använda det klassbaserade API:et istället.

gettext.bindtextdomain(domain, localedir=None)

Bind domän till locale-katalogen localedir. Mer konkret kommer gettext att leta efter binära .mo-filer för den angivna domänen med hjälp av sökvägen (på Unix): localedir/language/LC_MESSAGES/domain.mo, där språk söks i miljövariablerna LANGUAGE, LC_ALL, LC_MESSAGES respektive LANG.

Om localedir utelämnas eller None, returneras den aktuella bindningen för domain. [1]

gettext.textdomain(domain=None)

Ändrar eller frågar efter den aktuella globala domänen. Om domain är None returneras den aktuella globala domänen, annars sätts den globala domänen till domain, som returneras.

gettext.gettext(message)

Returnerar den lokaliserade översättningen av message, baserat på den aktuella globala domänen, språket och locale-katalogen. Denna funktion är vanligtvis alias som _() i den lokala namnrymden (se exempel nedan).

gettext.dgettext(domain, message)

Som gettext(), men letar upp meddelandet i den angivna domänen.

gettext.ngettext(singular, plural, n)

Som gettext(), men tar hänsyn till pluralformer. Om en översättning hittas, tillämpa pluralformeln på n och returnera det resulterande meddelandet (vissa språk har fler än två pluralformer). Om ingen översättning hittas returneras singular om n är 1; annars returneras plural.

Pluralformeln hämtas från kataloghuvudet. Det är ett C- eller Python-uttryck som har en fri variabel n; uttrycket utvärderas till indexet för plural i katalogen. Se GNU gettext-dokumentationen” för den exakta syntaxen som ska användas i .po-filer och formlerna för en mängd olika språk.

gettext.dngettext(domain, singular, plural, n)

Som ngettext(), men sök upp meddelandet i den angivna domänen.

gettext.pgettext(context, message)
gettext.dpgettext(domain, context, message)
gettext.npgettext(context, singular, plural, n)
gettext.dnpgettext(domain, context, singular, plural, n)

Liknar motsvarande funktioner utan p i prefixet (det vill säga gettext(), dgettext(), ngettext(), dngettext()), men översättningen är begränsad till det givna meddelandet kontext.

Tillagd i version 3.8.

Observera att GNU gettext också definierar en dcgettext()-metod, men denna ansågs inte användbar och är därför för närvarande inte implementerad.

Här är ett exempel på typisk användning av detta API:

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))

Klassbaserat API

Det klassbaserade API:et i modulen gettext ger dig mer flexibilitet och större bekvämlighet än GNU gettext API:et. Det är det rekommenderade sättet att lokalisera dina Python-program och moduler. gettext definierar en GNUTranslations-klass som implementerar parsning av GNU .mo-formatfiler och har metoder för att returnera strängar. Instanser av denna klass kan också installera sig själva i det inbyggda namnrymden som funktionen _().

gettext.find(domain, localedir=None, languages=None, all=False)

Denna funktion implementerar standardalgoritmen för filsökning i .mo. Den tar en domän, identisk med vad textdomain() tar. Valfri localedir är som i bindtextdomain(). Valfritt languages är en lista med strängar, där varje sträng är en språkkod.

Om localedir inte anges används standardkatalogen för systemspråk. [2] Om languages inte anges, söks följande miljövariabler: LANGUAGE, LC_ALL, LC_MESSAGES och LANG. Den första som returnerar ett icke-tomt värde används för variabeln languages. Miljövariablerna bör innehålla en kolonseparerad lista över språk, som kommer att delas upp efter kolon för att ge den förväntade listan över språkkodsträngar.

find() expanderar och normaliserar språken och itererar sedan genom dem och söker efter en befintlig fil som är uppbyggd av dessa komponenter:

localedir/language/LC_MESSAGES/domain.mo

Det första sådana filnamnet som finns returneras av find(). Om ingen sådan fil hittas returneras None. Om all anges returneras en lista med alla filnamn i den ordning de förekommer i språklistan eller miljövariablerna.

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False)

Returnerar en *Translations-instans baserad på domain, localedir och languages, som först skickas till find() för att få en lista över de associerade .mo-filsökvägarna. Instanser med identiska .mo-filnamn cachelagras. Den faktiska klassen som instansieras är class_ om den anges, annars GNUTranslations. Klassens konstruktor måste ta ett enda filobjekt-argument.

Om flera filer hittas används de senare filerna som fallback för de tidigare. För att göra det möjligt att ställa in fallback används copy.copy() för att klona varje översättningsobjekt från cacheminnet; de faktiska instansdata delas fortfarande med cacheminnet.

Om ingen .mo-fil hittas, ger denna funktion upphov till OSError om fallback är false (vilket är standard), och returnerar en NullTranslations-instans om fallback är true.

Ändrad i version 3.3: IOError användes tidigare, nu är det ett alias för OSError.

Ändrad i version 3.11: parametern codeset har tagits bort.

gettext.install(domain, localedir=None, *, names=None)

Detta installerar funktionen _() i Pythons namnrymd för inbyggda program, baserat på domain och localedir som skickas till funktionen translation().

För parametern names, se beskrivningen av översättningsobjektets install()-metod.

Som du ser nedan markerar du vanligtvis de strängar i din applikation som är kandidater för översättning genom att linda in dem i ett anrop till _()-funktionen, så här:

print(_('Den här strängen kommer att översättas.'))

För enkelhetens skull vill du att funktionen _() installeras i Pythons namnrymd för inbyggda funktioner, så att den är lättillgänglig i alla moduler i din applikation.

Ändrad i version 3.11: names är nu en parameter som endast innehåller nyckelord.

Klassen NullTranslations

Översättningsklasser är det som faktiskt implementerar översättningen av originalfilens meddelandesträngar till översatta meddelandesträngar. Basklassen som används av alla översättningsklasser är NullTranslations; detta ger det grundläggande gränssnittet som du kan använda för att skriva dina egna specialiserade översättningsklasser. Här är metoderna i NullTranslations:

class gettext.NullTranslations(fp=None)

Tar emot ett valfritt file object fp, som ignoreras av basklassen. Initialiserar ”skyddade” instansvariabler _info och _charset som anges av härledda klasser, samt _fallback, som anges genom add_fallback(). Den anropar sedan self._parse(fp) om fp inte är None.

_parse(fp)

Denna metod, som inte finns i basklassen, tar filobjektet fp och läser data från filen och initierar dess meddelandekatalog. Om du har ett filformat för meddelandekatalogen som inte stöds bör du åsidosätta den här metoden för att tolka ditt format.

add_fallback(fallback)

Lägg till fallback som fallback-objekt för det aktuella översättningsobjektet. Ett översättningsobjekt bör konsultera fallbacken om det inte kan tillhandahålla en översättning för ett visst meddelande.

gettext(message)

Om en fallback har angetts, vidarebefordra gettext() till fallbacken. Annars returneras message. Åsidosatt i härledda klasser.

ngettext(singular, plural, n)

Om en fallback har angetts, vidarebefordra ngettext() till fallbacken. Annars returneras singular om n är 1; annars returneras plural. Åsidosatt i härledda klasser.

pgettext(context, message)

Om en fallback har angetts, vidarebefordra pgettext() till fallbacken. Annars returneras det översatta meddelandet. Åsidosatt i härledda klasser.

Tillagd i version 3.8.

npgettext(context, singular, plural, n)

Om en fallback har angetts, vidarebefordra npgettext() till fallbacken. Annars returneras det översatta meddelandet. Överskriven i härledda klasser.

Tillagd i version 3.8.

info()

Returnerar en ordbok som innehåller de metadata som finns i meddelandekatalogfilen.

charset()

Returnera kodningen för filen med meddelandekatalogen.

install(names=None)

Denna metod installerar gettext() i den inbyggda namnrymden och binder den till _.

Om parametern names anges måste den vara en sekvens som innehåller namnen på de funktioner som du vill installera i namnrymden för builtins utöver _(). Namn som stöds är 'gettext', 'ngettext', 'pgettext' och 'npgettext'.

Observera att det här bara är ett sätt, om än det mest praktiska, att göra funktionen _() tillgänglig för ditt program. Eftersom den påverkar hela applikationen globalt, och särskilt det inbyggda namnrymden, bör lokaliserade moduler aldrig installera _(). Istället bör de använda den här koden för att göra _() tillgänglig för sin modul:

import gettext
t = gettext.translation('mymodule', ...)
_ = t.gettext

Detta placerar _() endast i modulens globala namnrymd och påverkar därför endast anrop inom denna modul.

Ändrad i version 3.8: Lagt till 'pgettext' och 'npgettext'.

Klassen GNUTranslations

Modulen gettext tillhandahåller ytterligare en klass härledd från NullTranslations: GNUTranslations. Denna klass åsidosätter _parse() för att möjliggöra läsning av GNU gettext format .mo filer i både big-endian och little-endian format.

GNUTranslations utför tolkning av valfria metadata från översättningskatalogen. Det är konvention med GNU gettext att inkludera metadata som översättning för den tomma strängen. Dessa metadata är i RFC 822-stil key: value-par och bör innehålla nyckeln Project-Id-Version. Om nyckeln Content-Type hittas används egenskapen charset för att initialisera den ”skyddade” instansvariabeln _charset, med standardvärdet None om den inte hittas. Om teckenuppsättningen är angiven konverteras alla meddelande-id:n och meddelandesträngar som läses från katalogen till Unicode med hjälp av denna teckenuppsättning, annars antas ASCII.

Eftersom meddelande-id:n också läses som Unicode-strängar kommer alla *gettext()-metoder att anta att meddelande-id:n är Unicode-strängar, inte byte-strängar.

Hela uppsättningen nyckel-/värdepar placeras i en ordbok och anges som den ”skyddade” instansvariabeln _info.

Om .mo-filens magiska nummer är ogiltigt, det stora versionsnumret är oväntat, eller om andra problem uppstår när filen läses, kan instansiering av en GNUTranslations-klass ge upphov till OSError.

class gettext.GNUTranslations

Följande metoder åsidosätts från basklassens implementation:

gettext(message)

Letar upp message-id i katalogen och returnerar motsvarande meddelandesträng som en Unicode-sträng. Om det inte finns någon post i katalogen för message-id och en reserv har angetts, vidarebefordras sökningen till reservens gettext()-metod. I annat fall returneras id:t för message.

ngettext(singular, plural, n)

Gör en pluralformsuppslagning av ett meddelande-id. singular används som meddelandets id vid uppslagning i katalogen, medan n används för att avgöra vilken pluralform som ska användas. Den returnerade meddelandesträngen är en Unicode-sträng.

Om meddelandets id inte finns i katalogen och en reserv anges, vidarebefordras begäran till reservens ngettext()-metod. Annars returneras singular när n är 1, och plural returneras i alla andra fall.

Här är ett exempel:

n = len(os.listdir('.'))
cat = GNUTranslations(somefile)
message = cat.ngettext(
    'There is %(num)d file in this directory',
    'There are %(num)d files in this directory',
    n) % {'num': n}
pgettext(context, message)

Letar upp context och message id i katalogen och returnerar motsvarande meddelandesträng som en Unicode-sträng. Om det inte finns någon post i katalogen för message id och context, och en fallback har angetts, vidarebefordras sökningen till fallbackens pgettext()-metod. Annars returneras message id.

Tillagd i version 3.8.

npgettext(context, singular, plural, n)

Gör en pluralformsuppslagning av ett meddelande-id. singular används som meddelandets id vid uppslagning i katalogen, medan n används för att avgöra vilken pluralform som ska användas.

Om meddelande-id för kontext inte finns i katalogen och en reserv anges, vidarebefordras begäran till reservens npgettext()-metod. Annars returneras singular när n är 1, och plural returneras i alla andra fall.

Tillagd i version 3.8.

Stöd för Solaris meddelandekatalog

Operativsystemet Solaris definierar sitt eget binära filformat .mo, men eftersom det inte finns någon dokumentation om detta format stöds det inte för närvarande.

Katalogens konstruktör

GNOME använder en version av modulen gettext av James Henstridge, men denna version har ett något annorlunda API. Dess dokumenterade användning var:

import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))

För kompatibilitet med denna äldre modul är funktionen Catalog() ett alias för funktionen translation() som beskrivs ovan.

En skillnad mellan denna modul och Henstridges: hans katalogobjekt stödde åtkomst via ett mappnings-API, men detta verkar vara oanvänt och stöds därför inte för närvarande.

Internationalisera dina program och moduler

Internationalisering (I18N) avser den process genom vilken ett program görs medvetet om att det kan användas på flera språk. Lokalisering (L10N) avser anpassningen av ditt program, när det väl har internationaliserats, till det lokala språket och kulturella vanor. För att kunna tillhandahålla flerspråkiga meddelanden för dina Python-program måste du vidta följande åtgärder:

  1. förbereda ditt program eller din modul genom att särskilt markera översättningsbara strängar

  2. köra en uppsättning verktyg över dina markerade filer för att generera kataloger över råmeddelanden

  3. skapa språkspecifika översättningar av meddelandekatalogerna

  4. använda modulen gettext så att meddelandesträngar översätts korrekt

För att förbereda din kod för I18N måste du titta på alla strängar i dina filer. Varje sträng som behöver översättas bör markeras genom att den omsluts av _('...') — det vill säga ett anrop till funktionen _. Till exempel:

filename = 'mylog.txt'
message = _('writing a log message')
with open(filename, 'w') as fp:
    fp.write(message)

I det här exemplet markeras strängen 'writing a log message' som en kandidat för översättning, medan strängarna 'mylog.txt' och 'w' inte gör det.

Det finns ett par verktyg för att extrahera de strängar som är avsedda för översättning. GNU:s ursprungliga gettext stödde endast C- eller C++-källkod, men dess utökade version xgettext skannar kod skriven i ett antal språk, inklusive Python, för att hitta strängar som markerats som översättningsbara. Babel är ett internationaliseringsbibliotek för Python som innehåller ett pybabel-skript för att extrahera och sammanställa meddelandekataloger. François Pinards program som heter xpot gör ett liknande jobb och finns tillgängligt som en del av hans po-utils-paket.

(Python innehåller också rena Python-versioner av dessa program, kallade pygettext.py och msgfmt.py; vissa Python-distributioner installerar dem åt dig. pygettext.py liknar xgettext, men förstår bara Python-källkod och kan inte hantera andra programmeringsspråk som C eller C++. pygettext.py stöder ett kommandoradsgränssnitt som liknar xgettext; för detaljer om hur det används, kör pygettext.py --help. msgfmt.py är binärt kompatibelt med GNU msgfmt. Med dessa två program behöver du kanske inte GNU gettext-paketet för att internationalisera dina Python-program)

xgettext, pygettext och liknande verktyg genererar .po-filer som är meddelandekataloger. De är strukturerade, läsbara filer som innehåller alla markerade strängar i källkoden, tillsammans med en platshållare för de översatta versionerna av dessa strängar.

Kopior av dessa .po-filer överlämnas sedan till de enskilda mänskliga översättarna som skriver översättningar för varje naturligt språk som stöds. De skickar tillbaka de färdiga språkspecifika versionerna som en <language-name>.po-fil som sammanställs till en maskinläsbar .mo binär katalogfil med hjälp av programmet msgfmt. Filerna .mo används av modulen gettext för den faktiska översättningsbearbetningen vid körning.

Hur du använder modulen gettext i din kod beror på om du internationaliserar en enskild modul eller hela programmet. De kommande två avsnitten kommer att diskutera varje fall.

Lokalisera din modul

Om du lokaliserar din modul måste du se till att inte göra globala ändringar, t.ex. i det inbyggda namnrymden. Du bör inte använda GNU gettext API utan istället det klassbaserade API:et.

Låt oss säga att din modul heter ”spam” och modulens olika översättningar av naturligt språk .mo-filer finns i /usr/share/locale i GNU gettext-format. Så här skulle du skriva högst upp i din modul:

import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.gettext

Lokalisera din applikation

Om du lokaliserar ditt program kan du installera _()-funktionen globalt i det inbyggda namnrymden, vanligtvis i programmets huvuddrivrutinsfil. Detta gör att alla dina applikationsspecifika filer bara använder _('...') utan att du uttryckligen behöver installera den i varje fil.

I det enkla fallet behöver du bara lägga till följande kod i huvuddrivrutinsfilen för din applikation:

import gettext
gettext.install('myapplication')

Om du behöver ange locale-katalogen kan du skicka den till funktionen install():

import gettext
gettext.install('myapplication', '/usr/share/locale')

Byta språk i farten

Om ditt program behöver stödja många språk samtidigt kanske du vill skapa flera översättningsinstanser och sedan växla mellan dem uttryckligen, så här:

import gettext

lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])

# start by using language1
lang1.install()

# ... time goes by, user selects language 2
lang2.install()

# ... more time goes by, user selects language 3
lang3.install()

Uppskjutna omräkningar

I de flesta kodningssituationer översätts strängar där de kodas. Ibland behöver du dock markera strängar för översättning, men skjuta upp den faktiska översättningen till senare. Ett klassiskt exempel är:

animals = ['mollusk',
           'albatross',
           'rat',
           'penguin',
           'python', ]
# ...
for a in animals:
    print(a)

Här vill du markera strängarna i listan djur som översättningsbara, men du vill faktiskt inte översätta dem förrän de skrivs ut.

Här är ett sätt du kan hantera denna situation:

def _(message): return message

animals = [_('mollusk'),
           _('albatross'),
           _('rat'),
           _('penguin'),
           _('python'), ]

del _

# ...
for a in animals:
    print(_(a))

Detta fungerar eftersom dummydefinitionen av _() helt enkelt returnerar strängen oförändrad. Och denna dummydefinition kommer tillfälligt att åsidosätta alla definitioner av _() i den inbyggda namnrymden (fram till kommandot del). Var dock försiktig om du har en tidigare definition av _() i den lokala namnrymden.

Observera att den andra användningen av _() inte identifierar ”a” som översättningsbart till programmet gettext, eftersom parametern inte är en bokstavlig sträng.

Ett annat sätt att hantera detta är med följande exempel:

def N_(message): return message

animals = [N_('mollusk'),
           N_('albatross'),
           N_('rat'),
           N_('penguin'),
           N_('python'), ]

# ...
for a in animals:
    print(_(a))

I det här fallet markerar du översättningsbara strängar med funktionen N_(), vilket inte står i konflikt med någon definition av _(). Du måste dock lära ditt program för extrahering av meddelanden att leta efter översättningsbara strängar markerade med N_(). xgettext, pygettext, pybabel extract och xpot stöder alla detta genom att använda kommandoradsalternativet -k. Valet av N_() här är helt godtyckligt; det kunde lika gärna ha varit MarkThisStringForTranslation().

Tack till

Följande personer har bidragit med kod, feedback, designförslag, tidigare implementeringar och värdefull erfarenhet för att skapa denna modul:

  • Peter Funk

  • James Henstridge

  • Juan David Ibáñez Palomar

  • Marc-André Lemburg

  • Martin von Löwis

  • François Pinard

  • Barry Warsaw

  • Gustavo Niemeyer

Fotnoter