Creazione di un'applicazione multilingua

Posted by reggie, Thu Apr 26 22:13:00 UTC 2007

Ora che abbiamo affrontato a grandi linee alcuni degli aspetti fondamentali che ci permettono di gestire la globalizzazione all’interno di una Rails application, cerchiamo di approfondire il discorso costruendo un’applicazione di esempio, caratterizzata da una semplice gestione di vocabolari cartacei che vogliamo vendere descritti in diverse lingue. Questo ci permetterà di introdurre nuovi aspetti molto interessanti, che probabilmente incontreremo nel momento in cui costruiremo qualcosa di realmente funzionante.

Diamo il via alle danze

Iniziamo con la creazione di una semplicissima tabella nella quale inserire il nome, la descrizione ed il prezzo dei nostri vocabolari. Per la creazione della tabella utilizzeremo una delle caratteristiche più uniche che Rails possa offrire: la migration. Quindi la prima cosa da fare è creare una classe all’interno della quale poterla definire e per farlo ci viene in aiuto il generatore migrate.

1
2
3
4
5
6
# creazione della classe in cui definire la migrazione
E:\dictionary> ruby script/generate migration Dictionary

# file e path generati dalla migrazione
E:\dictionary> create  db/migrate
E:\dictionary> create  db/migrate/001_dictionary.rb

Come possiamo vedere è stato generato il nuovo file 001_dictionary.rb ed è proprio qui che noi andremo a definire la struttura della nostra tabella. Sarà poi il comando rake a creare in modo definitivo la tabella all’interno del database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Estratto del file db/migrate/001_dictionaries_table.rb
class Dictionary < ActiveRecord::Migration
  # definizione caricamento della tabella dictionaries
  def self.up
    create_table :dictionaries do |t|
      t.column :name, :string
      t.column :description, :text
      t.column :price, :integer
    end
  end
  # definizione rimozione della tabella dictionaries
  def self.down
    drop_table :dictionaries
  end
end
1
2
# caricamento della tabella dictionaries
E:\dictionary> rake migrate

Ora che abbiamo creato la nostra tabella creiamo, all’interno della nostra applicazione, una struttura che ci permetta di gestire l’inserimento dei dizionari.

1
2
# scaffolding della tabella dictionary
E:\dictionary> ruby script/generate scaffold Dictionary

Via alla traduzione

Quello che ora vogliamo fare è tradurre questi record in più lingue, o almeno tradurre una parte di essi. Apportiamo quindi le modifiche necessarie al modello dictionary.

1
2
3
4
5
6
7
8
9
10
11
12
# Estratto del file app/models/dictionary.rb
class Dictionary < ActiveRecord::Base 
  
  # definizione traduzione dei campi description e price
  translates :description, :price 
  
  # gestione della traduzione del campo price 
  # ponendo la sua gestione come campo valuta
  composed_of :price,     
              :class_name => "Globalize::Currency", 
              :mapping => [ %w(price cents) ]
end

Come possiamo vedere, abbiamo richiesto la gestione multilingua dei campi description e price. Abbiamo poi utilizzato anche un nuovo metodo composed_of, il quale ci permetterà di gestire campi di tipo valuta. All’interno di Rails questa tecnica viene chiamata aggregazione e permette di associare ad un campo della nostra tabella un oggetto Ruby più o meno complesso. Nel nostro esempio all’interno del campo price non facciamo altro che settare un oggetto di tipo Currency (definito dal plugin Globalize) che come vedremo gestirà in automatico la formattazione della valuta a seconda del locale attivo. Proviamo ora ad inserire qualche record nella nostra tabella per verificare che tutto funzioni correttamente.

Figura 4: Errore durante la creazione di un record all’interno della tabella dictionary

Qualcosa è andato storto. Come possiamo vedere dalla figura 4 risulta esserci qualche problema, quindi cerchiamo di capire quale possa essere la causa esaminando il messaggio che viene visualizzato nel browser. Questo consiste principalmente in una stringa che afferma la mancata definizione del metodo cents per la stringa “1111”.

1
2
# messaggio di errore principale
undefined method 'cents' for "1111":String

In effetti noi non vorremmo richiamare il metodo cents su di un oggetto String (come mostrato dall’errore), ma su di un oggetto di tipo Currency, quindi dobbiamo creare un metodo che faccia questa conversione prima della creazione di un nuovo oggetto Dictionary (il modello che rappresenta il nostro record).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Estratto del file app/controllers/dictionary_controller.rb
class Dictionary < ActiveRecord::Base 
  # chiamata del metodo set_currency prima di andare
  # ad eseguire i metodi create ed update
  before_filter :set_currency, :only => [:create, :update]  

  # passaggio del campo price da String a Currency prima della creazione
  def create
    ...
  end
  # passaggio del campo price da String a Currency prima della modifica
  def update
    ...
  end
  private 
    # definizione dell'oggetto Currency sul campo price 
    def set_currency
      params[:dictionary][:price] = Currency.new(params[:dictionary][:price])
    end
end

Siamo quindi pronti a vedere i primi record inseriti all’interno della nostra tabella, e se tutto è stato fatto correttamente, il risultato ottenuto sarà simile a quello di figura 5, in cui possiamo vedere come il prezzo sia stato formattato inserendo automaticamente il simbolo dell’euro e la virgola per separare le due cifre decimali.

Figura 5: Lista dei dizionari inseriti in lingua italiana

Traduciamo le nostre informazioni in lingue diverse

Quello che manca ora è generare delle traduzioni in altre lingue, ad esempio in inglese. Nulla di più semplice. Ci basterà entrare all’interno dell’azione index del controller dictionary, settare il locale desiderato, per poi andare a modificare i record che desideriamo tradurre. Il risultato ottenuto sarà molto interessante, tenendo presente che tutte le associazioni tra i contenuti dei record e le traduzioni saranno gestiti in modo automatico dal magico Globalize.

1
2
3
4
5
6
7
# Estratto del file app/controllers/dictionary_controller.rb
  ...  
  def list
    Locale.set('en-US') # cambio del locale di default
    @dictionary_pages, @dictionaries = paginate :dictionaries, :per_page => 10
  end
  ...

Se andassimo ad aggiornare ora la lista dei vocabolari inseriti nella nostra applicazione noteremmo subito la variazione della formattazione del prezzo (anche se il prezzo rimane sempre lo stesso), mentre le descrizioni rimarrebbero uguali a quelle inserite in lingua italiana. Quello che vogliamo fare ora è quindi dare una descrizione ed un prezzo proprio per la lingua inglese, e questo per ogni record inserito. Per farlo dobbiamo semplicemente andare in modifica del record e inserire la nuova descrizione ed il nuovo prezzo. Nel momento in cui torneremo nella pagina principale, vedremo le descrizioni in lingua inglese ed i nuovi prezzi(figura 6).

Figura 6: Lista dei dizionari inseriti in lingua inglese

Se a questo punto andassimo a cambiare il locale inglese con quello italiano, ci ritroveremmo di nuovo con le traduzioni in lingua italiana. Abbiamo quindi raggiunto il nostro scopo. In questo piccolo esempio abbiamo fatto la traduzione in sole due lingue, ma nulla ci vieta di fare traduzioni in tutte le lingue che desideriamo, semplicemente seguendo i due passi sopra descritti, cioè:

  • settare il locale nella lingua che vogliamo tradurre;
  • andare a modificare i record.

Se andiamo un pochino a fondo, anche in questo caso il funzionamento che sta alla base della traduzione risulta essere decisamente semplice. Come possiamo vedere dalla figura 7, la traduzione multilingua si basa principalmente sui seguenti campi:

  • facets: indica la colonna della tabella dictionaries da tradurre;
  • item_id: è l’identificativo del record da tradurre;
  • text: contiene la traduzione nella lingua definita dal campo language_id.

Figura 7: Traduzione dei models all’interno della tabella globalize_translations

Settaggio del locale in un’applicazione reale

Arrivati a questo punto siamo abbastanza soddisfatti, ma desideriamo fare qualcosina in più. In effetti dover cambiare di volta in volta il locale all’interno della nostra azione index non è la soluzione più comoda, quindi forniremo alla nostra applicazione un’interfaccia ed un insieme di metodi che permettano un cambio veloce della lingua desiderata. Iniziamo quindi la creazione del metodo set_locale, il quale, come si può intuire dal nome, ci permetterà di cambiare il locale attivo in modo semplice ma completo.

1
2
3
4
5
6
7
8
9
10
11
# Estratto del file app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # settaggio di un nuovo locale attivo
  def set_locale
    if params[:locale]
      Locale.set params[:locale]  
    end
    # Ridirezione alla pagina principale dell'applicazione
    redirect_to(:controller => "dictionaries", :action => "index")
  end
end

Fatto questo, dobbiamo semplicemente creare due link che vadano a richiamare questo nuovo metodo, permettendo così il cambio della lingua della nostra applicazione. Inoltre visualizzeremo con un’altro messaggio quale sia il locale attivo in un determinato momento. Tutte queste modifiche le inseriremo all’interno del layout.

1
2
3
4
5
6
7
# Estratto del file app/views/layouts/dictionary.rhtml
...
<body>
<%= link_to 'Italiano', :action => 'set_locale', :locale => 'it-IT' %> |
<%= link_to 'English', :action => 'set_locale', :locale => 'en-US' %> |
<span style="color: green"> Locale attivo: <%= Locale.active.code %> </span>
...

Non dimenticate di cancellare Locale.set dal metodo list. Vediamo ora il risultato a cui siamo arrivati (figura 8) in cui troviamo i due nuovi link Italiano ed English che ci permetteranno di passare da un linguaggio all’altro.

Figura 8: Risultato delle traduzioni multilingua

Ma se voglio differenziare singolare e plurale

Prima di concludere introduciamo un ultimo comando che potrebbe essere utile durante lo sviluppo della nostra applicazione. Per la nostra applicazione di esempio potrebbe risultare utile inserire un messaggio nel quale vengano elencati quanti siano i vocabolari messi a disposizione. In questo caso non ci basta effettuare solo la traduzione, ma dobbiamo differenziare la notazione singolare (c’è 1 vocabolario) dalla notazione plurale (ci sono n vocabolari). Chiariamo l’idea con un esempio, non completo, ma che ci fa capire il meccanismo nella pratica

1
2
3
# Estratto del file app/views/layouts/dictionary.rhtml
<body>
  <%= "Ci sono %d vocabolari nella tua pagina" / @number_of_dictionaries %>  ...

Naturalmente per ottenere il risultato desiderato dobbiamo anche effettuare la traduzione della lingua dall’italiano all’inglese, e gestire anche le differenze tra la forma singolare e la forma plurale. Come avvenuto in precedenza anche in questo caso useremo il metodo set_translation, però con una piccola variazione.

1
2
3
4
5
# Estratto del file app/controllers/dictionary.tb
Locale.set_translation("Ci sono %d vocabolari nella tua pagina",
                       Language.pick('en-US'), # lingua in cui tradurre 
                       "There is one dictionary in your page", # vers. singolare
                       "There are %d items in your page")# vers. plurale

Fatto questo il risultato ottenuto nel caso in cui avessimo un solo item sarà simile al seguente.


There is one item in your page

Conclusioni

Riassumendo abbiamo dato un’occhiata a come sia possibile sfruttare la potenza di Rails e di Globalize per creare un’applicazione multilingua reale. Naturalmente si tratta di un semplice esempio e sono molte le cose che non abbiamo affrontato. Per chi desiderasse approfondire qui di seguito ci sono i link che ho utilizzato e nei quali ho trovato tutte le informazioni per scrivere questo piccolo tutorial.

Per chi desidera approfondire la conoscenza di Globalize Per chi desidera chiarire un pochino di terminologia

Licenza

Questo articolo è coperto dalla licenza Creative Commons Attribution-NonCommercial-NoDerivs 2.5 Italy.

Filed Under: Libreria Rails Tutorial | Tags:

Comments

  1. kain 02.02.08 / 16PM
    ciao, grazie per l'articolo, è molto utile. ho due domanda fare: - quando si ha una form per inserire il prezzo di un nuovo prodotto, nell'esempio soprastante inserisci il corrispettivo direttamente in centesimi? - non esiste qualcosa in globalize che permette la visualizzazione di un prezzo, data una currency fissa come dollaro o euro, tradotta? 1400€ != 1400$. leggendo il codice mi pare di no, però mi risulta che la Money class abbia qualcosa per farlo..
  2. kain 02.02.08 / 16PM
    urgh, scusate per la formattazione del commento precedente. inoltre, @translates :description, :price@ è una feature di globalize edge? a me con la stable1-2 non sembra sortire alcun effetto.
  3. assente 02.02.08 / 16PM
    Globalize è carino, ci mancherebbe solo un convertitore da gettext per le traduzioni statiche (quelle dell'intefaccia e non dei contenuti).

Have your say

A name is required. You may use HTML in your comments.