Phoenix I18n

În articolele anterioare am abordat diferitele aspecte ale Elixirului - un limbaj de programare modern. Astăzi, totuși, aș dori să mă abat de la limbajul însuși și să discut un cadru foarte rapid și fiabil MVC numit Phoenix care este scris în Elixir.

Acest cadru a apărut cu aproape cinci ani în urmă și a primit o tracțiune de atunci. Desigur, nu este la fel de popular ca și Rails sau Django, dar are un potențial mare și îmi place foarte mult.

În acest articol vom vedea cum să introducem I18n în aplicațiile Phoenix. Ce este i18n, tu intrebi? Ei bine, este un numar care inseamna "internationalizare", intrucat exista exact 18 caractere intre prima litera "i" si ultima "n". Probabil, ați mai întâlnit și o l10n numeronym ceea ce înseamnă "localizare". Dezvoltatorii în aceste zile sunt atât de leneși încât nu pot scrie nici măcar câteva caractere suplimentare, eh?

Internaționalizarea este un proces foarte important, mai ales dacă prevedeți că aplicația este folosită de oameni din întreaga lume. La urma urmei, nu toata lumea stie bine limba engleza, iar traducerea aplicatiei in limba materna a utilizatorului da o impresie buna.

Se pare că procesul de traducere a aplicațiilor Phoenix este oarecum diferit de, de exemplu, traducerea aplicațiilor Rails (dar destul de similar cu același proces din Django). Pentru a traduce aplicațiile Phoenix, folosim o soluție destul de populară, numită Gettext, care a fost deja de peste 25 de ani. Gettext funcționează cu tipuri speciale de fișiere, și anume PO și POT, și acceptă caracteristici precum scoping, pluralizare și alte bunuri. 

Deci, în acest post vă voi explica ce este Gettext, cum PO diferă de POT, cum să localizați mesajele în Phoenix și unde să stocați traducerile. De asemenea, vom vedea cum să schimbați localizarea aplicației și cum să lucrați cu regulile și domeniile de pluralizare.

Putem incepe?

Internaționalizarea cu Gettext

Gettext este un instrument de internaționalizare open-source testat de luptă inițial introdus inițial de Sun Microsystems în 1990. În 1995, GNU a lansat propria versiune de Gettext, care este acum considerată a fi cea mai populară acolo (cea mai recentă versiune a fost 0.19.8 la timpul de scriere a acestui articol). Gettext poate fi folosit pentru a crea sisteme multilingve de orice dimensiune și tip, de la aplicații web la sisteme operaționale. Această soluție este destul de complexă și, bineînțeles, nu vom discuta despre toate caracteristicile acesteia. Documentația completă Gettext poate fi găsită la gnu.org.

Gettext vă oferă toate instrumentele necesare pentru a efectua localizarea și prezintă câteva cerințe privind modul în care fișierele de traducere trebuie denumite și organizate. Două tipuri de fișiere sunt utilizate pentru a găzdui traducerile: PO și MO.

PO (Obiect portabil) fișierele de stocare a fișierelor pentru șirurile date, precum și regulile de pluralizare și metadatele. Aceste fișiere au o structură destul de simplă și pot fi editate cu ușurință de un om, deci în acest articol le vom respecta. Fiecare fișier PO conține traduceri (sau o parte din traduceri) pentru o singură limbă și ar trebui să fie stocate într-un director numit după această limbă: ro, fr, de, etc.

MO (Obiectul mașinii) fișierele conțin date binare care nu sunt menite a fi editate direct de către un om. Ele sunt mai greu de lucrat, iar discutarea acestora este în afara scopului acestui articol.

Pentru a face lucrurile mai complexe, există, de asemenea POT (șablon de obiect portabil) fișiere. Ei găzduiesc doar șiruri de date pentru a traduce, dar nu și traducerile în sine. Practic, fișierele POT sunt folosite doar ca planuri pentru a crea fișiere PO pentru diferite localizări.

Sample Aplicație Phoenix

Bine, acum să începem să practicăm! Dacă doriți să continuați, asigurați-vă că ați instalat următoarele:

  • OTP (versiunea 18 sau o versiune ulterioară)
  • Elixir (1.4+)
  • Cadrul Phoenix (voi folosi versiunea 1.3)

Creați o nouă aplicație de probă fără o bază de date, executând:

mix phx.new i18ndemo --no-ecto

--no-ecto spune că baza de date nu ar trebui să fie utilizată de aplicație (Ecto este un instrument de comunicare cu DB însuși). Rețineți că generatorul ar putea necesita câteva minute pentru a pregăti totul.

Acum folosiți CD pentru a merge la noul creat i18ndemo și executați următoarea comandă pentru a încărca serverul:

mix phx.server

Apoi, deschideți browserul și navigați la http: // localhost: 4000, unde ar trebui să vedeți un "Bine ați venit la Phoenix!" mesaj.

Bună, Gettext!

Ce este interesant despre aplicația noastră Phoenix și, în special, mesajul primit este că Gettext este deja folosit în mod implicit. Mergeți și deschideți demo / lib / demo_web / template-uri / pagina / index.html.eex fișier care acționează ca pagină de pornire implicită. Eliminați totul, cu excepția acestui cod:

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

Acest mesaj primitor utilizează a gettext care acceptă un șir pentru a traduce ca primul argument. Acest șir poate fi considerat a cheie de traducere, deși este oarecum diferită de cheile utilizate în Rails I18n și alte cadre. În Rails am fi folosit o cheie asemănătoare page.welcome, în timp ce șirul tradus este o cheie în sine. Deci, dacă traducerea nu poate fi găsită, putem afișa acest șir direct. Chiar și un utilizator care știe limba engleză prost poate obține cel puțin un sentiment de bază despre ceea ce se întâmplă.

Această abordare este destul de la îndemână, de fapt, oprește-te pentru o secundă și gândește-te la asta. Aveți o aplicație în care toate mesajele sunt în limba engleză. Dacă doriți să o internaționalizați, în cel mai simplu caz tot ce trebuie să faceți este să vă împachetați mesajele cu gettext și să le asigurăm traduceri (mai târziu vom vedea că procesul de extragere a cheilor poate fi ușor automatizat, ceea ce accelerează tot mai mult lucrurile).

Bine, să ne întoarcem la fragmentul nostru de cod mic și să aruncăm o privire asupra celui de-al doilea argument gettext: nume: "Phoenix". Aceasta este așa-numita legare-un parametru înfășurat cu % că dorim să interpolăm în traducerea dată. În acest exemplu, există un singur parametru numit Nume.

De asemenea, putem adăuga un mesaj pe această pagină pentru demonstrații: 

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

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

Adăugarea unei noi traduceri

Acum, că avem două mesaje pe pagina principală, unde ar trebui să adăugăm traduceri pentru ele? Se pare că toate traducerile sunt stocate sub priv / gettext care are o structură predefinită. Să facem un moment pentru a discuta modul în care trebuie organizate fișierele Gettext (aceasta se aplică nu numai Phoenix, ci și oricărei aplicații utilizând Gettext).

Mai întâi de toate, ar trebui să creăm un folder numit după localizarea pe care o vor stoca pentru traduceri. În interior, ar trebui să existe un dosar numit LC_MESSAGES conținând una sau mai multe .po fișierele cu traducerile reale. În cel mai simplu caz, ați avea unul default.po fișier per localizare. Mod implicit aici este numele domeniului (sau domeniul de aplicare). Domeniile sunt utilizate pentru a împărți traducerile în diferite grupuri: de exemplu, ați putea avea nume de domenii admin, WYSIWIG, cart, si altul. Acest lucru este convenabil atunci când aveți o aplicație mare cu sute de mesaje. Cu toate acestea, pentru aplicațiile mai mici, având o singură Mod implicit domeniu este suficient. 

Deci, structura noastră de fișiere ar putea arăta astfel:

  • ro
    • LC_MESSAGES
      • default.po
      • admin.po
  • ru
    • LC_MESSAGES
      • default.po
      • admin.po

Pentru a începe crearea fișierelor PO, avem mai întâi nevoie de șablonul corespunzător (POT). Putem crea manual, dar sunt prea leneș să o fac așa. Să executați în schimb următoarea comandă:

mixte gettext.extract

Este un instrument foarte util care scanează fișierele proiectului și verifică dacă Gettext este folosit oriunde. După ce scenariul își termină treaba, un nou priv / gettext / default.pot fișierul care conține șiruri de translatat va fi creat.

După cum am învățat deja, fișierele POT sunt șabloane, așa că stochează numai cheile, nu traducerile, deci nu modificați manual aceste fișiere. Deschideți un fișier nou creat și aruncați o privire la conținutul acestuia:

Acest fișier este un fișier PO Template. "Aici sunt adesea extrase din codul sursă. # # Adăugați traduceri noi manual numai dacă sunt dinamice ## traduceri care nu pot fi extrase static. ## ## Run 'mix gettext.extract' pentru a aduce acest fișier până la data ##. Lăsați 'msgstr este gol ca schimbându-i aici ca fără efect #: editați-i în loc de fișiere PO (' .po '). "Folosim versiunea% version" #: lib / demo_web / templates / page / index.html.eex: 3 msgid "" #: lib / demo_web / templates / page / index.html "Bine ați venit la% name!" msgstr ""

Convenabil, nu-i așa? Toate mesajele noastre au fost inserate automat și putem vedea cu ușurință exact unde se află. msgid, după cum probabil ați ghicit, este cheia, în timp ce msgstr va conține o traducere.

Următorul pas este, desigur, generarea unui fișier PO. Alerga:

mixați gettext.merge priv / gettext

Acest script va folosi default.pot șablon și creați un default.po fișier în priv / gettext / ro / LC_MESSAGES pliant. Deocamdată, avem doar o limbă engleză, însă în secțiunea următoare se va adăuga și suport pentru o altă limbă.

Apropo, este posibil să creați sau să actualizați șablonul POT și toate fișierele PO dintr-o dată utilizând următoarea comandă:

mixați gettext.extract --merge

Acum, hai să deschidem priv / gettext / ro / LC_MESSAGES / default.po fișier, care are următorul conținut:

## 'msgid În acest fișier provin fișierele POT (.pot). ## ## Nu adăugați, modificați sau eliminați "msgid 's manual aici ca ## sunt legați de cei din fișierul POT corespunzător ## (cu același domeniu). ## ## Utilizați 'mix gettext.extract - Merge' sau 'mix gettext.merge' ## pentru a îmbina fișierele POT în fișiere PO. msgstr "" "Limba: en \ n" #: lib / demo_web / templates / page / index.html.eex: șabloane / pagina / index.html.eex: 2 msgid "Bine ați venit la% name!" msgstr ""

Acesta este dosarul în care ar trebui să efectuăm traducerea reală. Desigur, nu are sens să faceți acest lucru deoarece mesajele sunt deja în limba engleză, așa că trebuie să mergem la secțiunea următoare și să adăugăm sprijin pentru oa doua limbă.

Locații multiple

Firește, locația prestabilită pentru aplicațiile Phoenix este engleza, însă această setare poate fi schimbată cu ușurință prin ajustarea config / config.exs fişier. De exemplu, hai să setăm limba locală implicită în limba rusă (nu ezitați să rămânem în altă limbă la alegere):

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

Este, de asemenea, o idee bună să specificați lista completă a tuturor locațiilor acceptate:

config: demo, I18ndemoWeb.Gettext, default_locale: "ru", localizări: ~ w (en ru)

Acum, ceea ce trebuie să facem este să generăm un nou fișier PO conținând traduceri pentru limba rusă. Se poate face prin rularea gettext.merge script din nou, dar cu a --locale intrerupator:

mixați gettext.merge priv / gettext --locale ru

Evident, a priv / gettext / ru / LC_MESSAGES dosarul cu .po fișierele din interior vor fi generate. Rețineți, apropo, că în afară de default.po fișier, de asemenea, avem errors.po. Acesta este un loc prestabilit pentru traducerea mesajelor de eroare, dar în acest articol o vom ignora.

Acum tweak priv / gettext / ru / LC_MESSAGES / default.po prin adăugarea unor traduceri:

#: lib / demo_web / templates / page / index.html.eex: 3 msgid "Folosim versiunea% version" msgstr "" #: lib / demo_web / templates / page / index.html "Bine ați venit la% name!" msgstr "Utilizarea aplicației% name!"

Acum, în funcție de locația aleasă, Phoenix va face traducerile în engleză sau rusă. Dar tine-te! Cum putem schimba între localizări în aplicația noastră? Să mergem la următoarea secțiune și să aflăm!

Comutarea între locații

Acum, când sunt prezente câteva traduceri, trebuie să permitem utilizatorilor noștri să treacă între localuri. Se pare că există o mufă terță parte pentru cea numită set_locale. Funcționează extragând locația selectată de la adresa URL sau Accept-Language Antet HTTP. Astfel, pentru a specifica o localizare în URL-ul, ați tasta http: // localhost: 4000 / ro / some_path. Dacă locația nu este specificată (sau dacă a fost solicitată o limbă neacceptată), se va întâmpla unul dintre cele două lucruri:

  • Dacă cererea conține o Accept-Language Antetul HTTP și această localizare sunt acceptate, utilizatorul va fi redirecționat către o pagină cu localizarea corespunzătoare.
  • În caz contrar, utilizatorul va fi redirecționat automat la o adresă URL care conține codul localizării implicite.

Deschide  mix.exs fișier și drop in set_locale la dependențele funcţie:

 defp deps do [# ... : set_locale, "~> 0.2.1"] sfârșit

Trebuie, de asemenea, să o adăugăm la cerere funcţie:

 Definiție aplicație [mod: Demo.Application, [], extra_applications: [: logger,: runtime_tools,: set_locale]] sfârșit

Apoi, instalați totul:

mix deps.get

Router-ul nostru se află la lib / demo_web / router.ex necesită și unele modificări. Mai specific, trebuie să adăugăm un nou conector la : browser- conducte:

 conducte: browser-ul nu # ... plug SetLocale, gettext: DemoWeb.Gettext, default_locale: sfârșitul "ru"

De asemenea, creați un nou domeniu de aplicare:

 domeniu "/: locale", DemoWeb face pipe_through: browser get "/", PageController,: index end

Si asta e! Puteți să porniți serverul și să navigați la http: // localhost: 4000 / ru și http: // localhost: 4000 / ro. Rețineți că mesajele sunt traduse corect, ceea ce este exact ceea ce avem nevoie!

Alternativ, vă puteți codifica o caracteristică similară prin folosirea unui fișă Module. Un mic exemplu poate fi găsit în ghidul oficial Phoenix.

Un ultim lucru de menționat este faptul că, în unele cazuri, este posibil să trebuiască să aplicați o anumită localizare. Pentru a face acest lucru, pur și simplu utilizați a with_locale funcţie:

Gettext.with_locale I18ndemoWeb.Gettext, "en", fn -> MyApp.I18ndemoWeb.gettext ("test") sfârșit

pluralizare

Am învățat fundamentele utilizării Gettext cu Phoenix, așa că a venit timpul să discutăm lucruri ceva mai complexe. pluralizare este una dintre ele. Practic, lucrul cu formulare plurală și singulară este o sarcină foarte comună, dar potențial complexă. Lucrurile sunt mai mult sau mai puțin evidente în limba engleză, deoarece aveți "1 mere", "2 mere", "9000 mere" etc (deși "1 boi", "2 boi"!).

Din păcate, în alte limbi, cum ar fi rusă sau poloneză, regulile sunt mai complexe. De exemplu, în cazul merelor, ați spune "1 яблоко", "2 яблока", "9000 яблок". Dar din fericire pentru noi, Phoenix are un a Gettext.Plural comportament (puteți vedea comportamentul în acțiune în unul dintre articolele mele anterioare) care acceptă multe limbi diferite. De aceea tot ce trebuie să facem este să profităm de ngettext funcţie.

Această funcție acceptă trei argumente necesare: un șir în formă singulară, un șir în formă plurală și număr. Al patrulea argument este opțional și poate conține legături care ar trebui interpolate în traducere.

Sa vedem ngettext în acțiune, spunând cât de mulți bani are utilizatorul prin modificarea demo / lib / demo_web / template-uri / pagina / index.html.eex fişier:

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

%numara este o interpolare care va fi înlocuită cu un număr (540 în acest caz). Nu uitați să actualizați șablonul și toate fișierele PO după adăugarea șirului de mai sus:

mixați gettext.extract --merge

Veți vedea că un nou bloc a fost adăugat la ambele default.po fișiere:

msgstr "Aveți% count bucks" msgid_plural "msgstr [1]" "

Nu avem aici nici una, ci două chei: în forme singulare și pluraliste. msgstr [0] va conține un text care să fie afișat atunci când există un singur mesaj. msgstr [1], desigur, conține textul pentru a afișa când există mai multe mesaje. Acest lucru este bine pentru limba engleză, dar nu este suficient pentru limba rusă, unde trebuie să introducem un al treilea caz: 

msgstr "Aveți un buck. Ow :(" msgstr [1] "Aveți% count bucks" msgstr [1] ] "У вас% count долларов"

Caz 0 este folosit pentru 1 buck, și caz 1 pentru zero sau câțiva dolari. Caz 2 este folosit altfel.

Scoping Traduceri cu domenii

Un alt subiect pe care am vrut să-l discut în acest articol este dedicat domenii. După cum știm deja, domeniile sunt folosite pentru a traduce sfere, în principal în aplicații mari. Practic, ei se comportă namespace.

La urma urmei, s-ar putea să ajungeți într-o situație în care aceeași cheie este folosită în mai multe locuri, dar ar trebui tradusă puțin diferit. Sau când aveți prea multe traduceri într-un singur rând default.po fișier și ar dori să le împărțim cumva. Acesta este momentul în care domenii pot veni într-adevăr la îndemână. 

Gettext acceptă mai multe domenii din cutie. Tot ce trebuie să faceți este să utilizați dgettext funcția, care funcționează aproape la fel ca gettext. Singura diferență este că acceptă numele de domeniu ca primul argument. De exemplu, să introducem un domeniu de notificări la afișarea notificărilor. Adăugați încă trei linii de cod la demo / lib / demo_web / template-uri / pagina / index.html.eex fişier:

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

Acum trebuie să creăm noi fișiere POT și PO:

mixați gettext.extract --merge

După terminarea scenariului de a-și face treaba, notifications.pot precum și două notifications.po fișierele vor fi create. Rețineți încă o dată că acestea sunt numite după domeniu. Tot ce trebuie să faceți acum este să adăugați traducerea pentru limba rusă modificând priv / ru / LC_MESSAGES / notifications.po fişier:

msgid "Heads up:% msg" msgstr "Încărcare:% msg"

Dacă doriți să pluralizați un mesaj stocat într-un anumit domeniu? Acest lucru este la fel de simplu ca utilizarea a dngettext funcţie. Funcționează exact așa ngettext dar acceptă și numele unui domeniu ca fiind primul argument:

dgettext "domeniu", "șir singular% msg", "șir plural% msg", 10, msg:

Concluzie

În acest articol, am văzut cum să introducem internaționalizarea într-o aplicație Phoenix cu ajutorul Gettext. Ați învățat ce este Gettext și ce tip de fișiere funcționează. Avem această soluție în acțiune, am lucrat cu fișiere PO și POT și am utilizat diferite funcții Gettext.

De asemenea, am văzut o modalitate de a adăuga suport pentru mai multe locații locale și a adăugat o modalitate de a comuta cu ușurință între ele. În cele din urmă, am văzut cum să aplicăm reguli de pluralizare și cum să traducem sfera de aplicare cu ajutorul domeniilor.

Sperăm că acest articol a fost util pentru dvs.! Dacă doriți să aflați mai multe despre Gettext în cadrul Phoenix, vă puteți referi la ghidul oficial care oferă exemple utile și referință API pentru toate funcțiile disponibile.

Vă mulțumesc că ați rămas cu mine și vă voi vedea în curând!

Cod