2013-12-10 4 views
11

Sie sollten eine PHP-Bibliothek schreiben, die über Packagist oder Pear veröffentlicht wird. Es richtet sich an Peer-Entwickler, die es in beliebigen Einstellungen verwenden.So können Sie eine PHP-Drittanbieterbibliothek internationalisieren

Diese Bibliothek enthält einige Statusmeldungen, die für den Client festgelegt wurden. Wie internationalisiere ich diesen Code, damit die Entwickler, die die Bibliothek verwenden, die größtmögliche Freiheit haben, ihre eigene Lokalisierungsmethode zu verwenden? Ich möchte nichts annehmen, besonders nicht den Entwickler zwingen, gettext zu verwenden.

an einem Beispiel zu umgehen, lassen Sie uns diese Klasse nehmen:

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); 
    } 

} 

Es gibt zwei Probleme: Wie kann ich die private $message für die Übersetzung zu markieren, und wie lasse ich die Entwickler die Zeichenfolge innerhalb lokalisieren callMeToo()?

One (sehr unbequem) Option wäre, für einige i18n Methode im Konstruktor zu fragen, wie so:

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); 
} 

aber ich hoffe, einen hohen Preis für eine elegantere Lösung.

Edit 1: Abgesehen von der einfachen String-Substitution ist das Feld von i18n breit. Die Prämisse ist, dass ich keine i18n-Lösung mit meiner Bibliothek packen möchte oder den Benutzer zwinge, einen spezifisch für meinen Code zu wählen.

Wie kann ich dann meinen Code strukturieren, um die beste und flexibelste Lokalisierung für verschiedene Aspekte zu ermöglichen: Stringübersetzung, Zahlen- und Währungsformatierung, Datum und Uhrzeit, ...? Angenommen, der eine oder der andere erscheint als Ausgabe aus meiner Bibliothek. An welcher Stelle oder Schnittstelle kann der Entwickler ihre Lokalisierungslösung einstecken?

Antwort

7

Die am häufigsten verwendete Lösung ist eine Zeichenkettendatei. Z.B. wie folgt vor:

# 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!' 
); 

können Sie die $this->message Zuordnung zu vermeiden, auch mit Magie Getter arbeiten:

# library again 
class Foo { 
    # … code from above 

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

    return null; 
    } 
} 

Sie können sogar eine loadStrings Methode hinzufügen, die ein Array von Strings vom Benutzer nimmt und fusionieren es mit Ihrer internen Zeichenkettentabelle.

Edit 1: Um mehr Flexibilität zu erreichen, würde ich den obigen Ansatz ein wenig ändern. Ich würde eine Übersetzungsfunktion als Objektattribut hinzufügen und diese immer aufrufen, wenn ich eine Zeichenfolge lokalisieren möchte. Die Standardfunktion sucht nur die Zeichenkette in der Zeichenkettentabelle und gibt den Wert selbst zurück, wenn sie keine lokalisierte Zeichenkette finden kann, genau wie gettext. Der Entwickler, der Ihre Bibliothek verwendet, könnte dann die Funktion auf seine eigene ändern, um einen völlig anderen Lokalisierungsansatz zu erreichen.

Datumslokalisierung ist kein Problem. Das Festlegen der Ländereinstellung hängt von der Software ab, in der die Bibliothek verwendet wird. Das Format selbst ist eine lokalisierte Zeichenfolge, z. $this->translate('%Y-%m-%d') würde eine lokalisierte Version der Datumsformat-Zeichenfolge zurückgeben.

Die Lokalisierung der Nummer erfolgt durch Einstellen der richtigen Ländereinstellung und Verwendung von Funktionen wie sprintf().

Währung Lokalisierung ist jedoch ein Problem.Ich denke, der beste Ansatz wäre es, eine Währungsumrechnungsfunktion hinzuzufügen (und vielleicht auch, um die Flexibilität zu erhöhen, eine andere Funktion zur Formatierung von Zahlen), die ein Entwickler überschreiben könnte, wenn er das Währungsformat ändern möchte. Alternativ könnten Sie auch Formatstrings für Währungen implementieren. Zum Beispiel %CUR %.02f - in diesem Beispiel würden Sie %CUR durch das Währungssymbol ersetzen. Währungssymbole selbst sind ebenfalls lokalisierte Zeichenfolgen.

Edit 2: Wenn Sie nicht setlocale verwenden wollen, müssen Sie eine Menge Arbeit erledigen ... im Grunde müssen Sie strftime() und sprintf() umschreiben, um lokalisierte Daten und Zahlen zu erreichen. Natürlich möglich, aber viel Arbeit.

+0

Danke, interessant. Im Grunde scheinen mir die vorgeschlagenen 'loadStrings' gut zu meinen Bedürfnissen zu passen. Kennst du irgendwelche Projekte in der Wildnis, wie gehen sie damit um? (Zend ist allerdings ausgenommen, sie haben ihre eigene i18n-Bibliothek.) – Boldewyn

+0

@Boldewyn Ich kenne ein paar Closed-Source-Projekte, die auf diese Weise damit umgehen, aber ich muss über ein Open-Source-Projekt nachdenken. Die meisten sind überhaupt nicht lokalisiert und wenn sie sind, benutzen sie gettext ... – ckruse

+0

Ja, das ist genau mein Problem, leider ... – Boldewyn

2

Der grundlegende Ansatz besteht darin, dem Verbraucher eine Methode zum Definieren eines Mappings bereitzustellen. Es kann jede Form annehmen, solange der Benutzer ein bijektives Mapping definieren kann.

Zum Beispiel Mantis Bug Tracker verwendet ein einfaches globals file:

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

Ihre Methode ist einfach, aber funktioniert gut. Wickeln Sie es in einer Klasse, wenn Sie bevorzugen:

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

Verwenden Sie eine XML-Datei stattdessen oder eine INI-Datei oder eine CSV-Datei ... was auch immer Format von einer Vielzahl von Designs, in der Tat.


beantworten Ihre spätere Änderungen/Kommentare

Ja, wird die oben nicht viel unterscheiden sich von anderen Lösungen. Aber ich glaube, es gibt kaum etwas anderes gesagt werden:

  • Übersetzung kann nur durch String-Ersetzung erreicht werden (die Abbildung eine unendliche Anzahl von Formen annehmen kann)
  • Formatierung Nummer und Daten ist keine Ihrer Anliegen. Es ist die Verantwortung der Präsentationsschicht, und Sie sollten nur rohe Zahlen (oder DateTime s oder Zeitstempel) zurückkehren, (es sei denn, Ihre Bibliothek Zweck Lokalisierung ist;)
+0

Danke für die Antwort, aber ich bin besonders interessiert an den Konsequenzen für Benutzer meiner Bibliothek auf dem Weg. Was sind die Fallstricke, die diese Lösungen verursachen könnten? Abgesehen davon, sehe ich nicht, wie sich Ihr Konzept von dem unterscheidet, was ich in der bereits erwähnten Frage oder @ckruse angegeben habe. Ich werde auch die Frage aktualisieren, um weitere i18n-Probleme zu erweitern. – Boldewyn

2

Es gibt ein Hauptproblem hier. Sie möchten den Code nicht so machen, wie er gerade in Ihrer Frage für die Internationalisierung ist.

Lassen Sie mich erklären. Der Hauptübersetzer ist wahrscheinlich ein Programmierer. Die zweite und dritte könnte sein, aber dann möchten Sie es in jede Sprache übersetzen, auch für Nicht-Programmierer. Dies sollte für Nicht-Programmierer einfach sein. Jagen durch Klassen, Funktionen usw. für Nicht-Programmierer ist definitiv nicht in Ordnung.

Also schlage ich Folgendes vor: behalte deine Quellsätze (englisch) in einem agnostischen Format, das für jeden einfach zu verstehen ist. Dies kann eine XML-Datei, eine Datenbank oder jede andere Form sein, die Sie sehen. Dann verwenden Sie Ihre Übersetzungen wo Sie sie brauchen. Sie können es gerne tun:

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); 
    } 
} 

Wenn Sie darüber hinaus eine small translation script I did verwenden möchten, können Sie es weiterhin vereinfachen kann. Es verwendet eine Datenbank, sodass es möglicherweise nicht so flexibel ist, wie Sie es wünschen. Sie müssen es injizieren und die Sprache wird in der Initialisierung festgelegt. Beachten Sie, dass der Text automatisch zur Datenbank hinzugefügt wird, wenn er nicht vorhanden ist.

Es könnte leicht geändert werden, um eine XML-Datei oder eine andere Quelle für übersetzte Zeichenfolgen zu verwenden.

Hinweise für die zweite Methode:

  • Dies ist anders als Ihre vorgeschlagene Lösung, da sie die Arbeit in der Ausgabe tun, anstatt bei der Initialisierung, so dass keine Notwendigkeit des Überblick zu behalten von jeder Schnur.

  • Sie müssen Ihre Sätze nur einmal auf Englisch schreiben. Die Klasse, die ich geschrieben habe, wird sie in die Datenbank stellen, vorausgesetzt, sie ist korrekt initialisiert, was Ihren Code extrem DRY macht. Genau deshalb habe ich es angefangen, anstatt nur gettext zu verwenden (und die lächerliche Größe von gettext für meine einfachen Anforderungen).

  • Con: es ist eine alte Klasse. Ich wusste damals nicht viel. Jetzt würde ich ein paar Dinge ändern: ein language Feld anstatt en, es usw. machen, einige Ausnahmen hier und dort werfen und einige der Tests hochladen, die ich gemacht habe.

+0

Danke für die Antwort. Ich sah mir auch den Code auf Github an. Leider funktioniert diese Lösung nicht für mich. Ich möchte meine eigenen Anforderungen so weit wie möglich einschränken und gleichzeitig dem Benutzer die Möglichkeit geben, das, was von meinem Code ausgegeben wird, vollständig zu lokalisieren.Ich habe die Frage auch um Dinge wie Datumsformatierung aktualisiert. Ich fürchte, es ist viel komplizierter, als wenn es bei der Ausgabe oder während der Initialisierung übersetzt wird. – Boldewyn

+0

Mit Ihrem Update, ich denke, es ist zu weit für SO tatsächlich. Entweder würde der Code Hunderte von Zeilen benötigen oder es ist eher eine konzeptionelle Frage, die besser in [Programmierer] (http://programmers.stackexchange.com/) gestellt wird. Aber zögern Sie nicht, die Klasse, auf die ich hingewiesen habe, um eine bessere Lokalisierung zu erreichen, zu erweitern (und zu Github zurückzuschicken, wenn Sie möchten). –

+0

Eine andere interessante Frage ist, wie hast du mit der Währung umgehen können? Da dies mit der Zeit variiert, müssten Sie eine externe API verwenden, so dass es immer unwichtiger wird, alle Ihre Anfragen zu bearbeiten, und es wird nicht * einfach * sein. –

Verwandte Themen