Traduzioni¶
Il termine “internazionalizzazione” si riferisce al processo di astrazione delle stringhe e altri pezzi specifici dell’applicazione che variano in base al locale, in uno strato dove possono essere tradotti e convertiti in base alle impostazioni internazionali dell’utente (ad esempio lingua e paese). Per il testo, questo significa che ognuno viene avvolto con una funzione capace di tradurre il testo (o “messaggio”) nella lingua dell’utente:
// il testo verrà *sempre* stampato in inglese
echo 'Hello World';
// il testo può essere tradotto nella lingua dell'utente finale o
// restare in inglese
echo $translator->trans('Hello World');
Note
Il termine locale si riferisce all’incirca al linguaggio dell’utente e al paese.
Può essere qualsiasi stringa che l’applicazione utilizza poi per gestire le traduzioni
e altre differenze di formati (ad esempio il formato di valuta). Si consiglia di utilizzare il codice di lingua ISO 639-1,
un carattere di sottolineatura (_
), poi il codice di paese ISO 3166-1 alpha-2
(per esempio fr_FR
per francese/Francia).
In questo capitolo si imparerà a usare il componente Translation nel framework Symfony. Si può leggere la documentazione del componente Translation per saperne di più. Nel complesso, il processo ha diverse fasi:
- Abilitare e configurare il servizio translation di Symfony;
- Astrarre le stringhe (i. “messaggi”) avvolgendoli nelle chiamate al
Translator
(“Traduzione di base”); - Creare risorse di traduzione per ogni lingua supportata che traducano tutti i messaggio dell’applicazione;
- Determinare, impostare e gestire le impostazioni locali dell’utente per la richiesta e, facoltativamente, sull’intera sessione.
Configurazione¶
Le traduzioni sono gestire da un servizio translator
, che utilizza i
locale dell’utente per cercare e restituire i messaggi tradotti. Prima di utilizzarlo,
abilitare translator
nella configurazione:
Vedere Fallback e locale predefinito per dettagli sulla voce fallbacks
e su cosa faccia Symfony quando non trova una traduzione.
Il locale usato nelle traduzioni è quello memorizzato nella richiesta. Tipicamente,
è impostato tramite un attributo _locale
in una rotta (vedere Il locale e gli URL).
Traduzione di base¶
La traduzione del testo è fatta attraverso il servizio translator
(Symfony\Component\Translation\Translator
). Per tradurre un blocco
di testo (chiamato messaggio), usare il metodo
:method:`Symfony\\Component\\Translation\\Translator::trans`. Supponiamo,
ad esempio, che stiamo traducendo un semplice messaggio all’interno del controllore:
// ...
use Symfony\Component\HttpFoundation\Response;
public function indexAction()
{
$translated = $this->get('translator')->trans('Symfony is great');
return new Response($translated);
}
Quando questo codice viene eseguito, Symfony tenterà di tradurre il messaggio “Symfony is great” basandosi sul locale dell’utente. Perché questo funzioni, bisogna dire a Symfony come tradurre il messaggio tramite una “risorsa di traduzione”, che è una raccolta di traduzioni dei messaggi per un dato locale. Questo “dizionario” delle traduzioni può essere creato in diversi formati, ma XLIFF è il formato raccomandato:
Per informazioni sulla posizione di questi file, vedere Sedi per le traduzioni e convenzioni sui nomi.
Ora, se la lingua del locale dell’utente è il francese (per esempio fr_FR
o fr_BE
),
il messaggio sarà tradotto in J'aime Symfony
. Si può anche tradurre il
messaggio da un template.
Il processo di traduzione¶
Per tradurre il messaggio, Symfony utilizza un semplice processo:
- Viene determinato il
locale
dell’utente corrente, che è memorizzato nella richiesta; - Un catalogo di messaggi tradotti viene caricato dalle risorse di traduzione definite
per il
locale
(ad es.fr_FR
). Vengono anche caricati i messaggi dal locale predefinito e aggiunti al catalogo, se non esistono già. Il risultato finale è un grande “dizionario” di traduzioni; - Se il messaggio si trova nel catalogo, viene restituita la traduzione. Se no, il traduttore restituisce il messaggio originale.
Quando si usa il metodo trans()
, Symfony cerca la stringa esatta all’interno
del catalogo dei messaggi e la restituisce (se esiste).
Segnaposto per i messaggi¶
A volte, un messaggio da tradurre contiene una variabile:
use Symfony\Component\HttpFoundation\Response;
public function indexAction($name)
{
$translated = $this->get('translator')->trans('Hello '.$name);
return new Response($translated);
}
Tuttavia, la creazione di una traduzione per questa stringa è impossibile, poiché il traduttore proverà a cercare il messaggio esatto, includendo le parti con le variabili (per esempio “Hello Ryan” o “Hello Fabien”).
Per dettagli su come gestire questa situazione, vedere component-translation-placeholders nella documentazione del componente. Per i template, vedere Template Twig.
Pluralizzazione¶
Un’ulteriore complicazione si presenta con traduzioni che possono essere plurali o meno, in base a una qualche variabile:
There is one apple.
There are 5 apples.
Per poterlo gestire, usare il metodo :method:`Symfony\\Component\\Translation\\Translator::transChoice`
del tag o del filtro transchoice
nel template.
Per ulteriori e approfondite informazioni, vedere component-translation-pluralization nella documentazione del componente Translation.
Traduzioni nei template¶
Le traduzioni avvengono quasi sempre all’interno di template. Symfony fornisce un supporto nativo sia per i template Twig che per quelli PHP.
Template Twig¶
Symfony fornisce tag specifici per Twig (trans
e transchoice
), che aiutano
nella traduzioni di messaggi di blocchi statici di testo:
{% trans %}Hello %name%{% endtrans %}
{% transchoice count %}
{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
{% endtranschoice %}
Il tag transchoice
prende in automatico la variabile %count%
dal contesto
e la passa al traduttore. Questo meccanismo funziona solo
usando un segnaposto che segue lo schema %variabile%
.
Caution
La notazione %variabile%
dei segnaposti è obbligatoria quando si traduce in un
template Twig usando il tag.
Tip
Se si deve usare un simbolo di percentuale (%
) in una stringa, occorre
raddoppiarlo: {% trans %}Percent: %percent%%%{% endtrans %}
Si può anche specificare il dominio del messaggio e passare variabili aggiuntive:
{% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
{% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}
{% transchoice count with {'%name%': 'Fabien'} from "app" %}
{0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf] %name%, there are %count% apples
{% endtranschoice %}
I filtri trans
e transchoice
possono essere usati per tradurre testi
variabili ed espressioni complesse:
{{ message|trans }}
{{ message|transchoice(5) }}
{{ message|trans({'%name%': 'Fabien'}, "app") }}
{{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}
Tip
L’uso dei tag o dei filtri di traduzione ha il medesimo effetto, ma con una
sottile differenza: l’escape automatico si applica solo alla traduzione
che usa un filtro. In altre parole, se ci si deve assicurare che
il testo tradotto non abbia escape, occorre applicare il filtro
raw
dopo il filtro di traduzione:
{# il testo tra tag non subisce escape #}
{% trans %}
<h3>foo</h3>
{% endtrans %}
{% set message = '<h3>foo</h3>' %}
{# stringhe e variabili tradotte con filtro subiscono escape #}
{{ message|trans|raw }}
{{ '<h3>bar</h3>'|trans|raw }}
Tip
Si può impostare il dominio di un intero template Twig con un semplice tag:
{% trans_default_domain "app" %}
Notare che questo influenza solo in template attuale, non i template “inclusi” (per evitare effetti collaterali).
Template PHP¶
Il servizio di traduzione è accessibile nei template PHP attraverso
l’aiutante translator
:
<?php echo $view['translator']->trans('Symfony is great') ?>
<?php echo $view['translator']->transChoice(
'{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
10,
array('%count%' => 10)
) ?>
Sedi per le traduzioni e convenzioni sui nomi¶
Symfony cerca i file dei messaggi (ad esempio le traduzioni) in due sedi:
- la cartella
app/Resources/translations
; - la cartella
app/Resources/<nome bundle>/translations
; - la cartella
Resources/translations/
del bundle.
I posti sono elencati in ordine di priorità. Quindi, si possono sovrascrivere i messaggi di traduzione di un bundle in una qualsiasi delle due cartelle superiori.
Il meccanismo di priorità si basa sulle chiavi: occorre dichiarare solamente le chiavi da sovrascrivere in un file di messaggi a priorità superiore. Se una chiave non viene trovata in un file di messaggi, il traduttore si appoggerà automaticamente ai file di messaggi a priorità inferiore.
È importante anche il nome del file con le traduzioni: ogni file con i messaggi
deve essere nominato secondo il seguente schema: dominio.locale.caricatore
:
- dominio: Un modo opzionale per organizzare i messaggi in gruppi (ad esempio
admin
,navigation
o il predefinitomessages
, vedere “using-message-domains”); - locale: Il locale per cui sono state scritte le traduzioni (ad esempio
en_GB
,en
, ecc.); - caricatore: Come Symfony dovrebbe caricare e analizzare il file (ad esempio
xliff
,php
oyml
).
Il caricatore può essere il nome di un qualunque caricatore registrato. Per impostazione predefinita, Symfony fornisce i seguenti caricatori:
xliff
: file XLIFF;php
: file PHP;yml
: file YAML.
La scelta di quali caricatori utilizzare è interamente a carico dello sviluppatore ed è una questione
di gusti. L’opzione raccomandata è il formato xliff
.
Per altre opzioni, vedere component-translator-message-catalogs.
Note
È anche possibile memorizzare le traduzioni in una base dati o in qualsiasi altro mezzo,
fornendo una classe personalizzata che implementa
l’interfaccia Symfony\Component\Translation\Loader\LoaderInterface
.
Vedere dic-tags-translation-loader per maggiori informazioni.
Caution
Ogni volta che si crea una nuova risorsa di traduzione (o si installa un bundle che include risorse di traduzioni), assicurarsi di pulire la cache, in modo che Symfony possa rilevare le nuove risorse:
$ php app/console cache:clear
Fallback e locale predefinito¶
Ipotizzando che il locale dell’utente sia fr_FR
e che si stia traducendo la
chiave Symfony is great
. Per trovare la traduzione francese, Symfony
verifica le risorse di traduzione di vari locale:
- Prima, Symfony cerca la traduzione in una risorsa di traduzione
fr_FR
(p.e.messages.fr_FR.xfliff
); - Se non la trova, Symfony cerca una traduzione per una risorsa di traduzione
fr
(p.e.messages.fr.xliff
); - Se non trova nemeno questa, Symfony usa il parametro di configurazione
fallback
, che ha come valore predefinitoen
(vedere Configurazione).
New in version 2.6: La possibilità di scrivere nei log le traduzioni mancanti è stata introdotta in Symfony 2.6.
Note
Quando Symfony non trova una traduzione per il locale dato, aggiungerà la traduzione mancante al file di log. Per dettagli, vedere reference-framework-translator-logging.
Gestire il locale dell’utente¶
Il locale dell’utente attuale è memorizzato nella richiesta e accessibile
tramite l’oggetto request
:
use Symfony\Component\HttpFoundation\Request;
public function indexAction(Request $request)
{
$locale = $request->getLocale();
$request->setLocale('en_US');
}
Tip
Leggere /cookbook/session/locale_sticky_session per approfondimenti sull’argomento.
Vedere la sezione seguente, Il locale e gli URL, per impostare il locale tramite rotte.
Il locale e gli URL¶
Dal momento che si può memorizzare il locale dell’utente nella sessione, si può essere tentati
di utilizzare lo stesso URL per visualizzare una risorsa in più lingue in base
al locale dell’utente. Per esempio, http://www.example.com/contact
può
mostrare contenuti in inglese per un utente e in francese per un altro. Purtroppo
questo viola una fondamentale regola del web: un particolare URL deve restituire
la stessa risorsa indipendentemente dall’utente. Inoltre, quale
versione del contenuto dovrebbe essere indicizzata dai motori di ricerca?
Una politica migliore è quella di includere il locale nell’URL. Questo è completamente
supportato dal sistema delle rotte utilizzando il parametro speciale _locale
:
Quando si utilizza il parametro speciale _locale in una rotta, il locale corrispondente
verrà automaticamente impostato sulla richiesta e potrà essere recuperate tramite il metodo
:method:`Symfony\\Component\\HttpFoundation\\Request::getLocale`.
In altre parole, se un utente
visita l’URI /fr/contact
, il locale fr
viene impostato automaticamente
come locale per la richiesta corrente.
È ora possibile utilizzare il locale dell’utente per creare rotte ad altre pagine tradotte nell’applicazione.
Tip
Leggere /cookbook/routing/service_container_parameters per imparare come
evitare di inserire manualmente il requisito _locale
in ogni rotta.
Impostare un locale predefinito¶
Che fare se non si è in grado di determinare il locale dell’utente? Si può garantire che
un locale sia impostato a ogni richiesta, definendo un default_locale
per
il framework:
Tradurre i messaggi dei vincoli¶
Se si usano i vincoli di validazione dei form, la traduzione dei
messaggi di errore è facile: basta creare una risorsa di traduzione per
il dominio validators
.
Per iniziare, supponiamo di aver creato un oggetto PHP, necessario da qualche parte in un’applicazione:
// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;
class Author
{
public $name;
}
Aggiungere i vincoli tramite uno dei metodi supportati. Impostare l’opzione del messaggio
al testo sorgente della traduzione. Per esempio, per assicurarsi che la proprietà $name
non sia vuota, aggiungere il seguente:
Creare un file di traduzione sotto il catalogo validators
per i messaggi
dei vincoli, tipicamente nella cartella Resources/translations/
del
bundle.
Tradurre contenuti della base dati¶
La traduzione di contenuti della base dati andrebbe affidata a Doctrine, tramite l’estensione Translatable o il behavior Translatable (per PHP 5.4+). Per maggiori informazioni, vedere la documentazione di queste librerie.
Debug delle traduzioni¶
New in version 2.5: Il comando translation:debug
è stato introdotto in Symfony 2.5.
New in version 2.6: Prima di Symfony 2.6, questo comando si chiamava translation:debug
.
Durante la manutenzione di un bundle, si potrebbe usare o disabilitare un messaggio
di traduzioni, senza aggiornare tutti i cataloghi dei messaggi. Il comando translation:debug
aiuta a trovare questi messaggi di traduzioni mancanti o inutilizzati, per un
locale dato. Mostra una tabella con i risultati della traduzione del
messaggio nel locale fornito e il risultato quando viene usato il fallback.
Inoltre, mostra quando le traduzioni sono uguali all
traduzione fallback (potrebbe indicare che il messaggio non sia stato tradotto
correttamente).
Grazie agli estrattori di messaggi, il comando troverà il tag di traduzione o l’uso di filtri nei template Twig:
{% trans %}Symfony2 is great{% endtrans %}
{{ 'Symfony2 is great'|trans }}
{{ 'Symfony2 is great'|transchoice(1) }}
{% transchoice 1 %}Symfony2 is great{% endtranschoice %}
Individuerà anche i seguenti utilizzi di traduzione in template PHP:
$view['translator']->trans("Symfony2 is great");
$view['translator']->trans('Symfony2 is great');
Caution
Gli estrattori non sono in grado di ispezionare i messaggi tradoti fuori dai template, il che vuol dire che gli utilizzi di traduzioni in label di form o dentro a controllori non saranno individuati. Traduzioni dinamiche, che coinvolgano variabili o espressioni, non sono individuate nei template, il che vuol dire che questo esempio non sarà analizzato:
{% set message = 'Symfony2 is great' %}
{{ message|trans }}
Si supponga che il locale predefinito sia fr
e di aver configurato en
come locale di fallback
(vedere Configurazione e Fallback e locale predefinito su come configurarli).
Si supponga inoltre di aver già preparato alcune traduzioni per il locale fr
in un AcmeDemoBundle:
e per il locale en
:
Per individuare tutti i messaggi nel locale fr
per AcmeDemoBundle, eseguire:
$ php app/console debug:translation fr AcmeDemoBundle
Si otterrà questo output:
Indica che il messaggio Symfony2 is great
è inutilizzato, perché è stato tradotto,
ma viene usato.
Ora, se si traduce il messaggio in uno dei template, si otterrà questo output:
Lo stato è vuoto, che vuol dire che il messaggio è stato tradotto nel locale fr
e usato in un template.
Se si cancella il messaggio Symfony2 is great
dal file di traduzione per il locale fr
e
si esegue il comando, si otterrà:
Lo stato indica che il messaggio è mancante, perché non è tradotto nel locale fr
,
ma è usato in un template.
Inoltre, il messaggio nel locale fr
è uguale al messaggio nel locale en
.
Questo è caso particolare, perché il messaggio non tradotto ha lo stesso id della sua traduzione nel locale en
.
Se si copia il contenuto del file di traduzione del locale en
nel file di traduzione
del locale fr
e si esegue il comando, si otterrà:
Si può vedere che le traduzioni del messaggio sono identiche nei locale fr
ed en
, che
vuol dire che questo messaggio è stato probabilmente copiato da francese a inglese e forse ci si è dimenticati di tradurlo.
L’ispezione predefinita avviene su tutti i domini, ma si può specificare un singolo dominio:
$ php app/console debug:translation en AcmeDemoBundle --domain=messages
Quando i bundle hanno molti messaggi, è utile mostrare solo quelli non usati
oppure solo quelli mancanti, usando le opzioni --only-unused
o --only-missing
:
$ php app/console debug:translation en AcmeDemoBundle --only-unused
$ php app/console debug:translation en AcmeDemoBundle --only-missing
Riepilogo¶
Con il componente Translation di Symfony, la creazione e l’internazionalizzazione di applicazioni non è più un processo doloroso e si riduce solo a pochi semplici passi:
- Astrarre i messaggi dell’applicazione avvolgendoli utilizzando i metodi :method:`Symfony\\Component\\Translation\\Translator::trans` o :method:`Symfony\\Component\\Translation\\Translator::transChoice`; (vedere anche “/components/translation/usage”);
- Tradurre ogni messaggio in più locale creando dei file con i messaggi per la traduzione. Symfony scopre ed elabora ogni file perché i suoi nomi seguono una specifica convenzione;
- Gestire il locale dell’utente, che è memorizzato nella richiesta, ma può anche essere memorizzato nella sessione.