Phoenix I18n

I mina tidigare artiklar behandlade jag de olika aspekterna av Elixir-ett modernt funktionellt programmeringsspråk. Idag vill jag dock gå bort från själva språket och diskutera en mycket snabb och pålitlig MVC-ram kallad Phoenix som skrivs i Elixir.

Denna ram framkom för nästan fem år sedan och har sedan fått en del dragkraft. Naturligtvis är det inte så populärt som Rails eller Django än, men det har stor potential och jag gillar det verkligen.

I den här artikeln kommer vi att se hur man introducerar I18n i Phoenix-applikationer. Vad är i18n, du frågar? Tja, det är en siffra som betyder "internationalisering", eftersom det finns exakt 18 tecken mellan första bokstaven "i" och den sista "n". Förmodligen har du också träffat en l10n numerikon som betyder "lokalisering". Utvecklare dessa dagar är så lat att de inte ens kan skriva ett par extra tecken, eh?

Internationalisering är en mycket viktig process, särskilt om du förutser att ansökan används av människor från hela världen. När allt kommer omkring vet inte alla engelska engelska, och att få appen översatt till användarens modersmål ger ett bra intryck.

Det verkar som om processen med att översätta Phoenix-applikationer är något annorlunda än att säga, översätta Rails apps (men ganska lik samma process i Django). För att översätta Phoenix-applikationer använder vi ganska populär lösning som heter Gettext, som har funnits i mer än 25 år. Gettext arbetar med speciella typer av filer, nämligen PO och POT, och stöder funktioner som scoping, pluralisering och andra godsaker. 

Så i det här inlägget kommer jag att förklara för dig vad Gettext är, hur PO skiljer sig från POT, hur man lokaliserar meddelanden i Phoenix och var att lagra översättningar. Vi kommer också att se hur man byter programmets språk och hur man arbetar med pluraliseringsregler och domäner.

Ska vi börja?

Internationalisering med Gettext

Gettext är ett kamptestat open-source internationaliseringsverktyg som introducerades av Sun Microsystems 1990. 1995 släppte GNU sin egen version av Gettext, som nu anses vara den mest populära där ute (den senaste versionen var 0,19.8 på tidpunkten för att skriva denna artikel). Gettext kan användas för att skapa flerspråkiga system av alla storlekar och typer, från webbapps till operativsystem. Denna lösning är ganska komplex, och vi kommer förstås inte att diskutera alla dess funktioner. Den fullständiga Gettext-dokumentationen finns på gnu.org.

Gettext ger dig alla nödvändiga verktyg för att utföra lokalisering och presenterar några krav på hur översättningsfiler ska namnges och organiseras. Två filtyper används för att verta översättningar: PO och MO.

PO (Bärbart objekt) filer lagrar översättningar för givna strängar samt pluraliseringsregler och metadata. Dessa filer har en ganska enkel struktur och kan enkelt redigeras av en människa, så i den här artikeln kommer vi att hålla fast vid dem. Varje PO-fil innehåller översättningar (eller delar av översättningarna) för ett enda språk och bör lagras i en katalog som heter detta språk: sv, fr, de, etc.

MO (Maskinobjekt) filer innehåller binär data som inte är avsedda att redigeras direkt av en människa. De är svårare att arbeta med, och diskutera dem ligger utanför ramen för denna artikel.

För att göra sakerna mer komplexa finns det också POT (portabel objektmall) filer. De är endast värd för strängar av data att översätta, men inte översättningarna själva. I grund och botten används POT-filer bara som ritningar för att skapa PO-filer för olika platser.

Prov Phoenix applikation

Okej, så nu ska vi fortsätta att träna! Om du vill följa med, se till att du har installerat följande:

  • OTP (version 18 eller högre)
  • Elixir (1.4+)
  • Phoenix ramverk (jag ska använda version 1.3)

Skapa en ny provprogram utan en databas genom att köra:

blanda phx.new i18ndemo - no-ecto

--no-ecto säger att databasen inte ska användas av appen (Ecto är ett verktyg för att kommunicera med DB själv). Observera att generatorn kan behöva några minuter för att förbereda allt.

Använd nu CD att gå till den nyskapade i18ndemo mapp och kör följande kommando för att starta servern:

blanda phx.server

Öppna sedan webbläsaren och navigera till http: // localhost: 4000, där du borde se en "Välkommen till Phoenix!" meddelande.

Hej, Gettext!

Vad är intressant om vår Phoenix app och speciellt det välkomna meddelandet är att Gettext redan används som standard. Gå vidare och öppna demo / lib / demo_web / templates / sida / index.html.eex fil som fungerar som standard startsida. Ta bort allt utom denna kod:

<%= gettext "Welcome to %name!", name: "Phoenix" %>

Detta välkomna meddelande använder en gettext funktion som accepterar en sträng att översätta som första argumentet. Denna sträng kan betraktas som en översättningstangent, även om det är något annorlunda än nycklarna som används i Rails I18n och några andra ramar. I Rails skulle vi ha använt en nyckel som page.welcome, medan den översatta strängen här är en nyckel sig. Så, om översättningen inte kan hittas, kan vi direkt visa denna sträng. Även en användare som känner engelska dåligt kan få minst en grundläggande känsla av vad som händer.

Detta tillvägagångssätt är ganska praktiskt, sluta en sekund och tänka på det. Du har en ansökan där alla meddelanden är på engelska. Om du skulle vilja internationalisera det, är det enklaste att du bara sätter in dina meddelanden med gettext funktionen och tillhandahålla översättningar till dem (senare kommer vi att se att processen att extrahera nycklarna enkelt kan automatiseras, vilket snabbar upp sakerna ännu mer).

Okej, låt oss återvända till vårt lilla kodbitar och titta på det andra argumentet som skickats till gettext: namn: "phoenix". Detta är en så kallad bindning-en parameter inbäddad med % som vi skulle vilja interpolera till den givna översättningen. I det här exemplet är det bara en parameter som heter namn.

Vi kan också lägga till ett nytt meddelande till den här sidan för demonstrationsändamål: 

<%= gettext "Welcome to %name!", name: "Phoenix" %>

<%= gettext "We are using version %version", version: "1.3" %>

Lägger till en ny översättning

Nu när vi har två meddelanden på rotsidan, var ska vi lägga till översättningar för dem? Det verkar som om alla översättningar lagras under Priv / gettext mapp som har en fördefinierad struktur. Låt oss ta en stund för att diskutera hur Gettext-filer ska organiseras (detta gäller inte bara Phoenix men till någon app med Gettext).

Först och främst bör vi skapa en mapp som heter efter det land där det kommer att lagra översättningar för. Inuti borde det finnas en mapp som heter LC_MESSAGES innehållande en eller flera .po filer med de faktiska översättningarna. I det enklaste fallet skulle du ha en default.po fil per lokal. standard här är domänens (eller omfattningens) namn. Domäner används för att dela översättningar till olika grupper: till exempel kan du ha domäner som heter administration, WYSIWYG, vagn, och andra. Det här är praktiskt när du har en stor applikation med hundratals meddelanden. För mindre appar har dock en tunga standard domän är tillräckligt. 

Så vår filstruktur kan se ut så här:

  • sv
    • LC_MESSAGES
      • default.po
      • admin.po
  • ru
    • LC_MESSAGES
      • default.po
      • admin.po

För att börja skapa PO-filer behöver vi först motsvarande mall (POT). Vi kan skapa det manuellt, men jag är för lat för att göra det på så sätt. Låt oss springa följande kommando istället:

blanda gettext.extract

Det är ett mycket användbart verktyg som skannar projektets filer och kontrollerar om Gettext används överallt. När manuset avslutar sitt jobb, ett nytt Priv / gettext / default.pot fil som innehåller strängar att översätta kommer att skapas.

Som vi redan har lärt oss, är POT-filer mallar, så de lagrar bara nycklarna själva, inte översättningarna, så modifiera inte sådana filer manuellt. Öppna en nyskapad fil och titta på innehållet:

## Den här filen är en PO-mallfil. ## ## 'msgid's här extraheras ofta från källkod. ## Lägg till nya översättningar manuellt endast om de är dynamiska ## översättningar som inte kan extraheras statiskt. ## ## Kör "mix gettext.extract" för att hämta filen till ## date. Lämna 'tomt' som att ändra dem här som ingen effekt: redigera dem i PO ('.po') -filer istället. msgstr "Vi använder version% version" msgid "" #: lib / demo_web / templates / page / index.html "msgid" "msgstr" "" #: lib / demo_web / templates / page / index.html.eex: .eex: 2 msgid "Välkommen till% name!" msgstr "" "

Bekvämt är det inte? Alla våra meddelanden infogades automatiskt, och vi kan enkelt se exakt var de befinner sig. msgid, som du antagligen har gissat, är nyckeln, medan msgstr kommer att innehålla en översättning.

Nästa steg skapar förstås en PO-fil. Springa:

blanda gettext.merge priv / gettext

Detta skript kommer att utnyttja default.pot mall och skapa en default.po fil i Priv / gettext / sv / LC_MESSAGES mapp. För närvarande har vi bara en engelsk språkversion, men stöd för ett annat språk kommer också att läggas till i nästa avsnitt.

Förresten är det möjligt att skapa eller uppdatera POT-mallen och alla PO-filer på en gång genom att använda följande kommando:

blanda gettext.extract - merge

Låt oss nu öppna Priv / gettext / sv / LC_MESSAGES / default.po fil som har följande innehåll:

## 'msgid i den här filen kommer från POT (.pot) -filer. ## ## Lägg inte till, ändra eller ta bort "msgid manuellt här som ## de är bundna till de i motsvarande POT-fil ## (med samma domän). ## ## Använd 'mix gettext.extract --merge' eller 'mix gettext.merge' ## för att slå samman POT-filer i PO-filer. msgstr "Vi använder version% version" msgid "" #: lib / demo_web / mallar / sida / index.html.eex: 2 msgid "Välkommen till% name!" msgstr "" "

Det här är filen där vi ska utföra den faktiska översättningen. Det är självklart inte så litet att göra det eftersom meddelandena redan finns på engelska, så låt oss gå vidare till nästa avsnitt och lägga till stöd för ett andra språk.

Flera lokaliteter

Naturligtvis är standardplatsen för Phoenix-applikationer engelska, men denna inställning kan enkelt ändras genom att tweakera config / config.exs fil. Låt oss till exempel ställa in standardlandskapet till ryska (gärna hänga med något annat språk som du väljer):

config: demo, I18ndemoWeb.Gettext, default_locale: "ru"

Det är också en bra idé att ange en fullständig lista över alla stödda platser:

config: demo, I18ndemoWeb.Gettext, default_locale: "ru", locales: ~w (en ru)

Nu måste vi skapa en ny PO-fil som innehåller översättningar för den ryska localen. Det kan göras genom att springa gettext.merge manus igen, men med a --locale växla:

blanda gettext.merge priv / gettext - lokalt ru

Självfallet a Priv / gettext / ru / LC_MESSAGES mapp med .po filer inuti kommer att genereras. Observera förresten att det förutom default.po fil, vi har också errors.po. Det här är en standardplats för att översätta felmeddelanden, men i den här artikeln kommer vi att ignorera det.

Justera nu Priv / gettext / ru / LC_MESSAGES / default.po genom att lägga till några översättningar:

#: lib / demo_web / templates / page / index.html.eex: 3 msgid "Vi använder version% version" msgid "Используется версия% version" #: lib / demo_web / mallar / sida / index.html .eex: 2 msgid "Välkommen till% name!" msgid "Добро пожаловать в приложение% name!"

Nu, beroende på den valda platsen, kommer Phoenix att göra antingen engelska eller ryska översättningar. Men håll fast! Hur kan vi faktiskt växla mellan lokaliteter i vår ansökan? Låt oss gå vidare till nästa avsnitt och ta reda på det!

Växling mellan platser

Nu när vissa översättningar är närvarande måste vi göra det möjligt för våra användare att växla mellan lokala. Det verkar som om det finns en tredjepartsplugg för den som heter set_locale. Det fungerar genom att extrahera den valda platsen från webbadressen eller Accept-Language HTTP-rubrik. Så, för att ange en lokal i webbadressen, skulle du skriva http: // localhost: 4000 / sv / some_path. Om läget inte anges (eller om ett språk som inte stöds) begärs, kommer en av två saker att hända:

  • Om förfrågan innehåller en Accept-Language HTTP-header och den här platsen stöds, kommer användaren att omdirigeras till en sida med motsvarande språk.
  • Annars blir användaren omdirigerad automatiskt till en URL som innehåller koden för standardinställningen.

Öppna  mix.exs fil och släpp in set_locale till deps fungera:

 defp deps gör [# ... : set_locale, "~> 0.2.1"] slutet

Vi måste också lägga till den i Ansökan fungera:

 def ansökan gör [mod: Demo.Application, [], extra_applications: [: logger,: runtime_tools,: set_locale]] än

Installera sedan allt:

blanda deps.get

Vår router ligger vid lib / demo_web / router.ex kräver också vissa ändringar. Specifikt måste vi lägga till en ny plugga till : browser rörledning:

 pipeline: webbläsare gör # ... plug SetLocale, gettext: DemoWeb.Gettext, default_locale: "ru" slutet

Skapa också ett nytt omfång:

 scope "/: locale", DemoWeb gör pipe_through: webbläsaren får "/", PageController,: index end

Och det är allt! Du kan starta servern och navigera till http: // localhost: 4000 / ru och http: // localhost: 4000 / sv. Observera att meddelandena översätts ordentligt, vilket är exakt vad vi behöver!

Alternativt kan du själv koda en liknande funktion genom att använda en modulplugg. Ett litet exempel finns i den officiella Phoenix-guiden.

En sista sak att nämna är att i vissa fall kanske du behöver genomdriva en viss ort. För att göra det, använd helt enkelt a with_locale fungera:

Gettext.with_locale I18ndemoWeb.Gettext, "en", fn -> MyApp.I18ndemoWeb.gettext ("test") slutet

pluralise

Vi har lärt oss grunden att använda Gettext med Phoenix, så det är dags att diskutera lite mer komplexa saker. pluralise är en av dem. I grund och botten är arbetet med plural och singular former en mycket vanlig men potentiellt komplex uppgift. Sakerna är mer eller mindre uppenbara på engelska som du har "1 äpple", "2 äpplen", "9000 äpplen" etc (även om "1 ox", "2 oxer"!).

Tyvärr, på några andra språk som ryska eller polska är reglerna mer komplexa. Till exempel, när det gäller äpplen, skulle du säga "1 яблоко", "2 яблока", "9000 яблок". Men lyckligtvis för oss har Phoenix en Gettext.Plural beteende (du kan se beteendet i åtgärd i en av mina tidigare artiklar) som stöder många olika språk. Därför allt vi behöver göra är att dra nytta av ngettext fungera.

Denna funktion accepterar tre nödvändiga argument: en sträng i singular form, en sträng i pluralform och räkning. Det fjärde argumentet är valfritt och kan innehålla bindningar som ska interpoleras i översättningen.

Låt oss se ngettext i aktion genom att säga hur mycket pengar användaren har genom att ändra demo / lib / demo_web / templates / sida / index.html.eex fil:

<%= ngettext "You have one buck. Ow :(", "You have %count bucks", 540 %>

%räkna är en interpolation som kommer att ersättas med ett tal (540 I detta fall). Glöm inte att uppdatera mallen och alla PO-filer efter att du har lagt till ovanstående sträng:

blanda gettext.extract - merge

Du ser att ett nytt block har lagts till båda default.po filer:

msgstr "" "Du har en bok. Ow :(" msgid_plural "Du har% count bucks" msgid "0" "" msg [1] ""

Vi har inte en utan två nycklar här på en gång: i singular och i pluralformer. msgstr [0] kommer att innehålla lite text som ska visas när det bara finns ett meddelande. msgstr [1], naturligtvis innehåller texten som ska visas när det finns flera meddelanden. Det här är okej för engelska, men inte tillräckligt för ryska där vi måste introducera ett tredje fall: 

msgid "You have one buck. Ow :(" msgid_plural "Du har% count bucks" msgid "0%" 1. ] "У вас% count долларов"

Fall 0 används för 1 buck och case 1 för noll eller få dollar. Fall 2 används annars.

Scoping-översättningar med domäner

Ett annat ämne som jag ville diskutera i denna artikel ägnas åt domäner. Som vi redan vet är domäner vana vid översättningsomfattningar, främst i stora tillämpningar. I grund och botten fungerar de som namnrymder.

När allt kommer omkring kan du hamna i en situation när samma nyckel används på flera ställen, men bör översättas lite annorlunda. Eller när du har alltför många översättningar i en enda default.po fil och skulle vilja dela dem på något sätt. Det är då domäner kan komma in riktigt praktiskt. 

Gettext stöder flera domäner ur lådan. Allt du behöver göra är att använda dgettext funktion, som fungerar nästan lika som gettext. Den enda skillnaden är att den accepterar domännamnet som det första argumentet. Till exempel, låt oss introducera en anmälningsdomän för att visa meddelanden. Lägg till tre rader av kod till demo / lib / demo_web / templates / sida / index.html.eex fil:

<%= dgettext "notifications", "Heads up: %msg", msg: "something has happened!" %>

Nu behöver vi skapa nya POT- och PO-filer:

blanda gettext.extract - merge

När manuset avslutar jobbet, notifications.pot såväl som två notifications.po filer kommer att skapas. Observera än en gång att de är uppkallade efter domänen. Allt du behöver göra nu är att lägga till översättning för det ryska språket genom att ändra Priv / ru / LC_MESSAGES / notifications.po fil:

msgid "Heads up:% msg" msgstr "Föreställning:% msg"

Vad händer om du vill pluralisera ett meddelande som lagras under en viss domän? Detta är lika enkelt som att använda en dngettext fungera. Det fungerar precis som ngettext men accepterar också en domännamn som det första argumentet:

dgettext "domain", "Singular string% msg", "Plural string% msg", 10, msg: "demo"

Slutsats

I den här artikeln har vi sett hur man introducerar internationalisering i en Phoenix-applikation med hjälp av Gettext. Du har lärt dig vad Gettext är och vilken typ av filer den fungerar med. Vi har den här lösningen i funktion, har arbetat med PO- och POT-filer och använt olika Gettext-funktioner.

Vi har också sett ett sätt att lägga till stöd för flera lokala platser och lagt till ett sätt att enkelt växla mellan dem. Slutligen har vi sett hur man använder pluraliseringsregler och hur man använder översättningsomfattningar med hjälp av domäner.

Förhoppningsvis var den här artikeln till nytta för dig! Om du vill lära dig mer om Gettext i Phoenix-ramen, kan du referera till den officiella guiden, som innehåller användbara exempel och API-referens för alla tillgängliga funktioner.

Jag tackar dig för att du bodde hos mig och ses snart!