Phoenix I18n

In mijn vorige artikelen behandelde ik de verschillende aspecten van Elixir - een moderne functionele programmeertaal. Vandaag wil ik echter een stapje verder gaan met de taal zelf en een zeer snel en betrouwbaar MVC-raamwerk bespreken, Phoenix genaamd, dat is geschreven in Elixir.

Dit raamwerk is bijna vijf jaar geleden ontstaan ​​en heeft sindsdien enige tractie gekregen. Het is natuurlijk nog niet zo populair als Rails of Django, maar het heeft een groot potentieel en ik vind het echt leuk.

In dit artikel gaan we kijken hoe I18n in Phoenix-toepassingen kan worden geïntroduceerd. Wat is i18n, je vraagt? Welnu, het is een numeroniem dat "internationalisering" betekent, want er zijn precies 18 tekens tussen de eerste letter "i" en de laatste "n". Waarschijnlijk heb je ook een ontmoet l10n numeroniem wat "lokalisatie" betekent. Ontwikkelaars zijn tegenwoordig zo lui dat ze niet eens een paar extra karakters kunnen schrijven, hè?

Internationalisering is een zeer belangrijk proces, vooral als u verwacht dat de applicatie door mensen van over de hele wereld wordt gebruikt. Immers, niet iedereen kent Engels goed, en het laten vertalen van de app in de moedertaal van een gebruiker geeft een goede indruk.

Het lijkt erop dat het proces van het vertalen van Phoenix-applicaties enigszins verschilt van, bijvoorbeeld, het vertalen van Rails-apps (maar vrij gelijkaardig aan hetzelfde proces in Django). Om Phoenix-toepassingen te vertalen, gebruiken we een vrij populaire oplossing genaamd Gettext, die al meer dan 25 jaar bestaat. Gettext werkt met speciale soorten bestanden, namelijk PO en POT, en ondersteunt functies zoals scoping, pluralisatie en andere goodies. 

Dus in deze post ga ik je uitleggen wat Gettext is, hoe PO verschilt van POT, hoe je berichten in Phoenix kunt lokaliseren en waar je vertalingen kunt opslaan. We gaan ook kijken hoe we de locale van de applicatie kunnen veranderen en hoe we met pluralisatieregels en -domeinen kunnen werken.

Zullen we beginnen?

Internationalisering met Gettext

Gettext is een door de strijd getest open-source internationalisatietool dat oorspronkelijk door Sun Microsystems in 1990 werd geïntroduceerd. In 1995 bracht GNU zijn eigen versie van Gettext uit, die nu als de meest populaire wordt beschouwd (de nieuwste versie was 0.19.8 op de tijd van schrijven van dit artikel). Gettext kan worden gebruikt om meertalige systemen van elk formaat en type te maken, van webapps tot operationele systemen. Deze oplossing is vrij complex en we zullen natuurlijk niet alle functies bespreken. De volledige Gettext-documentatie is te vinden op gnu.org.

Gettext biedt u alle benodigde hulpmiddelen om lokalisatie uit te voeren en stelt enkele eisen aan de vraag hoe vertaalbestanden moeten worden benoemd en georganiseerd. Er worden twee bestandstypen gebruikt om vertalingen te hosten: PO en MO.

PO (Draagbaar object) bestanden opslaan vertalingen voor gegeven strings evenals pluralisatieregels en metadata. Deze bestanden hebben een vrij eenvoudige structuur en kunnen eenvoudig door een persoon worden bewerkt, dus in dit artikel zullen we ons aan hen houden. Elk PO-bestand bevat vertalingen (of een deel van de vertalingen) voor een enkele taal en moet worden opgeslagen in een map met de naam van deze taal: nl, fr, de, enz.

MO (Machine Object) bestanden bevatten binaire gegevens die niet bedoeld zijn om rechtstreeks door een mens te worden bewerkt. Ze zijn moeilijker om mee te werken en bespreken ervan valt buiten het bestek van dit artikel.

Om dingen complexer te maken, zijn er ook POT (sjabloon voor draagbare objecten) bestanden. Ze hosten alleen een reeks gegevens om te vertalen, maar niet de vertalingen zelf. In principe worden POT-bestanden alleen als blauwdrukken gebruikt om PO-bestanden voor verschillende landinstellingen te maken.

Voorbeeld Phoenix-toepassing

Oké, dus laten we nu doorgaan met oefenen! Als je wilt meegaan, zorg dan dat je het volgende hebt geïnstalleerd:

  • OTP (versie 18 of hoger)
  • Elixir (1.4+)
  • Phoenix-raamwerk (ik ga versie 1.3 gebruiken)

Maak een nieuwe voorbeeldtoepassing zonder een database door te draaien:

mix phx.new i18ndemo --no-ecto

--no-ecto zegt dat de database niet door de app moet worden gebruikt (Ecto is een hulpmiddel om met de DB zelf te communiceren). Merk op dat de generator mogelijk een paar minuten nodig heeft om alles voor te bereiden.

Gebruik nu CD om naar de nieuw gecreëerde te gaan i18ndemo map en voer de volgende opdracht uit om de server op te starten:

mix phx.server

Open vervolgens de browser en ga naar http: // localhost: 4000, waar je een "Welcome to Phoenix!" bericht.

Hallo, Gettext!

Wat interessant is aan onze Phoenix-app en, in het bijzonder, de welkomstboodschap is dat Gettext al standaard wordt gebruikt. Ga je gang en open de demo / lib / demo_web / templates / page / index.html.eex bestand dat fungeert als standaard startpagina. Verwijder alles behalve deze code:

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

Deze welkomstboodschap maakt gebruik van a gettext functie die een tekenreeks accepteert om te vertalen als het eerste argument. Deze reeks kan worden beschouwd als een vertaalsleutel, hoewel het enigszins verschilt van de toetsen die worden gebruikt in Rails I18n en enkele andere frameworks. In Rails zouden we een sleutel zoals hebben gebruikt page.welcome, terwijl hier de vertaalde reeks een sleutel is zelf. Dus als de vertaling niet kan worden gevonden, kunnen we deze reeks direct weergeven. Zelfs een gebruiker die slecht Engels kent, kan op zijn minst een idee krijgen van wat er gaande is.

Deze aanpak is eigenlijk best handig - stop even en denk erover na. Je hebt een applicatie waarbij alle berichten in het Engels zijn. Als u het wilt internationaliseren, hoeft u in het eenvoudigste geval uw berichten in te pakken met de gettext functioneer en voorzie vertalingen ervan (later zullen we zien dat het proces van het uitpakken van de sleutels eenvoudig kan worden geautomatiseerd, wat de zaken nog meer versnelt).

Oké, laten we terugkeren naar ons kleine codefragment en een kijkje nemen naar het tweede argument dat is doorgegeven gettext: naam: "Phoenix". Dit is een zogenaamde verbindend-een parameter omwikkeld met % die we willen interpoleren in de gegeven vertaling. In dit voorbeeld is er slechts één parameter genaamd naam.

We kunnen ook nog een bericht aan deze pagina toevoegen voor demonstratiedoeleinden: 

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

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

Een nieuwe vertaling toevoegen

Nu we twee berichten op de hoofdpagina hebben staan, waar moeten we dan vertalingen voor toevoegen? Het lijkt erop dat alle vertalingen zijn opgeslagen onder de priv / gettext map, die een vooraf gedefinieerde structuur heeft. Laten we een moment nemen om te bespreken hoe Gettext-bestanden moeten worden georganiseerd (dit geldt niet alleen voor Phoenix maar voor elke app met Gettext).

Allereerst moeten we een map maken met de naam van de locale waarvoor het vertalingen gaat opslaan. Binnenin zou er een map moeten zijn LC_MESSAGES een of meerdere bevatten .po bestanden met de eigenlijke vertalingen. In het eenvoudigste geval zou je er een hebben default.po bestand per landinstelling. standaard hier is de naam van het domein (of de scope). Domeinen worden gebruikt om vertalingen in verschillende groepen op te splitsen: u kunt bijvoorbeeld domeinen met de naam krijgen beheerder, WYSIWIG, kar, en andere. Dit is handig als u een grote applicatie met honderden berichten heeft. Voor kleinere apps echter met een sole standaard domein is voldoende. 

Onze bestandsstructuur ziet er dus als volgt uit:

  • nl
    • LC_MESSAGES
      • default.po
      • admin.po
  • ru
    • LC_MESSAGES
      • default.po
      • admin.po

Om te beginnen met het maken van PO-bestanden, hebben we eerst de bijbehorende sjabloon (POT) nodig. We kunnen het handmatig maken, maar ik ben te lui om het op deze manier te doen. Laten we in plaats daarvan het volgende commando uitvoeren:

mix gettext.extract

Het is een erg handige tool die de projectbestanden scant en controleert of Gettext overal wordt gebruikt. Nadat het script zijn taak heeft voltooid, een nieuw priv / gettext / default.pot bestand met strings om te vertalen zal worden gemaakt.

Zoals we al hebben geleerd, zijn POT-bestanden sjablonen, dus ze slaan alleen de sleutels zelf op, niet de vertalingen, dus wijzig dergelijke bestanden niet handmatig. Open een nieuw bestand en bekijk de inhoud ervan:

## Dit bestand is een PO-sjabloonbestand. ## ## 'msgid hier worden vaak geëxtraheerd uit de broncode. ## Voeg alleen handmatig nieuwe vertalingen toe als het dynamische ##-vertalingen zijn die niet statisch kunnen worden geëxtraheerd. ## ## Voer 'mix gettext.extract' uit om dit bestand naar de ## datum te brengen. Laat 'msgstr's leeg als ze hier veranderen als geen ## effect: bewerk ze in PO (' .po ') bestanden in plaats daarvan. msgstr "" msgstr "We gebruiken versie% versie" msgstr "" #: lib / demo_web / templates / page / index.html .eex: 2 msgstr "Welkom bij% name!" msgstr ""

Handig, toch? Al onze berichten werden automatisch ingevoegd en we kunnen eenvoudig zien waar ze zich precies bevinden. msgid, zoals je waarschijnlijk wel hebt geraden, is de sleutel, terwijl msgstr zal een vertaling bevatten.

De volgende stap is natuurlijk het genereren van een PO-bestand. Rennen:

mix gettext.merge priv / gettext

Dit script gaat gebruik maken van de default.pot sjabloon en maak een default.po bestand in de priv / gettext / nl / LC_MESSAGES map. Voorlopig hebben we alleen een Engelse locale, maar ondersteuning voor een andere taal zal ook in de volgende sectie worden toegevoegd.

Overigens is het mogelijk om de POT-sjabloon en alle PO-bestanden in één keer te maken of bij te werken met behulp van de volgende opdracht:

mix gettext.extract --merge

Laten we nu de. Openen priv / gettext / nl / LC_MESSAGES / default.po bestand, dat de volgende inhoud heeft:

## 'msgid's in dit bestand komen uit POT (.pot) bestanden. ## ## Voeg hier geen msgid's toe, wijzig of verwijder ze hier niet als ## ze zijn gekoppeld aan die in het corresponderende POT-bestand ## (met hetzelfde domein). ## ## Gebruik 'mix gettext.extract --merge' of 'mix gettext.merge' ## om POT-bestanden in PO-bestanden samen te voegen. msgstr "" msgstr "" "Taal: en \ n" #: lib / demo_web / templates / pagina / index.html.eex: 3 msgstr "We gebruiken versie% versie" msgstr "" #: lib / demo_web / templates / page / index.html.eex: 2 msgid "Welkom bij% name!" msgstr ""

Dit is het bestand waar we de eigenlijke vertaling moeten uitvoeren. Het heeft natuurlijk weinig zin om dit te doen omdat de berichten al in het Engels zijn, dus laten we doorgaan naar de volgende sectie en ondersteuning voor een tweede taal toevoegen.

Meerdere locaties

Natuurlijk is de standaardlocale voor Phoenix-toepassingen Engels, maar deze instelling kan eenvoudig worden gewijzigd door de config / config.exs het dossier. Laten we bijvoorbeeld de standaard locale instellen op Russisch (voel je vrij om te houden met een andere taal naar keuze):

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

Het is ook een goed idee om de volledige lijst met alle ondersteunde landinstellingen te specificeren:

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

Wat we nu moeten doen is een nieuw PO-bestand genereren met vertalingen voor de Russische locale. Het kan worden gedaan door het gettext.merge script opnieuw, maar met een --locale schakelaar:

mix gettext.merge priv / gettext --locale ru

Uiteraard, een priv / gettext / ru / LC_MESSAGES map met de .po bestanden binnen worden gegenereerd. Merk overigens op dat los van de default.po bestand, we hebben ook errors.po. Dit is een standaardlocatie voor het vertalen van foutmeldingen, maar in dit artikel gaan we het negeren.

Nu tweak priv / gettext / ru / LC_MESSAGES / default.po door enkele vertalingen toe te voegen:

msgstr "We gebruiken versie% versie" msgstr "Используется версия% version" #: lib / demo_web / templates / page / index.html .eex: 2 msgstr "Welkom bij% name!" msgstr "Добро пожаловать в приложение% name!"

Nu, afhankelijk van de gekozen locale, geeft Phoenix Engelse of Russische vertalingen. Maar wacht even! Hoe kunnen we eigenlijk schakelen tussen locales in onze applicatie? Laten we doorgaan naar het volgende gedeelte en ontdekken!

Schakelen tussen verschillende locaties

Nu sommige vertalingen aanwezig zijn, moeten we onze gebruikers in staat stellen om tussen de verschillende talen te schakelen. Het lijkt erop dat er een externe plug is die set_locale heet. Het werkt door de gekozen locale uit de URL of uit te pakken Accept-Language HTTP-header. Dus, om een ​​locale in de URL op te geven, zou je typen http: // localhost: 4000 / nl / some_path. Als de landinstelling niet is opgegeven (of als een niet-ondersteunde taal is aangevraagd), gebeurt er een van de twee dingen:

  • Als het verzoek een bevat Accept-Language HTTP-header en deze locale wordt ondersteund, de gebruiker wordt omgeleid naar een pagina met de bijbehorende locale.
  • Anders wordt de gebruiker automatisch doorgestuurd naar een URL die de code van de standaardlandinstelling bevat.

Open de  mix.exs bestand en drop-in set_locale naar de deps functie:

 defp-deps doen [# ... : set_locale, "~> 0.2.1"] eindigen

We moeten het ook toevoegen aan de toepassing functie:

 def application do [mod: Demo.Application, [], extra_applications: [: logger,: runtime_tools,: set_locale]] end

Installeer vervolgens alles:

mix deps.get

Onze router bevindt zich op lib / demo_web / router.ex vereist ook enkele veranderingen. Concreet moeten we een nieuwe plug toevoegen aan de : browser pijpleiding:

 pipeline: browser do # ... plug SetLocale, gettext: DemoWeb.Gettext, default_locale: "ru" end

Maak ook een nieuwe scope:

 scope "/: locale", DemoWeb do pipe_through: browser krijgt "/", PageController,: index end

En dat is het! U kunt de server opstarten en naar navigeren http: // localhost: 4000 / ru en http: // localhost: 4000 / nl. Merk op dat de berichten correct worden vertaald, en dat is precies wat we nodig hebben!

U kunt ook een soortgelijke functie zelf coderen door een module-plug te gebruiken. Een klein voorbeeld is te vinden in de officiële gids van Phoenix.

Een laatste ding om te vermelden is dat je in sommige gevallen een specifieke locale moet afdwingen. Om dit te doen, gebruikt u gewoon a with_locale functie:

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

pluralization

We hebben de grondbeginselen geleerd van het gebruik van Gettext met Phoenix, dus het is tijd om iets complexere dingen te bespreken. pluralization is een van hen. Kortom, werken met meervoudige en enkelvoudige vormen is een veel voorkomende maar mogelijk complexe taak. Dingen zijn min of meer duidelijk in het Engels als je "1 appel", "2 appels", "9000 appels" enz. Hebt (hoewel "1 os", "2 ossen"!).

Helaas zijn de regels in sommige andere talen, zoals Russisch of Pools, ingewikkelder. Bijvoorbeeld, in het geval van appels, zou je zeggen "1 яблоко", "2 яблока", "9000 яблок". Maar gelukkig voor ons heeft Phoenix een Gettext.Plural gedrag (u kunt het gedrag in actie zien in een van mijn vorige artikelen) dat veel verschillende talen ondersteunt. Daarom is alles wat we moeten doen, gebruik maken van de ngettext functie.

Deze functie accepteert drie vereiste argumenten: een tekenreeks in enkelvoud, een tekenreeks in meervoud en aantal. Het vierde argument is optioneel en kan bindingen bevatten die in de vertaling moeten worden geïnterpoleerd.

Laten we eens kijken ngettext in actie door te zeggen hoeveel geld de gebruiker heeft door het te wijzigen demo / lib / demo_web / templates / page / index.html.eex het dossier:

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

% Count is een interpolatie die zal worden vervangen door een nummer (540 in dit geval). Vergeet niet om de sjabloon en alle PO-bestanden bij te werken na het toevoegen van de bovenstaande reeks:

mix gettext.extract --merge

Je zult zien dat een nieuw blok aan beide is toegevoegd default.po bestanden:

msgstr "U hebt één buck. Ow :(" msgid_plural "U hebt% count bucks" msgstr [0] "" msgstr [1] ""

We hebben hier niet één maar twee sleutels tegelijk: in enkelvoud en in meervoudige vormen. msgstr [0] zal wat tekst bevatten om weer te geven als er maar één bericht is. msgstr [1], bevat natuurlijk de tekst die moet worden weergegeven als er meerdere berichten zijn. Dit is oké voor Engels, maar niet genoeg voor Russisch, waar we een derde geval moeten introduceren: 

msgstr "U hebt één buck. Ow :(" msgid_plural "U hebt% count bucks" msgstr [0] "У 1 доллар. Маловато будет!" msgstr [1] "У вас% count доллара" msgstr [2 ] "У вас% count долларов"

Geval 0 wordt gebruikt voor 1 dollar en koffer 1 voor nul of weinig geld. Geval 2 wordt anders gebruikt.

Scoping Translations met domeinen

Een ander onderwerp dat ik wilde bespreken in dit artikel is gewijd aan domeinen. Zoals we al weten, worden domeinen gebruikt om vertalingen te maken, voornamelijk in grote applicaties. In principe gedragen ze zich als namespaces.

U kunt immers in een situatie belanden dat dezelfde sleutel op meerdere plaatsen wordt gebruikt, maar een beetje anders moet worden vertaald. Of wanneer u veel te veel vertalingen in één exemplaar hebt default.po bestand en zou ze op de een of andere manier willen splitsen. Dat is wanneer domeinen erg handig kunnen zijn. 

Gettext ondersteunt meerdere domeinen uit de verpakking. Het enige dat u hoeft te doen is gebruik maken van de dgettext functie, die bijna hetzelfde werkt als gettext. Het enige verschil is dat het de domeinnaam als het eerste argument accepteert. Laten we bijvoorbeeld een meldingsdomein introduceren bij, nou, display-meldingen. Voeg nog drie regels code toe aan de demo / lib / demo_web / templates / page / index.html.eex het dossier:

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

Nu moeten we nieuwe POT- en PO-bestanden maken:

mix gettext.extract --merge

Nadat het script klaar is met zijn werk, notifications.pot evenals twee notifications.po bestanden worden aangemaakt. Merk nogmaals op dat ze naar het domein zijn genoemd. Het enige wat je nu hoeft te doen is een vertaling toevoegen voor de Russische taal door het wijzigen van de priv / ru / LC_MESSAGES / notifications.po het dossier:

msgid "Heads up:% msg" msgstr "Внимание:% msg"

Wat als u een bericht dat is opgeslagen onder een bepaald domein wilt pluraliseren? Dit is zo simpel als het gebruik van een dngettext functie. Het werkt net als ngettext maar accepteert ook de naam van een domein als eerste argument:

dgettext "domain", "Singular string% msg", "Meervoudig tekenreeks% msg", 10, msg: "demo"

Conclusie

In dit artikel hebben we gezien hoe we internationalisering in een Phoenix-toepassing konden introduceren met behulp van Gettext. Je hebt geleerd wat Gettext is en met wat voor soort bestanden het werkt. We hebben deze oplossing in actie, hebben met PO- en POT-bestanden gewerkt en verschillende Gettext-functies gebruikt.

We hebben ook een manier gezien om ondersteuning voor meerdere locaties toe te voegen en een manier toegevoegd om eenvoudig tussen deze locaties te schakelen. Ten slotte hebben we gezien hoe pluralisatieregels kunnen worden toegepast en hoe vertalingen kunnen worden gemaakt met behulp van domeinen.

Hopelijk was dit artikel nuttig voor u! Als u meer wilt weten over Gettext in het Phoenix-framework, kunt u de officiële gids raadplegen, die handige voorbeelden en API-referentie biedt voor alle beschikbare functies..

Ik dank u voor uw aanwezigheid bij mij en tot binnenkort!