Le applicazioni multiligua devono anche essere in grado di adattarsi al particolare modo rappresentare i dati di reioni dverse, non solo traducendo i messaggi di testo, ma anche cambiando la formattazione delle date o dei numeri decimali.
Se da tempo sono disponibili plugin per il framework Rails che permettono di affrontare il problema dell'internazionalizzazione, la versione 2.2 di Rails espone un set di API che permette la costruzioni di funzionalità dedicate alla localizzazione, incorporando un backend semplificato che espone le funzionalità più comuni.
Con questa strategia Rails raggiunge il duplice scopo di includere nel framework gli strumenti minimi per applicazioni multiligua e di offrire la base per lo sviluppo di soluzioni differenti che sostituiscano quella di base.
Con ogni probabilità questa impostazione sarà adottata con insistenza delle prossime major release di Rails.
In questo articolo esploriamo le funzionalità disponibili nel backend per l'internazionalizzazione di Rails 2.2, chiamato Simple Backend per sottolineare l'intento di offrire un set minimo di funzionalità. Utilizzeremo come esempio l'applicazione creata per l'articolo dedicato ai filtri in Rails, che può essere generata ex novo con i seguenti comandi:
rails int_notes cd int_notes ruby script/generate scaffold Note title:string content:text username:string password:string rake db:migrate
Prime configurazioni
Come prima cosa impostiamo la lingua (e tutto il contesto culturale locale) di default per l'applicazione, modificando il file config/environment.rb
. L'istruzione riportata sotto dovrebbe essere già presente nel file di configurazione ma commentata:
... config.i18n.default_locale = :en ...
Questa istruzione impone all'applicazione di utilizzare il contesto locale identificato dal simbolo en
tutte le volte che non è richiesto l'utilizzo di un contesto specifico. La configurazione del contesto locale corrispondente al simbolo en
risiede nel file config/locales/en.yml
, dove il nome del file corrisponde al simbolo utilizzato per identificare la lingua.
Seguendo questa regola creiamo il file config/locales/it.yml
che conterrà la configurazione locale in italiano, identificata dal simbolo it
.
Questi file di configurazione sono di tipo Yaml, lo stesso formato utilizzato per la configurazione del database (file config/database.yml
), è possibile specificare i file anche come script Ruby con estensione rb
ma è meno consueto.
Nei file Yaml le impostazioni sono espresse in modo gerarchico; ad esempio:
en: # simbolo che identifica il locale lorem: ipsum: "foo" dolor: "bar" sit: "foobar"
definisce proprietà che rispondono a:
lorem.ipsum con valore foo lorem.dolor con valore bar sit con valore foobar
Localizzazione delle viste
Avviamo il server e accediamo alla pagina di creazione di una nuova istanza del modello Note
puntando il browser all'indirizzo http://localhost:3000/notes/new
.
Sulla pagina individuiamo le stringhe "New note", "Title", "Content", ... "Back" che devono essere tradotte nelle differenti lingue; definiamo nel file config/locales/en.yml
una serie di coppie chiave/valore per le stringhe che abbiamo individuato:
en: new_note: "Create a new note" title: "Title" content: "Content" username: "Username" password: "Password" create: "Create" back: "Back"
Modifichiamo anche il file config/locales/it.yml
così che a fronte delle stesse chiavi siano presenti le stringhe tradotte in italiano:
it: new_note: "Crea una nuova nota" title: "Titolo" content: "Contenuto" username: "Nome utente" password: "Password" create: "Crea" back: "Indietro"
Ora possiamo modificare la vista app/views/notes/new.html.erb
per richiedere la visualizzazione delle stringhe per il locale selezionato; ricordando di avere il valore di default del locale impostato nel file config/environment.rb
non dobbiamo fare altro che richiedere la visualizzazione della stringa per quel localo utilizzando il metodo t()
e come parametro la chiave di accesso utilizzato nei precedenti file di configurazione:
<h1><%= t(:new_note) %></h1> <% form_for(@note) do |f| %> <%= f.error_messages %> <p><%= f.label t(:title) %> <br /> <%= f.text_field :title %> </p> <p><%= f.label t(:content) %> <br /> <%= f.text_area :content %> </p> <p><%= f.label t(:username) %> <br /> <%= f.text_field :username %> </p> <p><%= f.label t(:password) %> <br /> <%= f.text_field :password %> </p> <p><%= f.submit t(:create) %> </p> <% end %> <%= link_to t(:back), notes_path %>
Ricaricando la pagina non si dovrebbe avvertire alcun cambiamento, poiché le stringhe per l'inglese sono identiche alle precedenti. Proviamo a modificare la lingua di default nel file config/environment.rb
:
... config.i18n.default_locale = :it ...
Riavviando il server e accedendo alla stessa pagina vedremo cambiare le stringhe nelle corrispondenti in italiano.
Scelta della lingua e del contesto locale
Per cambiare lingua, la nostra applicazione richiede quindi la modifica di un file di configurazione e il riavvio, ma dovremmo dare la possibilità ad ogni utente di scegliere la lingua che preferisce. Inseriamo allora un link sulla pagina da utilizzare per indicare il contesto locale da attivare:
<p> <%= link_to "it", :locale => "it" %> | <%= link_to "en", :locale => "en" %> </p>
L'helper link_to
produce due link con i parametri controller
e action
(ed eventuali altri parametri utilizzati per la richiesta) aventi gli stessi valori della richiesta corrente, ed aggiunge ai parametri già presente il parametro locale
valorizzato a it
e en
; per la richiesta http://localhost:3000/notes/new
saranno quindi generati i link:
http://localhost:3000/notes/new?locale=it http://localhost:3000/notes/new?locale=en
Inseriamo ora un filtro applicato a tutte le action del controller NotesController
:
class NotesController < ApplicationController before_filter :set_locale # GET /notes ... private def set_locale I18n.locale = params[:locale] end end
Il metodo set_locale
imposta la lingua in accordo con il valore del parametro params[:locale]
. Se il parametro è nullo viene assegnato il valore di default definito nel file di configurazione.
È possibile evolvere il metodo set_locale
così da salvare la preferenza dell'utente nella sua sessione ed utilizzare la sua lingua preferita anche in assenza del parametro della richiesta (in caso contrario sarebbe necessario esplicitare il parametro locale per ogni richiesta).
Se l'applicazione contempla una gestione utenti (ad esempio utilizzando il plugin Restful Authentication
, si può dare agli utenti la possibilità di scegliere la lingua direttamente dal pannello di gestione del proprio account, e modificare il filtro così che estragga questo dato prima di ogni richiesta.
Se, per una particolare stringa, si vuole utilizzare un linguaggio differente rispetto a quello utilizzato per tutte le altre, è possibile scrivere:
t(:title, :locale => :'en')
In questo caso il singolo messaggio è impostato a en
: questa scelta prevale sull'impostazione locale per la singola action determinato dal filtro, che a sua volta prevale sulla impostazione locale di default, scritta nel file di configurazione.
Messaggi flash e validazione ActiveRecord
Non tutte le stringhe da tradurre sono presenti nelle viste; nel caso dei messaggi flash (utilizzati per segnalare un messaggio relativo ad una action nella vista della action successiva), questi sono definiti nel controller, mentre nel caso dei messaggi di errore della validazione ActiveRecord
sono definiti nei modelli.
Per i messaggi flash poco cambia rispetto a quanto fatto per le viste; ricordando che il metodo t()
restituisce una stringa è possibile ad esempio scrivere all'interno della action create per il controller NotesController
:
... def create @note = Note.new(params[:note]) respond_to do |format| if @note.save flash[:notice] = t(:note_created) ...
aggiungendo al file en.yml
note_created: "Your note has been successfully created"
e al file it.yml
note_created: "La nota è stata creata con successo"
per definire la stringa da utilizzare nelle due lingue.
Per i messaggi di errore di validazione di ActiveRecord l'unica attenzione particolare va riposte nell'utilizzo del namespace riservato a questi messaggi.
Come accennato in precedenza le impostazioni di configurazione per le diverse lingue, possono avere un'organizzazione gerarchica; con questa gerarchia è possibile definire namespace personalizzati e alcuni sono stati adibiti ad usi particolari come quello che stiamo per utilizzare.
Il messaggio di errore abbinato alla validazione validates_presence_of
corrisponde a activerecord.errors.messages.blank; proviamo a personalizzarlo nel file it.yml
:
activerecord: errors: blank: "non può essere non compilato"
È possibile personalizzare ulteriormente i messaggi indicando un testo particolare per un errore di validazione di un modello o addirittura di un attributo. Per specificare un errore da abbinare a validates_presence_of
per un modello ma indipendente dall'attributo:
activerecord.errors.models.note.blank
In questo modo si identificano tutti i messaggi di errore relativi alla validazione validates_presence_of
per il modello Note
. Per indicare invece l'attributo a cui applicare il messaggio:
activerecord.errors.models.note.attributes.title.blank
In questo modo viene identificato il messaggio di errore relativo alla validazione validates_presence_of
per il modello Notes
per il solo attributo title
.
Sia per le stringhe "ordinarie" che per i messaggi di errore di ActiveRecord
è possibile configurare dei messaggi parametrici, ad esempio il messaggio di benvenuto per un utente che ha appena eseguito il login si potrebbe definire:
it: welcome: "Benvenuto {{user_firstname}}!"
ed essere richiamato in questo modo:
t(:welcome, :user_firstname => "Joe")
Il risultato è l'inserimento di "Joe" all'interno della stringa prodotta.
Conclusioni
Il set di API messe a disposizione da Rails 2.2 permette di sviluppare diversi backend dedicati all'internalizzazione. Abbiamo analizzato SimpleBackend
, incluso nell'installazione standard di Rails, ma nulla impedirà lo svilupparsi di soluzioni alternative, come dimostra la nuova versione del plugin Globalize.