Phoenix I18n

I mine tidligere artikler dekket jeg de ulike aspektene av Elixir-et moderne funksjonelt programmeringsspråk. I dag vil jeg imidlertid gå bort fra selve språket og diskutere et veldig raskt og pålitelig MVC-rammeverk som heter Phoenix som er skrevet i Elixir.

Dette rammeprogrammet kom for nesten fem år siden og har fått litt trekkraft siden da. Selvfølgelig er det ikke så populært som Rails eller Django ennå, men det har stort potensial, og jeg liker det veldig godt.

I denne artikkelen skal vi se hvordan jeg introduserer I18n i Phoenix-applikasjoner. Hva er i18n, du spør? Vel, det er et tall som betyr "internasjonalisering", da det er nøyaktig 18 tegn mellom første bokstaven "jeg" og den siste "n". Sannsynligvis har du også møtt en l10n tallonym som betyr "lokalisering". Utviklere i disse dager er så lat at de ikke engang kan skrive et par ekstra figurer, eh?

Internationalisering er en svært viktig prosess, spesielt hvis du forutser at søknaden blir brukt av mennesker fra hele verden. Tross alt, ikke alle vet engelsk godt, og å få appen oversatt til brukerens morsmål gir et godt inntrykk.

Det ser ut til at prosessen med å oversette Phoenix-applikasjoner er noe forskjellig fra, for eksempel, oversette Rails apps (men ganske lik den samme prosessen i Django). For å oversette Phoenix-applikasjoner bruker vi ganske populær løsning som heter Gettext, som har eksistert i mer enn 25 år allerede. Gettext jobber med spesielle typer filer, nemlig PO og POT, og støtter funksjoner som scoping, pluralisering og andre godbiter. 

Så i dette innlegget skal jeg forklare for deg hva Gettext er, hvordan PO skiller seg fra POT, hvordan man lokaliserer meldinger i Phoenix, og hvor man skal lagre oversettelser. Også vi skal se hvordan du kan bytte programmets språk og hvordan du skal arbeide med pluraliseringsregler og domener.

Skal vi starte?

Internasjonalisering med Gettext

Gettext er et kamptestet open source-internasjonaliseringsverktøy som ble introdusert av Sun Microsystems i 1990. I 1995 lanserte GNU sin egen versjon av Gettext, som nå anses å være den mest populære der ute (den nyeste versjonen var 0,19.8 ved tidspunktet for å skrive denne artikkelen). Gettext kan brukes til å lage flerspråklige systemer av alle størrelser og typer, fra webapps til operativsystemer. Denne løsningen er ganske kompleks, og vi skal selvfølgelig ikke diskutere alle dens funksjoner. Den fullstendige Gettext-dokumentasjonen finnes på gnu.org.

Gettext gir deg alle nødvendige verktøy for å utføre lokalisering og presenterer noen krav til hvordan oversettelsesfiler skal navngis og organiseres. To filtyper brukes til vertsoversettelser: PO og MO.

PO (Bærbart objekt) filer lagrer oversettelser for gitt strenge, samt pluraliseringsregler og metadata. Disse filene har ganske enkel struktur og kan lett redigeres av et menneske, så i denne artikkelen vil vi holde fast ved dem. Hver PO-fil inneholder oversettelser (eller deler av oversettelsene) for et enkelt språk og bør lagres i en katalog oppkalt etter dette språket: no, fr, de, etc.

MO (Maskinobjekt) filer inneholder binære data som ikke er ment å bli redigert direkte av et menneske. De er vanskeligere å jobbe med, og diskutere dem er ikke omfattet av denne artikkelen.

For å gjøre tingene mer komplekse, er det også POT (Portable Object Template) filer. De er bare vert for data som skal oversettes, men ikke oversettelsene selv. I utgangspunktet brukes POT-filer bare som blueprints for å lage PO-filer for forskjellige lokaliteter.

Eksempel på Phoenix-søknad

Ok, så nå, la oss fortsette å øve! Hvis du vil følge med, må du sørge for at du har installert følgende:

  • OTP (versjon 18 eller høyere)
  • Eliksir (1.4+)
  • Phoenix rammeverk (jeg skal bruke versjon 1.3)

Opprett et nytt eksempelprogram uten database ved å kjøre:

bland phx.new i18ndemo - no-ecto

--no-ecto sier at databasen ikke skal brukes av appen (Ecto er et verktøy for å kommunisere med DB selv). Merk at generatoren kan kreve et par minutter for å forberede alt.

Bruk nå cd å gå til den nyopprettede i18ndemo mappe og kjør følgende kommando for å starte opp serveren:

bland phx.server

Deretter åpner du nettleseren og navigerer til http: // localhost: 4000, hvor du bør se en "Velkommen til Phoenix!" budskap.

Hei, Gettext!

Hva er interessant om vår Phoenix app, og spesielt den innbydende meldingen er at Gettext allerede brukes som standard. Gå videre og åpne demo / lib / demo_web / templates / page / index.html.eex fil som fungerer som standard startside. Fjern alt unntatt denne koden:

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

Denne innbydende meldingen utnytter en gettext funksjon som aksepterer en streng som skal oversettes som det første argumentet. Denne strengen kan betraktes som en oversettelse nøkkel, selv om det er noe annet enn nøklene som brukes i Rails I18n og noen andre rammer. I Rails ville vi ha brukt en nøkkel som page.welcome, mens her er den oversatte strengen en nøkkel seg selv. Så, hvis oversettelsen ikke kan bli funnet, kan vi vise denne strengen direkte. Selv en bruker som kjenner engelsk dårlig kan få minst en grunnleggende følelse av hva som skjer.

Denne tilnærmingen er ganske praktisk faktisk, stopp for et sekund og tenk på det. Du har en applikasjon der alle meldingene er på engelsk. Hvis du vil internasjonalisere det, er det i det enkleste tilfellet alt du trenger å pakke inn meldingene dine med gettext funksjon og gi oversettelser for dem (senere vil vi se at prosessen med å pakke ut nøklene lett kan automatiseres, noe som øker hastigheten til enda mer).

Ok, la oss gå tilbake til vår lille kodebit og se på det andre argumentet som ble sendt til gettext: navn: "phoenix". Dette er en såkalt bindende-en parameter innpakket med % som vi ønsker å interpolere inn i den gitte oversettelsen. I dette eksemplet er det bare en parameter som heter Navn.

Vi kan også legge til en melding til denne siden for demonstrasjonsformål: 

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

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

Legge til en ny oversettelse

Nå som vi har to meldinger på rotsiden, hvor skal vi legge til oversettelser for dem? Det ser ut til at alle oversettelser lagres under priv / gettext mappe, som har en forhåndsdefinert struktur. La oss ta et øyeblikk for å diskutere hvordan Gettext-filer skal organiseres (dette gjelder ikke bare Phoenix, men til en hvilken som helst app ved hjelp av Gettext).

Først av alt, bør vi opprette en mappe oppkalt etter lokalen den skal lagre oversettelser for. På innsiden bør det være en mappe som heter LC_MESSAGES inneholder en eller flere .po filer med de faktiske oversettelsene. I det enkleste tilfellet vil du ha en default.po fil per lokalitet. misligholde her er domenets (eller omfangets) navn. Domener brukes til å dele oversettelser i ulike grupper: for eksempel kan du ha domener som heter admin, WYSIWIG, kurven, og annen. Dette er praktisk når du har et stort program med hundrevis av meldinger. For mindre apps, men har en såle misligholde domenet er nok. 

Så vår filstruktur kan se slik ut:

  • no
    • LC_MESSAGES
      • default.po
      • admin.po
  • ru
    • LC_MESSAGES
      • default.po
      • admin.po

For å starte opprettelse av PO-filer trenger vi først den tilsvarende malen (POT). Vi kan lage det manuelt, men jeg er for lat til å gjøre det på denne måten. La oss kjøre følgende kommando i stedet:

bland gettext.extract

Det er et veldig praktisk verktøy som skanner prosjektets filer og kontrollerer om Gettext brukes overalt. Etter at skriptet er ferdig med jobben, en ny priv / gettext / default.pot fil som inneholder strenger som skal oversettes, vil bli opprettet.

Som vi allerede har lært, er POT-filer maler, så de lagrer bare tastene selv, ikke oversettelsene, så ikke endre slike filer manuelt. Åpne en nyopprettet fil og se på innholdet:

## Denne filen er en PO-malfil. ## ## 'msgid's her blir ofte hentet fra kildekoden. ## Legg til nye oversettelser manuelt bare hvis de er dynamiske ## oversettelser som ikke kan utdrages statisk. ## ## Run 'mix gettext.extract' for å bringe denne filen opp til ## dato. La 'være tomt som å endre dem her som ingen effekt: rediger dem i PO (' .po ') filer i stedet. msgstr "Vi bruker versjon% version" msgid "" #: lib / demo_web / templates / page / index.html "#: lib / demo_web / templates / page / index.html.eex: .eex: 2 msgid "Velkommen til% name!" msgstr "" "

Praktisk, er det ikke? Alle våre meldinger ble lagt inn automatisk, og vi kan enkelt se nøyaktig hvor de befinner seg. msgid, som du sikkert har gjettet, er nøkkelen, mens msgstr skal inneholde en oversettelse.

Det neste trinnet genererer selvsagt en PO-fil. Løpe:

bland gettext.merge priv / gettext

Dette skriptet skal bruke default.pot mal og opprett en default.po fil i priv / gettext / en / LC_MESSAGES mappe. For nå har vi bare en engelsk lokal, men støtte for et annet språk vil bli lagt til i neste avsnitt også.

Forresten, det er mulig å opprette eller oppdatere POT-malen og alle PO-filer på en gang ved å bruke følgende kommando:

bland gettext.extract --merge

La oss nå åpne priv / gettext / no / LC_MESSAGES / default.po fil, som har følgende innhold:

## 'msgid's i denne filen kommer fra POT (. Pot) filer. ## ## Ikke legg til, endre eller fjern 'msgid manuelt her som ## de er knyttet til de i den tilsvarende POT-filen ## (med samme domene). ## ## Bruk 'mix gettext.extract --merge' eller 'mix gettext.merge' ## for å fusjonere POT-filer til PO-filer. msgstr "" #: lib / demo_web / maler / side / index.html.eex: 2 msgid "Velkommen til% name!" msgstr "" "

Dette er filen der vi skal utføre den faktiske oversettelsen. Selvfølgelig er det lite fornuftig å gjøre det fordi meldingene allerede er på engelsk, så la oss gå videre til neste avsnitt og legge til støtte for et andre språk.

Flere lokaliteter

Naturligvis er standardinnstillingen for Phoenix-programmer engelsk, men denne innstillingen kan enkelt endres ved å justere config / config.exs fil. For eksempel, la oss sette standardlandskapet til russisk (vær så snill å holde fast ved hvilket som helst annet språk du ønsker):

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

Det er også en god idé å angi en fullstendig liste over alle støttede steder:

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

Nå er det vi trenger å generere en ny PO-fil som inneholder oversettelser for den russiske locale. Det kan gjøres ved å kjøre gettext.merge Skript igjen, men med en --locale bytte om:

bland gettext.merge priv / gettext - lokalt ru

Åpenbart, a priv / gettext / ru / LC_MESSAGES mappe med .po filer inni vil bli generert. Merk, forresten, at bortsett fra default.po fil, vi har også errors.po. Dette er et standardsted for å oversette feilmeldinger, men i denne artikkelen skal vi ignorere det.

Nå juster du priv / gettext / ru / LC_MESSAGES / default.po ved å legge til noen oversettelser:

#: lib / demo_web / templates / page / index.html.eex: 3 msgid "Vi bruker versjon% version" msgid "Используется версия% version" #: lib / demo_web / templates / page / index.html .eex: 2 msgid "Velkommen til% name!" msgid "Добро пожаловать в приложение% name!"

Nå, avhengig av den valgte localen, vil Phoenix gjengi enten engelske eller russiske oversettelser. Men hold igjen! Hvordan kan vi faktisk bytte mellom lokaliteter i vår søknad? La oss gå videre til neste avsnitt og finne ut!

Bytte mellom lokaliteter

Nå som noen oversettelser er til stede, må vi gjøre det mulig for brukerne å bytte mellom lokalene. Det ser ut til at det er en tredjepartsplugg for det som heter set_locale. Det fungerer ved å trekke ut den valgte lokalen fra URL eller Accept-Language HTTP-header. Så, for å spesifisere en lokalitet i nettadressen, ville du skrive http: // localhost: 4000 / en / some_path. Hvis lokalene ikke er spesifisert (eller hvis et språk som ikke støttes), vil en av to ting skje:

  • Hvis forespørselen inneholder en Accept-Language HTTP-header og denne lokaliteten støttes, blir brukeren omdirigert til en side med tilhørende språk.
  • Ellers blir brukeren automatisk omdirigert til en nettadresse som inneholder koden til standard locale.

Åpne  mix.exs fil og slipp inn set_locale til deps funksjon:

 defp deps gjør [# ... : set_locale, "~> 0.2.1"] slutten

Vi må også legge den til applikasjon funksjon:

 def søknad gjør [mod: Demo.Application, [], ekstra_applications: [: logger,: runtime_tools,: set_locale]] end

Deretter installerer du alt:

bland deps.get

Vår ruter ligger på lib / demo_web / router.ex krever også noen endringer. Spesielt må vi legge til en ny plugg til :nettleser rørledning:

 pipeline: nettleser gjør # ... plugger SetLocale, gettext: DemoWeb.Gettext, default_locale: "ru" slutten

Lag også et nytt omfang:

 omfang "/: locale", DemoWeb gjør pipe_through: nettleseren få "/", PageController,: index end

Og det er det! Du kan starte opp serveren og navigere til http: // localhost: 4000 / ru og http: // localhost: 4000 / no. Merk at meldingene er oversatt ordentlig, noe som er akkurat det vi trenger!

Alternativt kan du selv kode en lignende funksjon ved å bruke en modulplugg. Et lite eksempel finnes i den offisielle Phoenix-guiden.

En siste ting å nevne er at i noen tilfeller må du kanskje håndheve en bestemt lokalitet. For å gjøre det, bruk bare a with_locale funksjon:

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

pluralization

Vi har lært grunnleggende om å bruke Gettext med Phoenix, så tiden er kommet for å diskutere litt mer komplekse ting. pluralization er en av dem. I utgangspunktet er det å jobbe med flertall og singularformer en svært vanlig, men potensielt kompleks oppgave. Ting er mer eller mindre åpenbare på engelsk som du har "1 eple", "2 epler", "9000 epler" etc (selv om "1 okse", "2 okser"!).

Dessverre, på noen andre språk som russisk eller polsk, er reglene mer komplekse. For eksempel, når det gjelder epler, sier du "1 яблоко", "2 яблока", "9000 яблок". Men heldigvis for oss har Phoenix en Gettext.Plural oppførsel (du kan se atferden i aksjon i en av mine tidligere artikler) som støtter mange forskjellige språk. Derfor er alt vi trenger å gjøre, å dra nytte av ngettext funksjon.

Denne funksjonen aksepterer tre nødvendige argumenter: En streng i singular form, en streng i flertallsform og telle. Det fjerde argumentet er valgfritt og kan inneholde bindinger som skal interpoleres til oversettelsen.

La oss se ngettext i handling ved å si hvor mye penger brukeren har ved å endre demo / lib / demo_web / templates / page / index.html.eex fil:

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

%telle er en interpolasjon som vil bli erstattet med et tall (540 i dette tilfellet). Ikke glem å oppdatere mal og alle PO-filer etter at du har lagt til den ovennevnte strengen:

bland gettext.extract --merge

Du vil se at en ny blokk ble lagt til begge deler default.po filer:

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

Vi har ikke én, men to nøkler her samtidig: i entall og i flertall. msgstr [0] skal inneholde litt tekst som skal vises når det bare er én melding. msgstr [1], Selvfølgelig inneholder teksten som skal vises når det er flere meldinger. Dette er greit for engelsk, men ikke nok for russisk der vi trenger å introdusere en tredje sak: 

msgid "You have one buck. Ow :(" msgid_plural "Du har% count bucks" msgid. [0] "У 1 доллар. Маловато будет!" msgstr [1] "У вас% count доллара" msg ] "У вас% count долларов"

Sak 0 brukes til 1 buck og etui 1 for null eller få dollar. Sak 2 brukes ellers.

Scoping-oversettelser med domener

Et annet tema som jeg ønsket å diskutere i denne artikkelen er viet til domener. Som vi allerede vet, er domener vant til oversettelsesoversettelser, hovedsakelig i store applikasjoner. I utgangspunktet virker de som navnerom.

Tross alt kan du ende opp i en situasjon når samme nøkkel brukes på flere steder, men skal oversettes litt annerledes. Eller når du har alt for mange oversettelser i en enkelt default.po fil og vil dele dem på en eller annen måte. Det er da domene kan komme inn veldig praktisk. 

Gettext støtter flere domener ut av esken. Alt du trenger å gjøre er å bruke dgettext funksjon, som fungerer nesten det samme som gettext. Den eneste forskjellen er at den aksepterer domenenavnet som det første argumentet. For eksempel, la oss introdusere et varslingsdomene til, vel, vise varsler. Legg til tre flere linjer med kode til demo / lib / demo_web / templates / page / index.html.eex fil:

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

Nå må vi lage nye POT- og PO-filer:

bland gettext.extract --merge

Etter at skriptet er ferdig med å gjøre jobben sin, notifications.pot så vel som to notifications.po filer vil bli opprettet. Merk igjen at de er oppkalt etter domenet. Alt du trenger å gjøre nå er å legge til oversettelsen for det russiske språket ved å endre priv / ru / LC_MESSAGES / notifications.po fil:

msgid "Heads up:% msg" msgstr "Konfigurasjon:% msg"

Hva om du vil pluralisere en melding lagret under et gitt domene? Dette er like enkelt som å utnytte en dngettext funksjon. Det fungerer akkurat som ngettext men aksepterer også et domenes navn som det første argumentet:

dgettext "domenet", "Singular string% msg", "Flertalsstreng% msg", 10, msg: "demo"

Konklusjon

I denne artikkelen har vi sett hvordan å introdusere internasjonalisering i en Phoenix-applikasjon ved hjelp av Gettext. Du har lært hva Gettext er og hvilken type filer det fungerer med. Vi har denne løsningen i bruk, har jobbet med PO- og POT-filer, og benyttet ulike Gettext-funksjoner.

Vi har også sett en måte å legge til støtte for flere lokaliteter, og lagt til en måte å enkelt bytte mellom dem. Til slutt har vi sett hvordan man bruker pluraliseringsregler og hvordan man kan oversette oversettelser ved hjelp av domener.

Forhåpentligvis var denne artikkelen nyttig for deg! Hvis du vil lære mer om Gettext i Phoenix-rammen, kan du referere til den offisielle guiden, som gir nyttige eksempler og API-referanser for alle tilgjengelige funksjoner.

Jeg takker for at du bodde hos meg og ser deg snart!