Sådan oversætter du din webapplikation

Oct 26, 2007

For noget tid siden fik jeg til opgave at lave en multisproget applikation, m.o.a. et site som skulle kunne vises i flere forskellige sprog. Da jeg aldrig havde prøvet dette før, brugte jeg forholdsvis lang tid på at tænkt over hvordan den bedste og letteste måde, at gøre dette på, ville være. Som en hver anden sikkert ville gøre det, spurgte jeg mig frem, og så på hvordan andre applikationer implementerede flere sprog. Jeg så bla. på osCommerce, og ASP.Net forummet Yet Another Forum, som jeg for nyligt havde arbejdet med - men ingen af dem synes at håndterer denne opgave specielt fordelagtige. Efter at have være igennem et par applikationer mere, og snakket flere forskellige løsninger igennem med andre, faldt jeg endelig over en løsning, som synes at være smart.

Jeg skal fra start af være ærlig og give massere af credit til Think Vitamin, for deres artikkel omkring internationalisering af websites, da denne post ikke var blevet til uden at jeg havde læst deres først (backtrack med ord :)).

Først vil jeg starte ud med at fortælle lidt om den metode som jeg havde i tankerne i starten af forløbet, og forklare lidt om hvorfor det ikke blev denne vi anvendte i sidste ende. Hvis du blot vil læse om den "optimale måde", kan du springe dette skridt over, og gå direkte til gettext-metoden.

En ikke helt optimal metode

En af de første metoder jeg var inde på, var associative arrays metoden. En metode hvor du, for hvert sprog i din applikation, opretter et array. Arrayet får som index den streng der skal oversættes, og som værdi den oversatte streng.

Følgende eksempel viser hvordan du ville kunne bruge associative arrays, til at implementerer strenge på dansk og engelsk. For at udvide med flere sprog, skal du blot oprette endnu et array, og tilføje cases til switch-funktionen:

//sprog setup $lang = array(); //skal senere holde det valge sprog-array

$english = array(); $english["Klik for at fortsætte"] = "Click to continue";

$dansk = array(); $dansk["Klik for at fortsætte"] = "Klik for at fortsætte";

//sprog bestemmes efter en URL-parameter ?lang=xy switch($_GET["lang"]) { case "en": $lang = $english; break;

default: $lang = $dansk; break; }

//benyt det valge sprog-array echo $lang["Klik for at fortsætte"];

Det lader jo til at virker meget fint - og det gør det da også langt hen ad vejen. Men hvad nu hvis ud kommer til at lave en stavefejl? Hvis du f.eks. glemmer et "t" i "fortsætte", når du vil udskrive den oversatte tekst. Tja - Alt efter hvilket programmeringssprog du benytter, vil der opstå problemer af en eller anden art. Den mildeste heraf vil blot undlade at vise nogen tekst overhovedet, da der jo er angivet et ikke-eksisterende index = ingen værdi/tekst. I værste fald vil hele din applikation kaste en fejl, og siden vil ikke være brugbar. Dine brugere vil højst sansynligt blive efterladt i total forviring, og sitet synes pludseligt at forekomme rimelig uprof!

En anden ting, som kan være besværlig, ved brugen af associative arrays, er vedligeholdelse. Det kan hurtigt blive uoverskueligt at skulle finde/opdaterer/slette eller oprette en enkelt streng i alle "sprogene" - og det kommer man jo nok ikke udenom! Forestil dig at du har bare 100 strenge, af forskellige længder, som du skal have i flere forskellige sprog. Du kan selv lave den danske, muligvis andre, men hvordan vil du præsenterer dette for din oversætter? Det er ikke sikkert at denne har et pind forstand på programmering, og bare synet af et array vil skræmme personen og få opgaven til at virke liiiidt for kompliceret! Du kunne så selvfølgelig vælge at skrive hvert streng på en linie i et andet dokument, men så skal du til at kopiere/indsætte alle strengene på den rigtige plads i arrayet - for hvert eneste sprog! Naahhh...

Okay - jeg tror jeg har gjort det klart nok. Associative arrays er ikke den mest optimale løsning - men hvad er det så? Som den PHP-nørd jeg nu en gang er endt ud med at blive, søgte jeg selvfølgelig svaret i dette sprog - Og hvad jeg kom frem til var det dedikerede GNU Projekt gettext (som vidst også kan findes til andet end PHP).

Oversættelse med gettext

Nu har jeg jo været inde på nogle af de problematikker der kan ligge i f.eks. at bruge associative arrays, så for at gøre en lang historie kort, så kommer du selvfølgelig ud over disse ved at benytte gettext. Gettext introducerer samtidig en fantastisk måde at adskille kode fra indhold (læs: tekst), og når det endelig er sat op, er det enormt nemt at vedligeholde.

Ved at gennemfører følgende 6 trin, kan du for alvor nyde effekten af gettext:

  1. Den tekst du skal have oversat skal parses gennem PHP-funktionen gettext().
  2. Efter at alt tekst der skal oversættes er blevet parset bruges kommandoen xgettext som opretter et PO (Portable Object), hvilket i bund og grund er et rent tekst dokument.
  3. Du sendt PO filen til din oversætter, som kan oversætte teksten med en PO-editor.
  4. Din oversætter sender PO filen tilbage, nu indeholdende din tekst plus den oversatte tekst.
  5. Du compiler PO filen til en MO fil (Machine Object) som kan læses af gettext() funktionen.
  6. Du opsætter din locale (sprog opsætning for din applikation) og kan nu nyde din side i alle de oversatte sprog.
Gettext step by step

1). Benyt gettext() funktionen:

Det første punkt er nok det mest bøvlede, om ikke andet så det der vil tage mest tid. Ikke desto mindre er det ganske simpelt, men blot kedeligt. En streng som følgende:

<p>Denne tekst skal oversættes.</p>
Skal blive til:
<p><?php echo gettext("Denne tekst skal oversættes"); ?>.</p>
Dette skal som sagt gøres for alle strenge du vil have sendt med til din oversætter.

2). Oprettelse af PO filen:

I dette skridt skal du have gang i programmet xgettext, hvilket skal bruges til at løbe alle dine PHP-filer igennem, for at udtrække de strenge du har benyttet gettext()-funktionen på. Alle de fundne strenge vil blive samlet i en PO fil, som du kan sende afsted til oversættelse. En PO fil er i bund og grund blot en tekstfil tilføjet lidt ekstra informationer.

Benytter du Linux er det sandsynligt at du allerede har xgettext installeret, alternativt vil det ikke være svært at få fat i. Du kan evt. installerer gettext-modulet ved følgende kommando (eller benyt din egen packagemanager):

sudo apt-get install gettext
Alternativt findes PO-editoren poedit til både Windows, Mac og Linux. Til Windows kommer denne editor med alle de nødvendige hjælpeprogrammer til at kompleterer oversættelsen. Mens selve oversættelsen ikke er afhængig af poedit, kan programmet være meget anvendeligt alligevel, da det giver en nem og overskuelig måde for både at oversætte, kompilerer og vedligeholde.

Tager vi udgangspunkt i opretelsen af PO filen fra selve editoren poedit, er det nødvendigt at definerer et par indstillinger. Ved opretelse af et nyt "katalog" (som det kaldes på dansk) skal du under "stier" definerer kataloget som udgør roden i din applikation. Dersuden skal du udfylde nogle standardfelter (Projektinformation) som bruges rent informativt i PO filen. Når disse indstillinger er sat, vil du blive bedt om at gemme din PO fil. Første gang du gemmer denne vil poedit automatisk hente de strenge du har valgt skal med til oversættelse - herefter kan du til hver en tid opdaterer din PO fil ved blot at trykke på "Opdaterings katalog"-knappen.

Her skal du angive roden i din applikation. Dette er hvad du vil se, når du har opdateret dit katalog.

Alternativt skal du have gang i kosollen, hvilket dog heller ikke er svært. For at oprette en PO fil, som samler strenge fra alle PHP-filer i flere kataloger, skal du blot bruge følgende kommando:

xgettext -o messages.po *.php ./andet-katalog/*.php
Herefter vil du, i det katalog hvod du udfører kommandoen, få oprettet din PO fil.

3 & 4). Send PO filen til din oversætter

Som jeg nævnte tidligere virker det ikke særlig fordelagtigt at skulle oversætte direkte i en tekstfil, og da slet ikke hvis man skal sidde og passe sine strenge ind i et array. PO filer er heller ikke specielt pæne at skulle redigerer direkte i, selvom det kan lade sig gøre - men hvilken fil er det? Din oversætter bør derfor få fat i en PO editor, som f.eks. poedit som er ganske gratis. Herefter er oversættelsen let som en leg - Hvad din oversætter vil se, er hvad du ser på billedet herunder:

Oversættelse med poedit er lige til!

Oversætteren oversætter samtlige strenge, og sender den oversatte PO fil tilbage til dig.

5). Compiling af PO til MO

For at compile PO filen til en MO fil, som er den fil PHP bruger for at oversætte strenge der er benyttet gettext()-funktionen på, skal vi have gang i msgfmt programmet. msgfmt komme ligeledes med poedit til Windows, og til Linux ved at installerer hele gettext modulet. Igen er det meget nemmere ved brugen af poedit, og faktisk bliver dette skridt automatisk udført allerede hos din oversætter, men for en god ordens skyld tager vi det lige med her.

Når du har modtaget din PO fil fra oversætteren skal du blot åbne denne i poedit og trykke "gem"! Jeps, det var det, så har du oprettet en MO fil. vil du benytte kommandoprompten, skal du blot udfører følgende:

msgfmt navnet-på-din-po-fil.po
6). Sprog opsætning i din applikation:

Foruden at skulle finde alle de mange strenge og pakke dem ind i gettext()-funktionen, så var dette sidste skridt faktisk det eneste der gav mig en smule problemer. Problemerne grunder i at dette skridt kan være afhængig af din webserver - så frygt ikke hvis ikke du får det til at virke i første omgang (spørg evt. din udbyder til råds).

Først og fremmest skal du have opbygget en filstruktur, som vist på billedet til højre. Jeg har valgt at ligge alle mine sprog i en overordnet mappe kaldet "languages" - navnet på denne mappe kan du selv bestemme, men navnet skal bruges som et "domæne", som vist lige om lidt.

I dette eksempel har jeg kun taget to sprog med, skal du have flere sprog i din applikation, skal du oprette en mappe for hver af disse, hver mappe navngives med sprogets locale-code (F.eks. "en_US" for engelsk, "da_DK" for dansk osv.). Inde i hvert af disse mapper opretter du endnu en mappe som altid hedder "LC_MESSAGES". Som det også er vidst på billedet, skal du placerer MO-filerne i LM_MESSAGES mapperne, jeg har min tilsvarende PO filer liggende i samme mappe, dette er ikke nødvendigt, men blot overskueligt.

Den allersidste del der skal til, er et par linier kode, og så er du faktisk kørende. Tilføj følgende linier i evt. i en fil du du altid kan inkludere:

<?php //få fat i det valgte sprog $local = $_GET["lang"]; if($local == "") : $local = "da_DK"; endif;

//definer serverens "locale" putenv("LC_ALL=$local"); setlocale(LC_ALL, $local);

//lås domænet "messages" til biblioteket "languages" bindtextdomain("messages", "/absolute/path/to/languages/");

//bestem hvordan din oversatte tekst skal returneres, ændre evt. til iso-8859-1 bind_textdomain_codeset("messages","UTF-8");

//bestem domæne-navn, betyder at gettext vil se efter filer kaldet messages.mo textdomain("messages"); ?>

I eksemplet ovenfor, kan vi skifte sprog vha. en query streng. Personligt har jeg brugt cookies, men dette valg er jo op til dig. En af de rigtig fede ting ved denne metode er, at din applikation altid vil udskrive noget. Bilver $local variablen i ovenstående eksempel ikke sat, eller bliver den sat til noget der ikke giver mening, vil gettext altid udskrive dit originale sprog - i mit tilfælde dansk.

Puhaa - det var en lang smøre, men skulle du af en eller anden grund ikke have fået nok, kan du evt. læse mere om gettext på følgende URLer:

http://dk.php.net/gettext

http://www.gnu.org/software/gettext/

Comments

comments powered by Disqus