() translation by (you can also view the original English article)



In meinem Tutorial Lokalisierung mit I18n für das Erstellen Ihres Startups mit PHP-Serie habe ich spanischen Beispielcode erstellt, indem Sie Textzeichenfolgen in Google Translate ausschneiden und einfügen. Ich begann mich zu fragen, ob ich die Google Translate API mit dem I18n-Ressourcenextraktionsskript des Yii Framework integrieren könnte, um die Übersetzung für eine Reihe von Ländern zu automatisieren. Ich habe im Yii-Forum eine Feature-Anfrage gestellt und dann entschieden, ob ich die Funktion selbst erstellen kann.
In diesem Tutorial werde ich Sie durch meine Erweiterungen des Yii I18n-Extraktskripts führen, die genau dies tun. Ich zeige Ihnen, wie Sie meine Startanwendung Meeting Planner in eine Handvoll Sprachen übersetzen.
Beachten Sie, dass Google Translate nicht perfekt ist und sich nicht mit Problemen im Zusammenhang mit Zeit- und Datumsformaten und Währungen befasst. Für eine schnelle und kostengünstige (kostenlose) Möglichkeit, Standardübersetzungen für Ihre Webanwendung in mehr als 50 Sprachen zu erstellen, ist dies eine ideale Lösung.
Zum Beispiel ist hier ein auffälligerer Fehler aufgefallen, auf den ich beim Testen gestoßen bin - zum Glück sind dies selten:
1 |
'{nFormatted} TB' => '{nFormatted} tuberculosis', |
Wenn Sie einen professionelleren Ansatz benötigen, wies mich ein Freund auf einen kostenpflichtigen Dienst zur Verwaltung der Lokalisierung in Apps, Transifex, hin. Ich habe es selbst nicht überprüft, aber es sieht faszinierend aus.
Mit Google Translate arbeiten
Welche Sprachen unterstützt es?
Google Translate bietet Übersetzungsdienste für 64 Sprachen an, darunter Schwedisch, aber leider nicht Schwedisch.
Hier finden Sie eine Auswahl der von Google unterstützten Sprachen. Die vollständige Liste finden Sie hier:



Mit der Google Translate-API sprechen
Ich habe zwei Composer-Bibliotheken für die Arbeit mit der Google Translator-API in PHP gefunden:
- Die Google Translate-Bibliothek von Levan Velijanashvili
- Travis Tillotsons Google-Übersetzungsclient
Ich habe Velijanashvili zuerst gefunden, es ist also das, was ich in diesem Tutorial verwendet habe. Es nutzt Google Translate über seine kostenlose RESTful-Weboberfläche, sodass Sie keinen API-Schlüssel benötigen. Wenn Sie jedoch über eine große Ressourcenbibliothek verfügen oder viele Sprachen übersetzen möchten, sollten Sie Tillotson's integrieren, da es vollständig über Google Keys in den kostenpflichtigen Dienst von Google Translate integriert ist.
Für dieses Tutorial baue ich auf die Codebase der PHP-Serie zum Erstellen Ihres Startups auf. Um die Google Translate-Bibliothek von Velijanashvili zu installieren, geben Sie einfach Folgendes ein:
1 |
composer require stichoza/google-translate-php |
Hier ist ein Beispielcode, um aus dem Englischen ins Spanische zu übersetzen:
1 |
use Stichoza\Google\GoogleTranslate; |
2 |
echo GoogleTranslate::staticTranslate('hello world', "en", "es"). "\n"; |
Es sollte ausgeben:
hola mundo
Extending Yii2's I18n Message/Extract Script
Wie der I18n-Support von Yii2 heute funktioniert
Zu diesem Zeitpunkt möchten Sie vielleicht das Lernprogramm Lokalisierung mit I18n überprüfen, in dem erläutert wird, wie Sie Nachrichtenzeichenfolgen für Ihre erforderlichen Sprachübersetzungen extrahieren.
Sie können den Gii-Code-Generator von Yii verwenden, um Modelle und CRUD-Code zu generieren, der automatisch die Unterstützung von I18n integriert. Jeder String im Code wird durch einen Funktionsaufruf wie ersetzt Yii::t('category','text string to translate');
Yii bietet einen Konsolenbefehl message / extract, der alle diese Funktionsaufrufe in Ihrer Anwendung findet und eine Verzeichnisstruktur der Dateien nach Sprache und Kategorie für die Übersetzung all dieser Zeichenfolgen erstellt.
Hier eine Beispiel-String-Datei für Deutsch:
1 |
<?php
|
2 |
/**
|
3 |
* Message translations.
|
4 |
*
|
5 |
* This file is automatically generated by 'yii translate' command.
|
6 |
* It contains the localizable messages extracted from source code.
|
7 |
* You may modify this file by translating the extracted messages.
|
8 |
*
|
9 |
* Each array element represents the translation (value) of a message (key).
|
10 |
* If the value is empty, the message is considered as not translated.
|
11 |
* Messages that no longer need translation will have their translations
|
12 |
* enclosed between a pair of '@@' marks.
|
13 |
*
|
14 |
* Message string can be used with plural forms format. Check i18n section
|
15 |
* of the guide for details.
|
16 |
*
|
17 |
* NOTE: this file must be saved in UTF-8 encoding.
|
18 |
*/
|
19 |
return [ |
20 |
'Get started with Yii' => 'Machen Sie sich mit Yii begonnen', |
21 |
'Heading' => 'Überschrift', |
22 |
'My Yii Application' => 'Meine Yii-Anwendung', |
23 |
'Yii Documentation' => 'Yii Dokumentation', |
24 |
'Yii Extensions' => 'Yü -Erweiterungen', |
25 |
'Yii Forum' => 'Yii Forum', |
26 |
'Are you sure you want to delete this item?' => 'Sind Sie sicher, Sie wollen diesen Inhalt löschen ?', |
27 |
'Congratulations!' => 'Herzlichen Glückwunsch!', |
28 |
'Create' => 'schaffen', |
29 |
'Create {modelClass}' => 'schaffen {modelClass}', |
30 |
'Created At' => 'Erstellt am', |
31 |
'Delete' => 'löschen', |
32 |
'ID' => 'Identifikation', |
Hier ein Beispiel für die Verzeichnispfade:



Nachricht erweitern / extrahieren für Google Translate
Ich entschied mich für das Erstellen eines Ersatzskripts mit dem Namen message/google_extract
, mit dem Google Translate aufgerufen wird, wenn ein String übersetzt werden muss.
Verhindern, dass beschädigter Code Token übersetzt
Da I18n Parametermarken in geschwungenen geschweiften Klammern für Variablenwerte integriert, sind mir sofort einige Probleme aufgefallen. Hier sind zum Beispiel einige I18n-Zeichenfolgen, die Token und verschachtelte Token enthalten:
1 |
'Create {modelClass}' |
2 |
'Registered at {0, date, MMMM dd, YYYY HH:mm} from {1}' |
3 |
'{0, date, MMMM dd, YYYY HH:mm}' |
4 |
'{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' |
Die Google Translate-API verfügt nicht über einen Parameter zum Ignorieren von Token wie diesen in diesem Formular. Wir können diese jedoch nicht übersetzen, da sie Variablennamen und Formatzeichenfolgen im Code entsprechen.
Es schien mir nicht, dass ein regulärer Ausdruck das lösen könnte, wenn übersetzbare Zeichenketten und Marken gemeinsam vorhanden waren. Es ist wahrscheinlich, dass die Leser eine effizientere Lösung haben, als ich für die Lösung dieses Problems gefunden habe. Wenn Ihnen eines klar ist, schreiben Sie es in die Kommentare.
Ich habe mich entschieden, die Saiten nach Zeichen zu scannen und die Verschachtelung von geschweiften Klammern zu verfolgen. Ich gebe als erster zu, dass es vielleicht einen besseren Weg gibt. Hier ist meine Funktion parse_safe_translate()
:
1 |
/*
|
2 |
* parses a string into an array
|
3 |
* splitting by any curly bracket segments
|
4 |
* including nested curly brackets
|
5 |
*/
|
6 |
public function parse_safe_translate($s) { |
7 |
$debug = false; |
8 |
$result = array(); |
9 |
$start=0; |
10 |
$nest =0; |
11 |
$ptr_first_curly=0; |
12 |
$total_len = strlen($s); |
13 |
for($i=0; $i<$total_len; $i++) { |
14 |
if ($s[$i]=='{') { |
15 |
// found left curly
|
16 |
if ($nest==0) { |
17 |
// it was the first one, nothing is nested yet
|
18 |
$ptr_first_curly=$i; |
19 |
}
|
20 |
// increment nesting
|
21 |
$nest+=1; |
22 |
} elseif ($s[$i]=='}') { |
23 |
// found right curly
|
24 |
// reduce nesting
|
25 |
$nest-=1; |
26 |
if ($nest==0) { |
27 |
// end of nesting
|
28 |
if ($ptr_first_curly-$start>=0) { |
29 |
// push string leading up to first left curly
|
30 |
$prefix = substr ( $s , $start , $ptr_first_curly-$start); |
31 |
if (strlen($prefix)>0) { |
32 |
array_push($result,$prefix); |
33 |
}
|
34 |
}
|
35 |
// push (possibly nested) curly string
|
36 |
$suffix=substr ( $s , $ptr_first_curly , $i-$ptr_first_curly+1); |
37 |
if (strlen($suffix)>0) { |
38 |
array_push($result,$suffix); |
39 |
}
|
40 |
if ($debug) { |
41 |
echo '|'.substr ( $s , $start , $ptr_first_curly-$start-1)."|\n"; |
42 |
echo '|'.substr ( $s , $ptr_first_curly , $i-$ptr_first_curly+1)."|\n"; |
43 |
}
|
44 |
$start=$i+1; |
45 |
$ptr_first_curly=0; |
46 |
if ($debug) { |
47 |
echo 'next start: '.$start."\n"; |
48 |
}
|
49 |
}
|
50 |
}
|
51 |
}
|
52 |
$suffix = substr ( $s , $start , $total_len-$start); |
53 |
if ($debug) { |
54 |
echo 'Start:'.$start."\n"; |
55 |
echo 'Pfc:'.$ptr_first_curly."\n"; |
56 |
echo $suffix."\n"; |
57 |
}
|
58 |
if (strlen($suffix)>0) { |
59 |
array_push($result,substr ( $s , $start , $total_len-$start)); |
60 |
}
|
61 |
return $result; |
62 |
}
|
Es konvertiert einen I18n-String in ein Array von Elementen, die in übersetzbare und nicht übersetzbare Elemente unterteilt sind. Zum Beispiel dieser Code:
1 |
$message='The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.'; |
2 |
print_r($this->parse_safe_translate($message)); |
Erzeugt diese Ausgabe:
1 |
Array
|
2 |
(
|
3 |
[0] => The image " |
4 |
[1] => {file} |
5 |
[2] => " is too large. The height cannot be larger than |
6 |
[3] => {limit, number} |
7 |
[4] => |
8 |
[5] => {limit, plural, one{pixel} other{pixels}} |
9 |
[6] => . |
10 |
)
|
Immer wenn der Extraktionsprozess eine zu übersetzende neue Zeichenfolge identifiziert, teilt er die Zeichenfolge in diese Teile auf und ruft die Google Translate-API für jede übersetzbare Zeichenfolge auf, z. Eine, die nicht mit einer linken geschweiften Klammer beginnt. Anschließend werden diese Übersetzungen mit den tokenisierten Zeichenfolgen zu einer einzigen Zeichenfolge zusammengefasst.
Übersetzung eines Token-Strings mit Google Translate
Hier ist die Funktion getGoogleTranslation()
für einen String und eine Zielsprache. Die Ausgangssprache wird durch Yii::$app->language
festgelegt.
1 |
public function getGoogleTranslation($message,$language) { |
2 |
$arr_parts=$this->parse_safe_translate($message); |
3 |
$translation=''; |
4 |
foreach ($arr_parts as $str) { |
5 |
if (!stristr($str,'{')) { |
6 |
if (strlen($translation)>0 and substr($translation,-1)=='}') $translation.=' '; |
7 |
$translation.=GoogleTranslate::staticTranslate($str, Yii::$app->language, $language); |
8 |
} else { |
9 |
// add space prefix unless it's first
|
10 |
if (strlen($translation)>0) |
11 |
$translation.=' '.$str; |
12 |
else
|
13 |
$translation.=$str; |
14 |
}
|
15 |
}
|
16 |
print_r($translation); |
17 |
return $translation; |
18 |
}
|
Ich fand, dass die Kombination dieser Ansätze in meinen Tests fast perfekt funktionierte.
Anpassen von Yiis Nachricht / Auszug
Die I18n-Implementierung von Yii unterstützt das Laden von Ressourcenzeichenfolgen aus .PO-Dateien, .PHP-Dateien (die ich verwende) und der Datenbank. Für dieses Tutorial habe ich Message / Extract für die PHP-Dateigeneration angepasst.
Ich habe die message/ extract
in /console/controllers/TranslateController.php
kopiert und erweitert. Aufgrund der strengen Regeln von PHP 5.6.x habe ich die Funktionsnamen für saveMessagesToPHP
in saveMessagesToPHPEnhanced
und saveMessagesCategoryToPHP
in saveMessagesCategoryToPHPEnhanced
geändert.
Hier ist die saveMessagesToPHPEnhanced()
- Funktion:
1 |
/**
|
2 |
* Writes messages into PHP files
|
3 |
*
|
4 |
* @param array $messages
|
5 |
* @param string $dirName name of the directory to write to
|
6 |
* @param boolean $overwrite if existing file should be overwritten without backup
|
7 |
* @param boolean $removeUnused if obsolete translations should be removed
|
8 |
* @param boolean $sort if translations should be sorted
|
9 |
*/
|
10 |
protected function saveMessagesToPHPEnhanced($messages, $dirName, $overwrite, $removeUnused, $sort,$language) |
11 |
{
|
12 |
foreach ($messages as $category => $msgs) { |
13 |
$file = str_replace("\\", '/', "$dirName/$category.php"); |
14 |
$path = dirname($file); |
15 |
FileHelper::createDirectory($path); |
16 |
$msgs = array_values(array_unique($msgs)); |
17 |
$coloredFileName = Console::ansiFormat($file, [Console::FG_CYAN]); |
18 |
$this->stdout("Saving messages to $coloredFileName...\n"); |
19 |
$this->saveMessagesCategoryToPHPEnhanced($msgs, $file, $overwrite, $removeUnused, $sort, $category,$language); |
20 |
}
|
21 |
}
|
Es ruft die saveMessagesCategoryToPHP
-Funktion auf:
1 |
/**
|
2 |
* Writes category messages into PHP file
|
3 |
*
|
4 |
* @param array $messages
|
5 |
* @param string $fileName name of the file to write to
|
6 |
* @param boolean $overwrite if existing file should be overwritten without backup
|
7 |
* @param boolean $removeUnused if obsolete translations should be removed
|
8 |
* @param boolean $sort if translations should be sorted
|
9 |
* @param boolean $language language to translate to
|
10 |
* @param boolean $force google translate
|
11 |
* @param string $category message category
|
12 |
*/
|
13 |
protected function saveMessagesCategoryToPHPEnhanced($messages, $fileName, $overwrite, $removeUnused, $sort, $category,$language,$force=true) |
14 |
{
|
15 |
if (is_file($fileName)) { |
16 |
$existingMessages = require($fileName); |
17 |
sort($messages); |
18 |
ksort($existingMessages); |
19 |
if (!$force) { |
20 |
if (array_keys($existingMessages) == $messages) { |
21 |
$this->stdout("Nothing new in \"$category\" category... Nothing to save.\n\n", Console::FG_GREEN); |
22 |
return; |
23 |
}
|
24 |
}
|
25 |
$merged = []; |
26 |
$untranslated = []; |
27 |
foreach ($messages as $message) { |
28 |
if (array_key_exists($message, $existingMessages) && strlen($existingMessages[$message]) > 0) { |
29 |
$merged[$message] = $existingMessages[$message]; |
30 |
} else { |
31 |
$untranslated[] = $message; |
32 |
}
|
33 |
}
|
34 |
ksort($merged); |
35 |
sort($untranslated); |
36 |
$todo = []; |
37 |
foreach ($untranslated as $message) { |
38 |
$todo[$message] = $this->getGoogleTranslation($message,$language); |
39 |
}
|
40 |
ksort($existingMessages); |
41 |
foreach ($existingMessages as $message => $translation) { |
42 |
if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) { |
43 |
if (!empty($translation) && strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2, 2) === 0) { |
44 |
$todo[$message] = $translation; |
45 |
} else { |
46 |
$todo[$message] = '@@' . $translation . '@@'; |
47 |
}
|
48 |
}
|
49 |
}
|
50 |
|
51 |
$merged = array_merge($todo, $merged); |
52 |
if ($sort) { |
53 |
ksort($merged); |
54 |
}
|
55 |
if (false === $overwrite) { |
56 |
$fileName .= '.merged'; |
57 |
}
|
58 |
$this->stdout("Translation merged.\n"); |
59 |
} else { |
60 |
$merged = []; |
61 |
foreach ($messages as $message) { |
62 |
$merged[$message] = ''; |
63 |
}
|
64 |
ksort($merged); |
65 |
}
|
66 |
|
67 |
|
68 |
$array = VarDumper::export($merged); |
69 |
$content = <<<EOD |
70 |
<?php |
71 |
/**
|
72 |
* Message translations.
|
73 |
*
|
74 |
* This file is automatically generated by 'yii {$this->id}' command.
|
75 |
* It contains the localizable messages extracted from source code.
|
76 |
* You may modify this file by translating the extracted messages.
|
77 |
*
|
78 |
* Each array element represents the translation (value) of a message (key).
|
79 |
* If the value is empty, the message is considered as not translated.
|
80 |
* Messages that no longer need translation will have their translations
|
81 |
* enclosed between a pair of '@@' marks.
|
82 |
*
|
83 |
* Message string can be used with plural forms format. Check i18n section
|
84 |
* of the guide for details.
|
85 |
*
|
86 |
* NOTE: this file must be saved in UTF-8 encoding.
|
87 |
*/
|
88 |
return $array; |
89 |
EOD; |
90 |
|
91 |
file_put_contents($fileName, $content); |
92 |
$this->stdout("Translation saved.\n\n", Console::FG_GREEN); |
93 |
}
|
Leider ist der ursprüngliche Message / Extract-Code nicht kommentiert. Es gibt zwar einige weitere Verbesserungen, aber ich habe hier einfach einen Aufruf der Google Translate API hinzugefügt:
1 |
foreach ($untranslated as $message) { |
2 |
$todo[$message] = $this->getGoogleTranslation($message,$language); |
3 |
}
|
Und ich habe einen Parameter ($force=true)
hinzugefügt, um die Neuerstellung der Nachrichtendateien zu erzwingen:
1 |
if (!$force) { |
2 |
if (array_keys($existingMessages) == $messages) { |
3 |
$this->stdout("Nothing new in \"$category\" category... Nothing to save.\n\n", Console::FG_GREEN); |
4 |
return; |
5 |
} |
6 |
} |
Message Planner übersetzen
Die Tests wurden abgeschlossen und ich war begeistert, Message Planner in weitere Sprachen zu übersetzen. Zuerst fügen wir die neuen Sprachübersetzungen der Datei /console/config/i18n.php
hinzu:
1 |
<?php |
2 |
|
3 |
return [ |
4 |
// string, required, root directory of all source files |
5 |
'sourcePath' => __DIR__. DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR, |
6 |
// Root directory containing message translations. |
7 |
'messagePath' => __DIR__ . DIRECTORY_SEPARATOR .'..'. DIRECTORY_SEPARATOR . 'messages', |
8 |
// array, required, list of language codes that the extracted messages |
9 |
// should be translated to. For example, ['zh-CN', 'de']. |
10 |
'languages' => ['ar','es','de','it','iw','ja','yi','zh-CN'], |
Wenn Sie eine breitere Sprachunterstützung benötigen oder eine größere Anzahl von zu übersetzenden Zeichenfolgen benötigen, möchten Sie möglicherweise zu Travis Tillotsons Google Translation Client und einem kostenpflichtigen API-Zugriff wechseln.
Dann fügte ich die Übersetzungszeichenfolgen /frontend/views/layouts/main.php
und /frontend/views/site/index.php
hinzu, um die Übersetzung der Startseite zu demonstrieren. Da diese Seiten nicht von Yiis Gii-Code-Generator generiert werden, wurden die Textzeichenfolgen in reinem HTML-Code gelassen. Hier ist ein Beispiel, wie sie jetzt aussehen:
1 |
<div class="row"> |
2 |
<div class="col-lg-4"> |
3 |
<h2><?= Yii::t('frontend','Getting Started') ?></h2> |
4 |
<p><?= Yii::t('frontend','Follow along with our tutorial series at Tuts+ as we build Meeting Planner step by step. In this episode we talk about startups in general and the goals for our application.') ?></p> |
5 |
<p><a class="btn btn-default" href="http://code.tutsplus.com/tutorials/building-your-startup-with-php-getting-started--cms-21948"><?= Yii::t('frontend','Episode 1') ?> »</a></p> |
So sieht die Homepage auf Englisch aus:



Dann lief ich google_extract
:
1 |
./yii translate/google_extract /Users/Jeff/sites/mp/common/config/i18n.php |
Hinweis: Stellen Sie dabei sicher, dass die Anwendungssprache auf Ihre Standardsprache eingestellt ist, z. Englisch. Diese Einstellung befindet sich in /common/config/main.php
:
1 |
<?php |
2 |
return [ |
3 |
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', |
4 |
// available languages |
5 |
// 'ar','de','es','it','iw','ja','yi','zh-CN' |
6 |
'language' => 'en', // english |
7 |
'components' => [ |
Ich stellte fest, dass es notwendig war, google_extract
einmal auszuführen, um die Vorlage für die ursprüngliche Nachrichtendatei zu erstellen, und ein zweites Mal, um die Aufrufe von Google Translate einzuleiten.
Dann kann ich die Spracheinstellung in /common/config/main.php
ändern, um jede Übersetzung anzuzeigen. Die Ergebnisse sind ziemlich unglaublich für etwas, das so schnell generiert werden kann.
Hier ist die Startseite auf Chinesisch:



Hier ist die Homepage auf Arabisch:



Hier ist die Startseite auf Japanisch:



Hier ist die Homepage auf Jiddisch:



Hier ist die Startseite in deutscher Sprache:



Was kommt als nächstes?
Ich hoffe, dir hat dieses Tutorial gefallen. Es hat Spaß gemacht, etwas zu schreiben, das die Reichweite meiner Meeting Planner-Anwendung so stark beeinflusste. Wenn Sie mehr über Meeting Planner erfahren möchten, finden Sie in der Serie "Erstellen Sie Ihr Startup mit PHP" die nächsten Tutorials. Es gibt viele lustige Features.
Bitte zögern Sie nicht, Ihre Fragen und Kommentare unten hinzuzufügen. Ich nehme generell an den Diskussionen teil. Sie können mich auch auf Twitter @reifman erreichen oder mich direkt per E-Mail kontaktieren.