Phoenix I18n

Nei miei precedenti articoli ho trattato i vari aspetti di Elixir: un linguaggio di programmazione funzionale moderno. Oggi, tuttavia, vorrei uscire dal linguaggio stesso e discutere un framework MVC molto veloce e affidabile chiamato Phoenix che è scritto in Elisir.

Questo quadro è emerso quasi cinque anni fa e da allora ha ricevuto una certa trazione. Certo, non è ancora così popolare come Rails o Django, ma ha un grande potenziale e mi piace molto.

In questo articolo vedremo come introdurre I18n nelle applicazioni di Phoenix. Cosa è i18n, tu chiedi? Bene, è un numeronimo che significa "internazionalizzazione", in quanto vi sono esattamente 18 caratteri tra la prima lettera "i" e l'ultima "n". Probabilmente, hai anche incontrato un l10n numeronym che significa "localizzazione". Gli sviluppatori di questi tempi sono così pigri da non riuscire nemmeno a scrivere un paio di caratteri in più, eh?

L'internazionalizzazione è un processo molto importante, soprattutto se si prevede che l'applicazione sia utilizzata da persone di tutto il mondo. Dopotutto, non tutti conoscono bene l'inglese e avere l'app tradotta nella lingua nativa dell'utente dà una buona impressione.

Sembra che il processo di traduzione delle applicazioni di Phoenix sia in qualche modo diverso, ad esempio, dalla traduzione delle app Rails (ma abbastanza simile allo stesso processo in Django). Per tradurre le applicazioni di Phoenix, usiamo una soluzione piuttosto popolare chiamata Gettext, che è in circolazione da oltre 25 anni. Gettext funziona con tipi speciali di file, ovvero PO e POT, e supporta funzionalità come scoping, pluralizzazione e altre chicche. 

Quindi in questo post ho intenzione di spiegarvi cos'è Gettext, come PO differisce da POT, come localizzare i messaggi in Phoenix e dove memorizzare le traduzioni. Vedremo anche come cambiare le impostazioni locali dell'applicazione e come lavorare con regole e domini di pluralizzazione.

Dobbiamo cominciare?

Internazionalizzazione con Gettext

Gettext è uno strumento di internazionalizzazione open source testato in battaglia, introdotto inizialmente da Sun Microsystems nel 1990. Nel 1995, GNU ha rilasciato la sua versione di Gettext, che ora è considerata la più popolare là fuori (l'ultima versione era 0.19.8 a il tempo di scrivere questo articolo). Gettext può essere utilizzato per creare sistemi multilingue di qualsiasi dimensione e tipo, dalle app Web ai sistemi operativi. Questa soluzione è piuttosto complessa e, naturalmente, non ne discuteremo tutte le caratteristiche. La documentazione completa di Gettext può essere trovata su gnu.org.

Gettext fornisce tutti gli strumenti necessari per eseguire la localizzazione e presenta alcuni requisiti su come i file di traduzione dovrebbero essere nominati e organizzati. Per ospitare le traduzioni vengono utilizzati due tipi di file: PO e MO.

PO (Oggetto portatile) i file memorizzano le traduzioni per le stringhe fornite, nonché le regole di pluralizzazione e i metadati. Questi file hanno una struttura abbastanza semplice e possono essere facilmente modificati da un essere umano, quindi in questo articolo ci atteniamo a loro. Ogni file PO contiene traduzioni (o parte delle traduzioni) per una singola lingua e deve essere memorizzato in una directory denominata dopo questa lingua: it, fr, de, eccetera.

MO (Oggetto macchina) i file contengono dati binari che non devono essere modificati direttamente da un essere umano. Sono più difficili da lavorare, e discuterne è fuori dallo scopo di questo articolo.

Per rendere le cose più complesse, ci sono anche POT (modello oggetto portatile) File. Ospitano solo stringhe di dati da tradurre, ma non le traduzioni stesse. Fondamentalmente, i file POT vengono utilizzati solo come blueprint per creare file PO per varie impostazioni locali.

Esempio di applicazione Phoenix

Ok, quindi ora procediamo alla pratica! Se desideri seguire, assicurati di avere installato quanto segue:

  • OTP (versione 18 o successiva)
  • Elixir (1.4+)
  • Phoenix framework (userò la versione 1.3)

Crea una nuova applicazione di esempio senza un database eseguendo:

mix phx.new i18ndemo --no-ecto

--no-ecto afferma che il database non deve essere utilizzato dall'app (Ecto è uno strumento per comunicare con il DB stesso). Si noti che il generatore potrebbe richiedere un paio di minuti per preparare tutto.

Adesso usa CD per andare al nuovo creato i18ndemo cartella ed eseguire il seguente comando per avviare il server:

mix phx.server

Quindi, apri il browser e vai a http: // localhost: 4000, dove dovresti vedere un "Benvenuto a Phoenix!" Messaggio.

Ciao, Gettext!

Ciò che è interessante della nostra app Phoenix e, in particolare, il messaggio di benvenuto è che Gettext è già utilizzato di default. Vai avanti e apri il demo / lib / demo_web / templates / page / index.html.eex file che funge da pagina iniziale predefinita. Rimuovi tutto tranne questo codice:

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

Questo messaggio di benvenuto utilizza a gettext funzione che accetta una stringa da tradurre come primo argomento. Questa stringa può essere considerata come un chiave di traduzione, anche se è un po 'diverso dalle chiavi usate in Rails I18n e in altri framework. In Rails avremmo usato una chiave come page.welcome, mentre qui la stringa tradotta è una chiave si. Quindi, se non è possibile trovare la traduzione, possiamo visualizzare direttamente questa stringa. Anche un utente che conosce male l'inglese può avere almeno una sensazione di base di ciò che sta accadendo.

Questo approccio è abbastanza utile in realtà, fermati per un secondo e pensaci. Hai un'applicazione in cui tutti i messaggi sono in inglese. Se desideri internazionalizzarlo, nel caso più semplice tutto ciò che devi fare è avvolgere i tuoi messaggi con il gettext funzione e fornire traduzioni per loro (in seguito vedremo che il processo di estrazione delle chiavi può essere facilmente automatizzato, il che accelera le cose ancora di più).

Ok, torniamo al nostro piccolo snippet di codice e diamo un'occhiata al secondo argomento passato gettext: nome: "Phoenix". Questo è un cosiddetto rilegatura-un parametro avvolto con % che vorremmo interpolare nella traduzione data. In questo esempio, c'è solo un parametro chiamato nome.

Possiamo anche aggiungere un altro messaggio a questa pagina a scopo dimostrativo: 

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

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

Aggiungere una nuova traduzione

Ora che abbiamo due messaggi nella pagina principale, dove dovremmo aggiungere le traduzioni per loro? Sembra che tutte le traduzioni siano memorizzate sotto il priv / gettext cartella, che ha una struttura predefinita. Prendiamo un momento per discutere su come i file Gettext dovrebbero essere organizzati (questo vale non solo per Phoenix ma per qualsiasi app che usi Gettext).

Prima di tutto, dovremmo creare una cartella con il nome delle impostazioni locali per cui memorizzare le traduzioni. All'interno, dovrebbe esserci una cartella chiamata LC_MESSAGES contenente uno o più .Po file con le traduzioni attuali. Nel caso più semplice, ne avresti uno default.po file per locale. predefinito ecco il nome del dominio (o dell'ambito). I domini vengono utilizzati per dividere le traduzioni in vari gruppi: ad esempio, potresti avere nomi di domini Admin, WYSIWIG, carrello, e altro. Questo è utile quando hai una grande applicazione con centinaia di messaggi. Per le app più piccole, tuttavia, avere una suola predefinito il dominio è sufficiente. 

Quindi la nostra struttura dei file potrebbe essere simile a questa:

  • it
    • LC_MESSAGES
      • default.po
      • admin.po
  • ru
    • LC_MESSAGES
      • default.po
      • admin.po

Per iniziare a creare file PO, abbiamo prima bisogno del modello corrispondente (POT). Possiamo crearlo manualmente, ma sono troppo pigro per farlo in questo modo. Eseguiamo invece il seguente comando:

mix gettext.extract

È uno strumento molto utile che analizza i file del progetto e controlla se Gettext è utilizzato ovunque. Dopo che la sceneggiatura termina il suo lavoro, una nuova priv / gettext / default.pot verrà creato un file contenente stringhe da tradurre.

Come abbiamo già imparato, i file POT sono modelli, quindi memorizzano solo le chiavi stesse, non le traduzioni, quindi non modificare manualmente tali file. Apri un file appena creato e dai un'occhiata ai suoi contenuti:

## Questo file è un file modello PO. #### 'I messaggi di msgid qui sono spesso estratti dal codice sorgente. ## Aggiungi nuove traduzioni manualmente solo se sono traduzioni dinamiche ## che non possono essere estratte staticamente. ## ## Esegui "mix gettext.extract" per portare questo file alla data ##. Lasciare 'msgstr' è vuoto come modificandoli qui come nessun effetto ##: modificali invece in file PO ('.po'). msgstr "" msgstr "" #: lib / demo_web / templates / page / index.html.eex: 3 msgstr "Stiamo usando la versione% version" msgstr "" #: lib / demo_web / templates / page / index.html msgstr "Benvenuto in% nome!" msgstr ""

Conveniente, non è vero? Tutti i nostri messaggi sono stati inseriti automaticamente e possiamo facilmente vedere esattamente dove si trovano. msgstr, come probabilmente avete indovinato, è la chiave, mentre msgstr sta per contenere una traduzione.

Il passo successivo è, naturalmente, la generazione di un file PO. Correre:

mix gettext.merge priv / gettext

Questo script utilizzerà il default.pot modello e creare a default.po file nel priv / gettext / it / LC_MESSAGES cartella. Per ora, abbiamo solo un locale in inglese, ma il supporto per un'altra lingua verrà aggiunto anche nella sezione successiva.

A proposito, è possibile creare o aggiornare il modello POT e tutti i file PO in una volta sola usando il seguente comando:

mix gettext.extract --merge

Ora apriamo il priv / gettext / it / LC_MESSAGES / default.po file, che ha il seguente contenuto:

## 'msgid in questo file provengono da file POT (.pot). ## ## Non aggiungere, modificare o rimuovere "msgid qui manualmente come ## sono legati a quelli nel corrispondente file POT ## (con lo stesso dominio). ## ## Usa 'mix gettext.extract --merge' o 'mix gettext.merge' ## per unire file POT in file PO. msgid "" msgstr "" "Lingua: en \ n" #: lib / demo_web / templates / page / index.html.eex: 3 msgstr "Stiamo usando la versione% version" msgstr "" #: lib / demo_web / msgstr "Benvenuto in% nome!" msgstr ""

Questo è il file in cui dovremmo eseguire la traduzione effettiva. Ovviamente, non ha molto senso farlo perché i messaggi sono già in inglese, quindi passiamo alla sezione successiva e aggiungiamo il supporto per una seconda lingua.

Più locali

Naturalmente, le impostazioni internazionali predefinite per le applicazioni Phoenix sono l'inglese, ma questa impostazione può essere modificata facilmente modificando il file config / config.exs file. Ad esempio, impostiamo le impostazioni internazionali predefinite in russo (sentiti libero di utilizzare qualsiasi altra lingua di tua scelta):

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

È anche una buona idea specificare l'elenco completo di tutte le impostazioni locali supportate:

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

Ora quello che dobbiamo fare è generare un nuovo PO file contenente traduzioni per il locale russo. Può essere fatto eseguendo il gettext.merge script di nuovo, ma con a --località interruttore:

mix gettext.merge priv / gettext --locale ru

Ovviamente, a priv / gettext / ru / LC_MESSAGES cartella con il .Po i file all'interno verranno generati. Nota, a proposito, che a parte il default.po file, abbiamo anche errors.po. Questo è un posto predefinito per tradurre i messaggi di errore, ma in questo articolo lo ignoreremo.

Ora modificare il priv / gettext / RU / LC_MESSAGES / default.po aggiungendo alcune traduzioni:

msgstr "Stiamo usando la versione% version" msgstr "Используется версия% version" #: lib / demo_web / templates / page / index.html msgstr "Benvenuto in% nome!" msgstr "Добро пожаловать в приложение% name!"

Ora, a seconda delle impostazioni locali scelte, Phoenix eseguirà traduzioni in inglese o russo. Ma aspetta! Come possiamo effettivamente passare da un locale all'altro nella nostra applicazione? Passiamo alla prossima sezione e scopriamolo!

Passaggio da locale a locale

Ora che alcune traduzioni sono presenti, dobbiamo abilitare i nostri utenti a passare da un locale all'altro. Sembra che ci sia un plug di terze parti per quello chiamato set_locale. Funziona estraendo la locale scelta dall'URL o Accept-Language Intestazione HTTP Quindi, per specificare una localizzazione nell'URL, devi digitare http: // localhost: 4000 / it / QUALCHE_PERCORSO. Se la locale non è specificata (o se è stata richiesta una lingua non supportata), si verificherà una delle due cose:

  • Se la richiesta contiene un Accept-Language Intestazione HTTP e questa locale è supportata, l'utente verrà reindirizzato a una pagina con le impostazioni internazionali corrispondenti.
  • In caso contrario, l'utente verrà automaticamente reindirizzato a un URL che contiene il codice delle impostazioni internazionali predefinite.

Apri il  mix.exs archiviare e rilasciare set_locale al dipendenze funzione:

 defp deps do [# ... : set_locale, "~> 0.2.1"] fine

Dobbiamo anche aggiungerlo al applicazione funzione:

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

Quindi, installa tutto:

mescola deps.get

Il nostro router situato a lib / demo_web / router.ex richiede anche alcune modifiche. Nello specifico, dobbiamo aggiungere un nuovo plug-in al : Browser conduttura:

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

Inoltre, crea un nuovo ambito:

 scope "/: locale", DemoWeb fa pipe_through: il browser ottiene "/", PageController,: fine dell'indice

E questo è tutto! È possibile avviare il server e navigare http: // localhost: 4000 / ru e http: // localhost: 4000 / it. Si noti che i messaggi sono tradotti correttamente, che è esattamente ciò di cui abbiamo bisogno!

In alternativa, è possibile codificare una funzionalità simile utilizzando una spina del modulo. Un piccolo esempio può essere trovato nella guida ufficiale di Phoenix.

Un'ultima cosa da ricordare è che in alcuni casi potrebbe essere necessario imporre un locale specifico. Per farlo, basta utilizzare a with_locale funzione:

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

pluralizzazione

Abbiamo imparato i fondamenti dell'uso di Gettext con Phoenix, quindi è giunto il momento di discutere di cose leggermente più complesse. pluralizzazione è uno di loro. Fondamentalmente, lavorare con forme plurali e singolari è un compito molto comune anche se potenzialmente complesso. Le cose sono più o meno ovvie in inglese come hai "1 mela", "2 mele", "9000 mele" ecc. (Anche se "1 bue", "2 buoi"!).

Sfortunatamente, in alcune altre lingue come il russo o il polacco, le regole sono più complesse. Ad esempio, nel caso delle mele, diresti "1 яблоко", "2 яблока", "9000 яблок". Ma fortunatamente per noi, Phoenix ha un Gettext.Plural comportamento (puoi vedere il comportamento in azione in uno dei miei articoli precedenti) che supporta molte lingue diverse. Quindi tutto ciò che dobbiamo fare è sfruttare il ngettext funzione.

Questa funzione accetta tre argomenti obbligatori: una stringa in forma singolare, una stringa in forma plurale e conteggio. Il quarto argomento è facoltativo e può contenere associazioni che devono essere interpolate nella traduzione.

Vediamo ngettext in azione dicendo quanti soldi ha l'utente modificando il demo / lib / demo_web / templates / page / index.html.eex file:

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

%contare è un'interpolazione che verrà sostituita con un numero (540 in questo caso). Non dimenticare di aggiornare il modello e tutti i file PO dopo aver aggiunto la stringa precedente:

mix gettext.extract --merge

Vedrai che un nuovo blocco è stato aggiunto a entrambi default.po File:

msgstr "" "Hai% count bucks" msgstr [0] "" msgstr [1] ""

Non abbiamo una ma due chiavi qui contemporaneamente: in forme singolari e al plurale. msgstr [0] sta per contenere del testo da visualizzare quando c'è un solo messaggio. msgstr [1], ovviamente, contiene il testo da mostrare quando ci sono più messaggi. Va bene per l'inglese, ma non abbastanza per il russo, dove è necessario introdurre un terzo caso: 

msgstr "" Hai% count bucks "msgstr [0]" 1 доллар. Маловато будет! "msgstr [1]" У вас% count доллара "msgstr [2 ] "У вас% count долларов"

Astuccio 0 è usato per 1 dollaro e cassa 1 per zero o pochi dollari. Astuccio 2 è usato diversamente.

Scoping traduzioni con domini

Un altro argomento che volevo discutere in questo articolo è dedicato a domini. Come già sappiamo, i domini sono utilizzati per le traduzioni di ambito, principalmente in applicazioni di grandi dimensioni. Fondamentalmente, si comportano come spazi dei nomi.

Dopo tutto, si può finire in una situazione in cui la stessa chiave viene utilizzata in più punti, ma dovrebbe essere tradotta in modo leggermente diverso. O quando hai troppe traduzioni in una sola default.po file e vorrebbe dividerli in qualche modo. Questo è quando i domini possono essere veramente utili. 

Gettext supporta molteplici domini pronti all'uso. Tutto quello che devi fare è utilizzare il dgettext funzione, che funziona quasi come gettext. L'unica differenza è che accetta il nome di dominio come primo argomento. Ad esempio, introduciamo un dominio di notifica per, beh, visualizzare le notifiche. Aggiungi altre tre righe di codice al file demo / lib / demo_web / templates / page / index.html.eex file:

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

Ora abbiamo bisogno di creare nuovi file POT e PO:

mix gettext.extract --merge

Dopo che lo script ha finito di fare il suo lavoro, notifications.pot così come due notifications.po i file verranno creati. Nota ancora una volta che prendono il nome dal dominio. Tutto quello che devi fare ora è aggiungere la traduzione per la lingua russa modificando il priv / ru / LC_MESSAGES / notifications.po file:

msgstr "Avviso:% msg" msgstr "Внимание:% msg"

Cosa succede se si desidera pluralizzare un messaggio memorizzato in un determinato dominio? Questo è semplice come utilizzare a dngettext funzione. Funziona proprio come ngettext ma accetta anche il nome di un dominio come primo argomento:

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

Conclusione

In questo articolo, abbiamo visto come introdurre l'internazionalizzazione in un'applicazione Phoenix con l'aiuto di Gettext. Hai imparato cos'è Gettext e che tipo di file lavora con. Abbiamo questa soluzione in azione, abbiamo lavorato con i file PO e POT e utilizzato varie funzioni di Gettext.

Abbiamo anche visto un modo per aggiungere il supporto per più locale e aggiunto un modo per passare facilmente da una all'altra. Infine, abbiamo visto come utilizzare le regole di pluralizzazione e come orientare le traduzioni con l'aiuto dei domini.

Spero che questo articolo ti sia stato utile! Se desideri saperne di più su Gettext nel framework Phoenix, puoi fare riferimento alla guida ufficiale, che fornisce esempi utili e riferimenti API per tutte le funzioni disponibili.

Ti ringrazio per essere stato con me e a presto!