2013-12-10 16 views
11

Considera di scrivere una libreria PHP, che verrà pubblicata tramite Packagist o Pear. È rivolto agli sviluppatori peer che lo utilizzano in impostazioni arbitrarie.Come internazionalizzare una libreria di terze parti PHP

Questa libreria conterrà alcuni messaggi di stato determinati per il client. Come faccio a internazionalizzare questo codice, in modo che gli sviluppatori che utilizzano la libreria abbiano la massima libertà possibile per collegare il proprio metodo di localizzazione? Non voglio assumere nulla, specialmente non costringere il dev a usare gettext.

a lavorare su un esempio, prendiamo questa classe:

class Example { 

    protected $message = "I'd like to be translated in your client's language."; 

    public function callMe() { 
     return $this->message; 
    } 

    public function callMeToo($user) { 
     return sprintf('Hi %s, nice to meet you!', $user); 
    } 

} 

ci sono due problemi qui: Come faccio a contrassegnare il privato $message per la traduzione, e come posso permettere allo sviluppatore di localizzare la stringa all'interno callMeToo()?

Una possibilità (molto scomodo) sarebbe, a chiedere qualche metodo i18n nel costruttore, in questo modo:

public function __construct($i18n) { 
    $this->i18n = $i18n; 
    $this->message = $this->i18n($this->message); 
} 

public function callMeToo($user) { 
    return sprintf($this->i18n('Hi %s, nice to meet you!'), $user); 
} 

ma a caro prezzo sperare in una soluzione più elegante.

Modifica 1: Oltre alla sostituzione di stringa semplice, il campo di i18n è ampio. La premessa è che non voglio confezionare alcuna soluzione i18n con la mia libreria o costringere l'utente a sceglierne uno specifico per soddisfare il mio codice.

Quindi, come posso strutturare il mio codice per consentire la localizzazione migliore e più flessibile per diversi aspetti: traduzione di stringhe, formattazione di numeri e valute, date e orari, ...? Supponiamo che uno o l'altro appaiano come output dalla mia libreria. In quale posizione o interfaccia lo sviluppatore consumatore può collegare la sua soluzione di localizzazione?

risposta

7

La soluzione più utilizzata è un file di stringhe. Per esempio. come segue:

# library 
class Foo { 
    public function __construct($lang = 'en') { 
    $this->strings = require('path/to/langfile.' . $lang . '.php'); 
    $this->message = $this->strings['callMeToo']; 
    } 

    public function callMeToo($user) { 
    return sprintf($this->strings['callMeToo'], $user); 
    } 
} 

# strings file 
return Array(
    'callMeToo' => 'Hi %s, nice to meet you!' 
); 

È possibile, per evitare l'assegnazione $this->message, lavorare anche con getter magici:

# library again 
class Foo { 
    # … code from above 

    function __get($name) { 
    if(!empty($this->strings[$name])) { 
     return $this->strings[$name]; 
    } 

    return null; 
    } 
} 

È anche possibile aggiungere un metodo loadStrings che prende un array di stringhe da parte dell'utente e si fondono con la tua tabella degli archi interna.

Modifica 1: per ottenere maggiore flessibilità, cambierei leggermente l'approccio precedente. Vorrei aggiungere una funzione di traduzione come attributo oggetto e chiamarla sempre quando voglio localizzare una stringa. La funzione di default cerca semplicemente la stringa nella tabella delle stringhe e restituisce il valore stesso se non riesce a trovare una stringa localizzata, proprio come gettext. Lo sviluppatore che utilizza la tua libreria potrebbe quindi modificare la funzione in base alla propria fornita per eseguire un approccio di localizzazione completamente diverso.

Data la localizzazione non è un problema. L'impostazione della localizzazione è una questione di software la libreria viene utilizzato in. Il formato stesso è una stringa localizzata, ad esempio, $this->translate('%Y-%m-%d') sarebbe restituire una versione localizzata della stringa di formato della data.

Numero localizzazione è fatto impostando il locale giusto e utilizzando le funzioni come sprintf().

localizzazione valuta è un problema, però.Penso che l'approccio migliore sarebbe quello di aggiungere una funzione di traduzione di valuta (e, forse per una migliore flessibilità, un'altra funzione di formattazione numerica) che uno sviluppatore potrebbe sovrascrivere se vuole cambiare il formato della valuta. In alternativa puoi anche implementare stringhe di formato per le valute. Ad esempio %CUR %.02f - in questo esempio sostituirai %CUR con il simbolo della valuta. Anche i simboli di valuta sono stringhe localizzate.

Edit 2: Se non si desidera utilizzare setlocale devi fare un sacco di lavoro ... In pratica dovete riscrivere strftime() e sprintf() per ottenere date localizzate e numeri. Certo che è possibile, ma molto lavoro.

+0

Grazie, interessante. Fondamentalmente, il suggerito 'loadStrings' sembra essere una buona partita per le mie esigenze. Conosci qualche progetto in natura, come lo affrontano? (Zend è tuttavia esentato, hanno la loro libreria i18n.) – Boldewyn

+0

@Boldewyn Conosco un paio di progetti closed source che gestiscono questo modo, ma devo riflettere su un progetto open source. La maggior parte non sono affatto localizzati e se lo sono, usano gettext ... – ckruse

+0

Sì, questo è esattamente il mio problema, purtroppo ... – Boldewyn

2

L'approccio di base consiste nel fornire al consumatore un metodo per definire una mappatura. Può assumere qualsiasi forma, purché l'utente possa definire una mappatura biiettiva.

Per esempio, Mantis Bug Tracker utilizza una semplice globals file:

<?php 
    require_once "strings_$language.txt"; 
    echo $s_actiongroup_menu_move; 

Il loro metodo è semplice ma funziona bene. Avvolgerlo in una classe, se si preferisce:

<?php 
    $translator = new Translator(Translator::ENGLISH); // or make it a singleton 
    echo $translator->translate('actiongroup_menu_move'); 

utilizzare un file XML, invece, o un file INI o un file CSV ... qualsiasi formato di vostro gradimento, in effetti.


Rispondere le modifiche successive/commenti

Sì, quanto sopra non si discosta molto da altre soluzioni. Ma credo che ci sia poco altro da dire:

  • traduzione può essere raggiunto solo attraverso la sostituzione di stringhe (la mappatura può richiedere un numero infinito di forme)
  • numero di formattazione e le date è affar tuo. E 'responsabilità del livello di presentazione, e si dovrebbe semplicemente restituire numeri grezzi (o DateTime s o timestamp), (a meno che molto lo scopo della tua libreria è la localizzazione;)
+0

Grazie per aver risposto, ma sono particolarmente interessato alle conseguenze per gli utenti della mia libreria lungo il percorso. Quali sono le insidie ​​che queste soluzioni potrebbero creare? A parte questo, non riesco a vedere come il tuo concetto è diverso da quello che ho fornito nella domanda o @ckruse già menzionato. Aggiungerò anche la domanda per estendere ad altri problemi i18n. – Boldewyn

2

C'è un problema principale qui. Non vuoi rendere il codice come è adesso nella tua domanda per l'internazionalizzazione.

Lasciatemi spiegare. Il traduttore principale è probabilmente un programmatore. Il secondo e il terzo potrebbero essere, ma poi lo si vuole tradurre in qualsiasi lingua, anche per i non programmatori . Questo dovrebbe essere facile per i non programmatori. La caccia attraverso classi, funzioni, ecc. Per i non programmatori non è assolutamente accettabile.

Quindi propongo questo: mantieni le tue frasi di origine (inglese) in un formato agnostico , che sia facile da capire per tutti. Potrebbe trattarsi di un file xml, di un database o di qualsiasi altro modulo che si vede adattato. Quindi usa le tue traduzioni dove ne hai bisogno. Si può fare come:

class Example { 
    // Fetch them as you prefer and store them in $messages. 
    protected $messages = array(
    'en' => array(
     "message" => "I'd like to be translated in your client's language.", 
     "greeting" => "Hi %s, nice to meet you!" 
    ) 
    ); 

    public function __construct($lang = 'en') { 
    $this->lang = $lang; 
    } 

    protected function get($key, $args = null) { 
    // Store the string 
    $message = $this->messages[$this->lang][$key]; 
    if ($args == null) 
     return $this->translator($message); 
    else { 
     $string = $this->translator($message); 
     // Merge the two arrays so they can be passed as values 
     $sprintf_args = array_merge(array($string), $args); 
     return call_user_func_array('sprintf', $sprintf_args); 
     } 
    } 

    public function callMe() { 
    return $this->get("message"); 
    } 

    public function callMeToo($user) { 
    return $this->get("greeting", $user); 
    } 
} 

Inoltre, se si desidera utilizzare un small translation script I did, è possibile semplificare esso, inoltre. Usa un database, quindi potrebbe non avere così tanta flessibilità come stai cercando. È necessario iniettarlo e la lingua è impostata nell'inizializzazione. Si noti che il testo viene automaticamente aggiunto al database se non è presente.

class Example { 
    protected $translator; 

    // Translator already knows the language to translate the text to 
    public function __construct($Translator) { 
    $this->translator = $Translator; 
    } 

    public function callMe() { 
    return $this->translator("I'd like to be translated in your client's language."); 
    } 

    public function callMeToo($user) { 
    return sprintf($this->translator("Hi %s, nice to meet you!"), $user)); 
    } 
} 

Potrebbe essere facilmente modificato per utilizzare un file XML o qualsiasi altra fonte per le stringhe tradotte.

Note per il secondo metodo:

  • questo è diverso rispetto la soluzione proposta, in quanto sta facendo il lavoro in uscita, piuttosto che nella inizializzazione, quindi nessun bisogno di tenere traccia di ogni stringa.

  • È sufficiente scrivere le frasi una sola volta, in inglese. La classe che ho scritto la inserirà nel database a condizione che sia inizializzata correttamente, rendendo il tuo codice estremamente ASCIUTTO. Questo è esattamente il motivo per cui l'ho iniziato, invece di usare semplicemente gettext (e la ridicola dimensione di gettext per le mie semplici esigenze).

  • Contro: è una vecchia classe. Non sapevo molto allora. Ora cambierei un paio di cose: creare un campo language, anziché en, es, ecc., Lanciando alcune eccezioni qua e là e caricando alcuni dei test che ho fatto.

+0

Grazie per la risposta. Ho anche guardato il codice su Github. Sfortunatamente questa soluzione non funziona per me. Voglio limitare il più possibile le mie esigenze, consentendo comunque all'utente consumatore di localizzare completamente ciò che viene emesso dal mio codice.Ho anche aggiornato la domanda per includere aspetti come la formattazione della data. Temo che sia molto più complicato che tradurre in output o durante l'inizializzazione. – Boldewyn

+0

Con il tuo aggiornamento, penso che sia troppo ampio per SO in realtà. O il codice richiederebbe centinaia di righe o è più una domanda concettuale che è meglio chiedere in [programmatori] (http://programmers.stackexchange.com/). Comunque, sentiti libero di estendere (e spingere indietro a Github se vuoi) la classe che ho indicato per gestire meglio la localizzazione. –

+0

Un'altra domanda interessante è, come hai gestito la valuta? Dato che è variabile nel tempo, è necessario utilizzare un'API esterna, quindi diventa sempre più incongruo fare tutti i tuoi requeriments e non sarà * semplice *. –

Problemi correlati