hur man bygger en flerspråkig App: en Demo med PHP och Gettext
oavsett om du bygger en webbplats eller en fullfjädrad webbapplikation, vilket gör den tillgänglig för en bredare publik kräver ofta att den är tillgänglig på olika språk och språk.
grundläggande skillnader mellan de flesta mänskliga språk gör detta allt annat än enkelt. Skillnaderna i grammatikregler, språknyanser, datumformat och mer kombineras för att göra lokalisering till en unik och formidabel utmaning.
Tänk på detta enkla exempel.
pluraliseringsregler på engelska är ganska enkla: du kan ha en singularform av ett ord eller en pluralform av ett ord.
på andra språk, men – som slaviska språk – finns det två pluralformer utöver singularen. Du kan till och med Hitta språk med totalt fyra, fem eller sex pluralformer, som på Slovenska, irländska eller arabiska.
hur din kod är organiserad och hur dina komponenter och gränssnitt är utformade spelar en viktig roll för att bestämma hur enkelt du kan lokalisera din applikation.
internationalisering (i18n) av din kodbas, hjälper till att säkerställa att den kan anpassas till olika språk eller regioner relativt enkelt. Internationalisering görs vanligtvis en gång, helst i början av projektet för att undvika att behöva stora förändringar i källkoden på vägen.
när din kodbas har internationaliserats blir lokalisering (l10n) en fråga om att översätta innehållet i din ansökan till ett specifikt språk/språk.
Lokalisering måste utföras varje gång ett nytt språk eller en ny region behöver stödjas. När en del av gränssnittet (som innehåller text) uppdateras blir nytt innehåll tillgängligt – vilket sedan måste lokaliseras (dvs. översättas) till alla stödda platser.
i den här artikeln lär vi oss att internationalisera och lokalisera programvara skriven i PHP. Vi kommer att gå igenom de olika implementeringsalternativen och de olika verktygen som finns tillgängliga för att underlätta processen.
verktyg för internationalisering
det enklaste sättet att internationalisera PHP-programvara är att använda array-filer. Arrayer kommer att fyllas med översatta strängar, som sedan kan slås upp inifrån mallar:
<h1><?=$TRANS?></h1>
detta är dock knappast ett rekommenderat sätt för seriösa projekt, eftersom det definitivt kommer att innebära underhållsproblem på vägen. Vissa problem kan till och med dyka upp i början, till exempel bristen på stöd för variabel interpolering eller pluralisering av substantiv och så vidare.
ett av de mest klassiska verktygen (ofta som referens för i18n och l10n) är ett Unix-verktyg som heter Gettext.
även om det går tillbaka till 1995 är det fortfarande ett omfattande verktyg för att översätta programvara som också är lätt att använda. Även om det är ganska lätt att komma igång med, har det fortfarande kraftfulla stödverktyg.
Gettext är vad vi kommer att använda i det här inlägget. Vi kommer att presentera ett bra GUI-program som kan användas för att enkelt uppdatera dina l10n-källfiler och därigenom undvika behovet av att hantera kommandoraden.
bibliotek för att göra det enkelt
det finns stora PHP-webbramar och bibliotek som stöder Gettext och andra implementeringar av i18n. vissa är enklare att installera än andra, eller sportar ytterligare funktioner eller stöder olika i18n-filformat. Även om vi i det här dokumentet fokuserar på verktygen som tillhandahålls med PHP-kärnan, här är en lista över några andra som är värda att nämna:
-
oscarotero / Gettext: Gettext stöd med ett objektorienterat gränssnitt; innehåller förbättrade hjälpfunktioner, kraftfulla extraktorer för flera filformat (vissa av dem stöds inte av kommandot
gettext
). Kan också exportera till format utöver bara.mo/. po-filer, vilket kan vara användbart om du behöver integrera dina översättningsfiler i andra delar av systemet, som ett JavaScript-gränssnitt. -
symfony / translation: stöder många olika format, men rekommenderar att du använder verbose XLIFF ’ s. innehåller inte hjälpfunktioner eller en inbyggd extraktor, men stöder platshållare som använder
strtr()
internt. -
zend / i18n: Stöder array och INI-filer, eller Gettext format. Implementerar ett cachningslager för att undvika att behöva läsa filsystemet varje gång. Inkluderar även visa hjälpare, och locale medvetna inmatningsfilter och validerare. Det har dock ingen meddelandextraktor.
andra ramverk inkluderar även i18n-moduler, men de är inte tillgängliga utanför deras kodbaser:
-
Laravel: stöder grundläggande array-filer; har ingen automatisk extraktor men innehåller en
@lang
hjälpare för mallfiler. -
Yii: Stöder array, Gettext och databasbaserad översättning, och innehåller en meddelanden extractor. Stöds av
Intl
– tillägget, tillgängligt sedan PHP 5.3, och baserat på ICU-projektet. Detta gör det möjligt för Yii att köra kraftfulla ersättningar, som att stava ut siffror, formatera datum, tider, intervaller, valuta och ordinaler.
om du väljer att gå till ett av biblioteken som inte ger några extraktorer kanske du vill använda Gettext-formaten, så att du kan använda den ursprungliga Gettext-verktygskedjan (inklusive Poedit) som beskrivs i resten av kapitlet.
installera Gettext
du kan behöva installera Gettext och det relaterade PHP-biblioteket med hjälp av din pakethanterare, som apt-get eller yum. När det är installerat aktiverar du det genom att lägga till extension=gettext.so
(Linux/Unix) eller extension=php_gettext.dll
(Windows) till din php.ini
– fil.
här kommer vi också att använda Poedit för att skapa översättningsfiler. Du kommer förmodligen att hitta den i systemets pakethanterare; den är tillgänglig för Unix, Mac och Windows och kan laddas ner gratis på sin webbplats också.
typer av Gettext-filer
det finns tre filtyper som du vanligtvis hanterar när du arbetar med Gettext.
de viktigaste är PO (Portable Object) och MO (Machine Object) – filer, den första är en lista över läsbara ”översatta objekt” och den andra är motsvarande binär (tolkas av Gettext när du gör lokalisering). Det finns också en POT (po Mall) fil, som helt enkelt innehåller alla befintliga nycklar från dina källfiler, och kan användas som en guide för att generera och uppdatera alla PO-filer.
mallfilerna är inte obligatoriska; beroende på vilket verktyg du använder för att göra l10n, kommer du att bli bra med bara PO/MO-filer. Du har ett par po / MO-filer per språk och region, men bara en pott per domän.
separerande domäner
det finns vissa fall, i stora projekt, där du kan behöva separera översättningar när samma ord förmedlar olika mening i olika sammanhang.
i dessa fall måste du dela upp dem i olika ”domäner”, som i grunden heter grupper av POT/PO/MO-filer, där filnamnet är den nämnda översättningsdomänen.
små och medelstora projekt brukar, för enkelhet, bara använda en domän; dess namn är godtyckligt, men vi kommer att använda ”main” för våra kodexempel.
i Symfony-projekt används till exempel domäner för att separera översättningen för valideringsmeddelanden.
Lokalkod
en lokal är helt enkelt en kod som identifierar en version av ett språk. Det definieras enligt ISO 639-1 och ISO 3166-1 alpha-2-specifikationerna: två små bokstäver för språket, eventuellt följt av ett understreck och två stora bokstäver som identifierar land eller regional kod.
för sällsynta språk används tre bokstäver.
för vissa talare kan landsdelen verka överflödig. Faktum är att vissa språk har dialekter i olika länder, såsom österrikisk tyska (de_AT) eller brasiliansk portugisiska (pt_BR). Den andra delen används för att skilja mellan dessa dialekter – när den inte är närvarande tas den som en ”generisk” eller ”hybrid” version av språket.
katalogstruktur
för att använda Gettext måste vi följa en specifik struktur av mappar.
först måste du välja en godtycklig rot för dina l10n-filer i ditt källförråd. Inuti den har du en mapp för varje nödvändig lokal och en fast ”LC_MESSAGES” – mapp som innehåller alla dina po/MO-par.
pluralformer
som vi sa i inledningen kan olika språk sporta olika pluraliseringsregler. Gettext sparar oss dock detta problem.
när du skapar en ny .PO-fil, du måste deklarera pluraliseringsreglerna för det språket, och översatta bitar som är pluralkänsliga kommer att ha en annan form för var och en av dessa regler.
när du ringer Gettext i kod måste du ange ett nummer relaterat till meningen (t.ex. för frasen ”du har n-meddelanden.”, du måste ange värdet på n), och det kommer att fungera rätt formulär att använda – även med strängbyte om det behövs.
Pluralregler består av antalet regler som krävs med ett booleskt test för varje regel (test för högst en regel kan utelämnas). Till exempel:
-
japanska:
nplurals=1; plural=0;
– en regel: det finns inga pluralformer -
engelska:
nplurals=2; plural=(n != 1);
– två regler: Använd pluralform endast när n inte är 1, annars använd singularformen. -
Brasiliansk Portugisiska:
nplurals=2; plural=(n > 1);
– två regler, Använd pluralform endast när n är större än 1, använd annars singularformen.
för en djupare förklaring finns det en informativ LingoHub-handledning tillgänglig online.
Gettext bestämmer vilken regel som ska användas baserat på det angivna numret och kommer att använda rätt lokaliserad version av strängen. För strängar där pluralisering måste hanteras, måste du inkludera i .po fil en annan mening för varje plural regel definieras.
Exempelimplementering
efter all den teorin, låt oss bli lite praktiska. Här är ett utdrag av a .po-fil (oroa dig inte ännu för mycket om syntaxen, men istället bara få en känsla av det övergripande innehållet):
msgid ""msgstr """Language: pt_BR\n""Content-Type: text/plain; charset=UTF-8\n""Plural-Forms: nplurals=2; plural=(n > 1);\n"msgid "We're now translating some strings"msgstr "Nós estamos traduzindo algumas strings agora"msgid "Hello %1$s! Your last visit was on %2$s"msgstr "Olá %1$s! Sua última visita foi em %2$s"msgid "Only one unread message"msgid_plural "%d unread messages"msgstr "Só uma mensagem não lida"msgstr "%d mensagens não lidas"
det första avsnittet fungerar som en rubrik, med msgid
och msgstr
tom.
den beskriver filkodning, pluralformer och några andra saker. Det andra avsnittet översätter en enkel sträng från engelska till Brasiliansk Portugisiska, och den tredje gör detsamma, men utnyttjar strängbyte från sprintf
, vilket gör det möjligt för översättningen att innehålla användarnamnet och besöksdatumet.
det sista avsnittet är ett urval av pluraliseringsformer, som visar singular-och pluralversionen som msgid
på engelska och deras motsvarande översättningar som msgstr
0 och 1 (efter numret som ges av pluralregeln).
där används strängbyte också, så numret kan ses direkt i meningen med %d
. Pluralformerna har alltid två msgid
(singular och plural), så det rekommenderas att inte använda ett komplext språk som källa till översättning.
Lokaliseringsnycklar
som du kanske har märkt använder vi den faktiska engelska meningen som käll-ID. Att msgid
är densamma används i alla dina .po-filer, vilket innebär att andra språk kommer att ha samma format och samma msgid
fält men översatta msgstr
linjer.
på tal om översättningsnycklar finns det två vanliga ”filosofiska” tillvägagångssätt här:
1. msgstr som en riktig mening
de viktigaste fördelarna med detta tillvägagångssätt är:
-
om det finns delar av programvaran som inte är översatta på ett visst språk, kommer nyckeln som visas fortfarande att behålla någon mening. Om du till exempel kan översätta från engelska till spanska men behöver hjälp med att översätta till Franska kan du publicera den nya sidan med saknade franska meningar, och delar av webbplatsen visas istället på engelska.
-
det är mycket lättare för översättaren att förstå vad som händer och göra en korrekt översättning baserad på
msgid
. -
det ger dig ”gratis” l10n för ett språk – källan.
å andra sidan är den primära nackdelen att om du behöver ändra den faktiska texten måste du ersätta samma msgid
över flera språkfiler.
2. msgstr som en unik, strukturerad nyckel
detta skulle beskriva meningsrollen i applikationen på ett strukturerat sätt, inklusive mallen eller delen där strängen finns istället för dess innehåll.
Detta är ett bra sätt att få koden organiserad, separera textinnehållet från malllogiken. Det kan dock ge problem för översättaren som skulle sakna sammanhanget.
en källspråkfil skulle behövas som grund för andra översättningar. Till exempel skulle utvecklaren helst ha en ”en.PO ” fil, att översättare skulle läsa för att förstå vad man ska skriva i ”fr.po”.
saknade översättningar skulle visa meningslösa tangenter på skärmen (”top_menu.välkommen ”istället för” Hej där, användare!”på nämnda oöversatta franska sida).
det är bra eftersom det skulle tvinga översättningen att vara komplett innan publicering – men dåligt eftersom översättningsfrågor skulle vara riktigt hemska i gränssnittet. Vissa bibliotek innehåller dock ett alternativ att ange ett visst språk som ”reserv”, som har ett liknande beteende som det andra tillvägagångssättet.
Gettext-handboken gynnar det första tillvägagångssättet, eftersom det i allmänhet är lättare för översättare och användare vid problem. Det är det tillvägagångssätt vi kommer att använda här också.
det bör dock noteras att Symfony-dokumentationen gynnar sökordsbaserad översättning, för att möjliggöra oberoende ändringar av alla översättningar utan att påverka mallar också.
daglig användning
i en vanlig applikation skulle du använda vissa Gettext-funktioner när du skriver statisk text på dina sidor.
dessa meningar skulle då visas i .po-filer, få översatt, kompileras till. mo filer, och sedan används av Gettext när rendering själva gränssnittet. Med tanke på det, låt oss knyta samman det vi har diskuterat hittills i ett steg-för-steg-exempel:
1. En exempelmall fil, inklusive några olika gettext samtal
<?php include 'i18n_setup.php' ?><div> <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1> <!-- code indented this way only for legibility → <?php if ($unread): ?> <h2> <?=sprintf( ngettext('Only one unread message', '%d unread messages', $unread), $unread )?> </h2> <?php endif ?></div><h1><?=gettext('Introduction')?></h1><p><?=gettext('We\'re now translating some strings')?></p>
-
gettext()
översätter helt enkelt enmsgid
till motsvarandemsgstr
för ett visst språk. Det finns också shorthand-funktionen_()
som fungerar på samma sätt -
ngettext()
gör detsamma men med pluralregler -
det finns också
dgettext()
ochdngettext()
, som låter dig åsidosätta domänen för ett enda samtal (mer om domänkonfiguration i nästa exempel)
2. En exempelinstallationsfil (i18n_setup.php som används ovan), välja rätt språk och konfigurera Gettext
använda Gettext innebär en bit av en standardtext kod, men det handlar mest om att konfigurera språkkatalogen och välja lämpliga parametrar (en Språk och en domän).
<?php/** * Verifies if the given $locale is supported in the project * @param string $locale * @return bool */function valid($locale) { return in_array($locale, ) && valid($_GET)) { // the locale can be changed through the query-string $lang = $_GET; //you should sanitize this! setcookie('lang', $lang); //it's stored in a cookie so it can be reused} elseif (isset($_COOKIE) && valid($_COOKIE)) { // if the cookie is present instead, let's just keep it $lang = $_COOKIE; //you should sanitize this!} elseif (isset($_SERVER)) { // default: look for the languages the browser says the user accepts $langs = explode(',', $_SERVER); array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ); }); foreach ($langs as $browser_lang) { if (valid($browser_lang)) { $lang = $browser_lang; break; } }}// here we define the global system locale given the found languageputenv("LANG=$lang");// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instancesetlocale(LC_ALL, $lang);// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mobindtextdomain('main', '../locales');// indicates in what encoding the file should be readbind_textdomain_codeset('main', 'UTF-8');// if your application has additional domains, as cited before, you should bind them here as wellbindtextdomain('forum', '../locales');bind_textdomain_codeset('forum', 'UTF-8');// here we indicate the default domain the gettext() calls will respond totextdomain('main');// this would look for the string in forum.mo instead of main.mo// echo dgettext('forum', 'Welcome back!');?>
3. Förbereda översättning för den första körningen
en av de stora fördelarna Gettext har över anpassade ram i18n paket är dess omfattande och kraftfulla filformat.
kanske tänker du ” Åh man, det är ganska svårt att förstå och redigera för hand, en enkel array skulle vara lättare!”Gör inga misstag, applikationer som Poedit är här för att hjälpa – mycket. Du kan få programmet från deras hemsida, det är gratis och tillgängligt för alla plattformar. Det är ett ganska enkelt verktyg att vänja sig vid, och ett mycket kraftfullt samtidigt – med alla funktioner har Gettext tillgängligt. Vi kommer att arbeta här med den senaste versionen, Poedit 1.8.
i den första körningen bör du välja” File > New… ” från menyn. Du kommer att bli ombedd för språket; Välj / Filtrera det språk du vill översätta till, eller använd det format vi nämnde tidigare, till exempel en_US
eller pt_BR
.
spara nu filen-med den katalogstrukturen som vi nämnde också. Då ska du klicka på ”extrahera från källor”, och här konfigurerar du olika inställningar för extraktions-och översättningsuppgifterna. Du kommer att kunna hitta alla dessa senare genom ”Catalog > Properties”:
-
Källvägar: Inkludera alla mappar från projektet där
gettext()
(och syskon) kallas – det här är vanligtvis dina mallar/vyer. Detta är den enda obligatoriska inställningen. -
Översättningsegenskaper:
- projektnamn och version, Team och teamets e-postadress: användbar information som går i .po-filhuvud.
- pluralformer: det här är de regler vi nämnde tidigare. Du kan lämna det med standardalternativet för det mesta, eftersom Poedit redan innehåller en praktisk databas med pluralregler för många språk.
- teckenuppsättningar: UTF-8, företrädesvis.
- Source code charset: charset som används av din kodbas-förmodligen UTF-8 också, eller hur?
-
Källnyckelord: den underliggande programvaran vet hur
gettext()
och liknande funktionssamtal ser ut på flera programmeringsspråk, men du kan lika gärna skapa dina egna översättningsfunktioner. Det kommer att vara här du lägger till de andra metoderna. Detta kommer att diskuteras senare i avsnittet ”Tips”.
efter att ha ställt in dessa egenskaper kör Poedit en skanning genom dina källfiler för att hitta alla lokaliseringssamtal. Efter varje skanning visar Poedit en sammanfattning av vad som hittades och vad som togs bort från källfilerna. Nya poster kommer att vara tomma i översättningstabellen, så att du kan ange de lokaliserade versionerna av dessa strängar. Spara det och en. mo-fil kommer att sammanställas i samma mapp och, presto!, ditt projekt är internationaliserat!
Poedit kan också föreslå vanliga översättningar från webben och från tidigare filer. Det är praktiskt så du behöver bara kontrollera om de är vettiga och acceptera dem. Om du är osäker på en översättning kan du markera den som suddig och den visas i gult. Blå poster är de som inte har någon översättning.
4. Översätta strängar
som du kanske har märkt finns det två huvudtyper av lokaliserade strängar: enkla och de med pluralformer.
enkla har bara två rutor: källa och lokaliserad sträng. Källsträngen kan inte ändras, eftersom Gettext / Poedit inte inkluderar möjligheten att ändra dina källfiler; snarare måste du ändra källan själv och skanna om filerna. (Tips: Om du högerklickar på en översättningsrad visas en ledtråd med källfilerna och raderna där strängen används.)
Pluralformsträngar innehåller två rutor för att visa de två källsträngarna och flikar så att du kan konfigurera de olika slutformerna.
exempel på en sträng med pluralform på Poedit, som visar en översättningsflik för var och en.
när du ändrar dina källkodsfiler och behöver uppdatera översättningarna, tryck bara på Uppdatera och Poedit kommer att skanna om koden, ta bort obefintliga poster, slå samman de som ändrats och lägga till nya.
Poedit kan också försöka gissa några översättningar, baserat på andra som du gjorde. Dessa gissningar och de ändrade posterna kommer att få en ”Fuzzy” markör, vilket indikerar att de behöver granska, visas i gult i listan.
det är också användbart om du har ett översättningsteam och någon försöker skriva något de inte är säkra på: markera bara det Fuzzy och någon annan kommer att granska det senare.
slutligen rekommenderas det att lämna ”Visa > oöversatta poster först” markerade, eftersom det hjälper dig att undvika att glömma några poster. Från den menyn kan du också öppna delar av användargränssnittet som låter dig lämna kontextuell information för översättare om det behövs.
Tips & Tricks
webbservrar kan sluta cacha dina .mo-filer.
om du kör PHP som en modul på Apache (mod_php) kan du möta problem med .mo-filen som cachas. Det händer första gången det läses, och för att uppdatera det kan du behöva starta om servern.
på Nginx och PHP5 tar det vanligtvis bara ett par siduppdateringar för att uppdatera översättningscachen, och på PHP7 behövs det sällan.
bibliotek ger hjälpfunktioner för att hålla lokaliseringskoden kort.
som många föredrar är det lättare att använda _()
istället för gettext()
. Många anpassade i18n-bibliotek från ramverk använder också något som liknar t()
för att göra översatt kod kortare. Det är dock den enda funktionen som sportar en genväg.
du kanske vill lägga till i ditt projekt några andra, till exempel __()
eller _n()
för ngettext()
, eller kanske en snygg _r()
som skulle gå med i gettext()
och sprintf()
samtal. Andra bibliotek, som oscaroteros Gettext, ger också hjälpfunktioner som dessa.
i dessa fall måste du instruera Gettext-verktyget om hur man extraherar strängarna från de nya funktionerna. Var inte rädd, det är väldigt enkelt. Det är bara ett fält i .po-fil eller en inställningsskärm i Poedit (i redigeraren finns det alternativet i ”katalog > egenskaper > källor nyckelord”).
kom ihåg: Gettext känner redan till standardfunktionerna för många språk, så var inte orolig om listan verkar tom. Du måste inkludera specifikationerna för de nya funktionerna i den listan, enligt detta specifika format:
-
om du skapar något som
t()
, som helt enkelt returnerar översättningen för en sträng, kan du ange den somt
. Gettext vet att det enda funktionsargumentet är strängen som ska översättas; -
om funktionen har mer än ett argument kan du ange i vilken den första strängen är och, om det behövs, pluralformen också. Till exempel, om vår funktionssignatur är
__('one user', '%d users', $number)
, skulle specifikationen vara__:1,2
, vilket betyder att den första formen är det första argumentet och den andra formen är det andra argumentet. Om ditt nummer kommer som det första argumentet istället, skulle specifikationen vara__:2,3
, vilket indikerar att den första formen är det andra argumentet, och så vidare.
efter att ha inkluderat de nya reglerna i .po-fil, en ny skanning kommer att ta in dina nya strängar lika enkelt som tidigare.
gör din PHP-App Flerspråkig med Gettext
Gettext är ett mycket kraftfullt verktyg för att internationalisera ditt PHP-projekt. Utöver sin flexibilitet som möjliggör stöd för ett stort antal mänskliga språk, kan dess stöd för mer än 20 programmeringsspråk enkelt överföra dina kunskaper om att använda den med PHP till andra språk som Python, Java eller C#.
Dessutom kan Poedit hjälpa till att jämna vägen mellan kod och översatta strängar, vilket gör processen enklare och lättare att följa. Det kan också effektivisera delade översättningsinsatser med sin Crowdin-integration.
när det är möjligt, överväga andra språk som dina användare kan tala. Detta är mest viktigt för icke-engelska projekt: du kan öka din användaråtkomst om du släpper den på engelska såväl som ditt modersmål.
naturligtvis har inte alla projekt ett behov av internationalisering, men det är mycket lättare att starta i18n under ett projekts barndom, även om det inte ursprungligen behövs, än det är att göra det senare på vägen om det senare skulle bli ett krav. Och med verktyg som Gettext och Poedit är det enklare än någonsin.