Localisez votre application Web pour n'importe quel pays avec l'API Google Translate

Ce que vous allez créer

Dans mon tutoriel Localisation avec I18n pour la construction de votre démarrage avec PHP, j'ai créé un exemple de code espagnol en copiant-collant des chaînes de texte dans Google Translate. J'ai commencé à me demander si je pouvais intégrer l'API Google Translate avec le script d'extraction de ressources I18n du framework Yii pour automatiser la traduction dans un certain nombre de pays. J'ai posté une demande de fonctionnalité sur le forum Yii, puis j'ai décidé de voir si je pouvais créer la fonctionnalité moi-même..

Dans ce tutoriel, je vais vous expliquer mes extensions du script d’extrait Yii I18n qui fait exactement cela. Et je vais vous montrer comment traduire mon application de démarrage, Meeting Planner, dans une poignée de langues.

N'oubliez pas que Google Translate n'est pas parfait et qu'il ne résout pas les problèmes liés aux formats d'heure et de date et aux devises. Mais pour un moyen rapide et abordable (gratuit) de créer des traductions par défaut pour votre application Web dans plus de 50 langues, il s'agit d'une solution idéale..

Par exemple, voici une erreur plus notable que j'ai rencontrée dans les tests. Heureusement, elles sont rares:

'nFormatted TB' => 'nFormatted tuberculosis', 

Si vous avez besoin d'une approche plus professionnelle, un ami m'a indiqué un service payant de gestion de la localisation dans les applications, Transifex. Je ne l'ai pas vérifié moi-même mais ça a l'air intriguant.

Travailler avec Google Translate

Quelles langues prend-il en charge??

Google Traduction propose des services de traduction dans 64 langues, dont le suédois mais malheureusement pas le suédois.

Voici un échantillon des langues prises en charge par Google - voir la liste complète ici:

Parler à l'API Google Translate

J'ai trouvé deux bibliothèques Composer permettant d'utiliser l'API Google Translator en PHP:

  • Bibliothèque de traduction Google de Levan Velijanashvili
  • Client de traduction Google de Travis Tillotson

J'ai trouvé Velijanashvili en premier, alors c'est ce que j'ai utilisé dans ce tutoriel. Il exploite Google Translate via son interface Web gratuite RESTful, de sorte que vous n’avez pas besoin d’une clé API. Toutefois, si vous avez une grande bibliothèque de ressources ou si vous envisagez de traduire de nombreuses langues, vous voudrez probablement intégrer celui de Tillotson, car celui-ci est entièrement intégré au service payant de Google Traduction via des clés..

Pour ce tutoriel, je me base sur la base de code de la série PHP: Construisez votre démarrage. Pour installer la bibliothèque de traduction Google de Velijanashvili, tapez simplement:

compositeur besoin de stichoza / google-translate-php

Voici un exemple de code à traduire de l'anglais vers l'espagnol:

utilisez Stichoza \ Google \ GoogleTranslate; echo GoogleTranslate :: staticTranslate ('bonjour le monde', "en", "es"). "\ n"; 

Il devrait produire:

hola mundo

Extension du script de message / extrait I18n de Yii2

Comment le support I18n de Yii2 fonctionne aujourd'hui

À ce stade, vous pouvez consulter mon didacticiel sur la localisation avec I18n, qui explique comment extraire des chaînes de message pour les traductions dans la langue requise.. 

Vous pouvez utiliser le générateur de code Gii de Yii pour générer des modèles et du code CRUD qui intègre automatiquement le support I18n. Chaque chaîne du code est remplacée par un appel de fonction tel que Yii :: t ('category', 'chaîne de texte à traduire');.

Yii offre un message / extrait de commande de console qui trouve tous ces appels de fonction dans votre application et crée une arborescence de répertoires de fichiers par langue et catégorie pour les traductions de toutes ces chaînes..

Voici un exemple de fichier de chaîne en allemand:

 'Machen Sie sich mit Yii begonnen', 'Heading' => 'Überschrift', 'My Yii Application' => 'Meine Yii-Anwendung', 'Yii Documentation' => 'Yii Dokumentation', 'Yii Extensions' => ' Yü -Erweiterungen ',' Yii Forum '=>' Forum Yii ',' Êtes-vous sûr de vouloir supprimer cet élément? ' => 'Sind Sie sicher, Sie wollen diesen Inhalt löschen?', 'Félicitations!' => 'Herzlichen Glückwunsch!', 'Create' => 'schaffen', 'Create modelClass' => 'schaffen modelClass', 'Created At' => 'Erstellt am', 'Delete' => 'löschen ',' ID '=>' Identifikation ',

Voici un exemple des chemins de répertoire:

Extension du message / extrait pour Google Translate

J'ai choisi l'approche consistant à créer un script de remplacement appelé message / google_extract qui appelle Google Translate chaque fois qu'il faut traduire une chaîne.

Empêcher la rupture de code de traduire des jetons

Parce que I18n intègre des jetons de paramètres entre accolades pour les valeurs variables, j'ai tout de suite rencontré des problèmes. Par exemple, voici quelques chaînes I18n qui incluent des jetons et des jetons imbriqués:

'Create modelClass "Enregistré le 0, date, MMMM jj, AAAA HH: mm à partir de 1" 0, date, MMMM jj, AAAA HH: mm "nFormaté n, pluriel, = 1 gibibyte autre gibibytes '

L'API Google Translate ne dispose pas d'un paramètre pour ignorer les jetons tels que ceux-ci dans ce formulaire. Mais nous ne pouvons pas les traduire car ils correspondent à des noms de variables et à des chaînes de format en code.

Il ne me semblait pas qu'une expression régulière puisse résoudre ce problème lorsque des chaînes et des jetons à traduire sont présents ensemble. Il est probable que les lecteurs disposent d’une solution plus efficace que celle que j’ai trouvée pour résoudre ce problème. Si vous l’avez bien comprise, veuillez la publier dans les commentaires..

J'ai choisi de numériser les chaînes par caractère et de suivre l'imbrication d'accolades. Je serai le premier à admettre qu'il y a peut-être un meilleur moyen. Voici ma fonction parse_safe_translate ():

/ * * analyse une chaîne de caractères dans un tableau * scindé par des segments entre accolades *, y compris les accolades imbriquées * / public function parse_safe_translate ($ s) $ debug = false; $ resultat = array (); $ start = 0; $ nid = 0; $ ptr_first_curly = 0; $ total_len = strlen ($ s); pour ($ i = 0; $ i<$total_len; $i++)  if ($s[$i]=='')  // found left curly if ($nest==0)  // it was the first one, nothing is nested yet $ptr_first_curly=$i;  // increment nesting $nest+=1;  elseif ($s[$i]=='')  // found right curly // reduce nesting $nest-=1; if ($nest==0)  // end of nesting if ($ptr_first_curly-$start>= 0) // chaîne push menant à la première gauche gauche curly $ préfixe = substr ($ s, $ start, $ ptr_first_curly- $ start); if (strlen ($ prefix)> 0) array_push ($ result, $ prefix);  // chaîne push curly (éventuellement imbriquée) $ suffix = substr ($ s, $ ptr_first_curly, $ i- $ ptr_first_curly + 1); if (strlen ($ suffix))> 0) array_push ($ result, $ suffix);  if ($ debug) echo '|' .substr ($ s, $ start, $ ptr_first_curly- $ start-1). "| \ n"; echo '|' .substr ($ s, $ ptr_first_curly, $ i- $ ptr_first_curly + 1). "| \ n";  $ start = $ i + 1; $ ptr_first_curly = 0; if ($ debug) echo 'prochain démarrage:'. $ start. "\ n";  $ suffix = substr ($ s, $ start, $ total_len- $ start); if ($ debug) echo 'Démarrer:'. $ start. "\ n"; echo 'Pfc:'. $ ptr_first_curly. "\ n"; echo $ suffix. "\ n";  if (strlen ($ suffix)> 0) array_push ($ result, substr ($ s, $ start, $ total_len- $ start));  return $ result;  

Il convertit une chaîne I18n en un tableau d'éléments séparés en éléments traduisibles et non traduisibles. Par exemple, ce code:

$ message = 'L'image "fichier" est trop grande. La hauteur ne peut pas être supérieure à limite, nombre limite, pluriel, un pixel autre pixels. '; print_r ($ this-> parse_safe_translate ($ message)); 

Génère cette sortie:

Tableau ([0] => L'image "[1] => fichier [2] =>" est trop grande. La hauteur ne peut pas être supérieure à [3] => limite, nombre [4] => [ 5] => limite, pluriel, un pixel autre pixels [6] =>.) 

Chaque fois que le processus d'extraction identifie une nouvelle chaîne à traduire, il décompose la chaîne en ces parties et appelle l'API Google Translate pour toute chaîne pouvant être traduite, par exemple. un qui ne commence pas par une accolade frisée gauche. Ensuite, il concatène ces traductions avec les chaînes tokenisées dans une seule chaîne.

Traduire une chaîne en jetons avec Google Translate

Voici la fonction getGoogleTranslation () pour une chaîne et une langue de destination. La langue source est déterminée par Yii :: $ app-> langue.

 fonction publique getGoogleTranslation ($ message, $ language) $ arr_parts = $ this-> parse_safe_translate ($ message); $ translation = "; foreach ($ arr_parts as $ str) if (! stristr ($ str, '')) if (strlen ($ translation)> 0 et substr ($ translation, -1) == ' ') $ translation. = "; $ translation. = GoogleTranslate :: staticTranslate ($ str, Yii :: $ app-> langue, $ langue);  else // ajoute un préfixe d'espace sauf s'il s'agit du premier if (strlen ($ translation)> 0) $ translation. = ". $ str; sinon $ translation. = $ str; print_r ($ translation); return $ translation;  

J'ai trouvé que la combinaison de ces approches fonctionnait presque parfaitement dans mes tests.

Personnaliser le message / extrait de Yii

L'implémentation I18n de Yii prend en charge le chargement de chaînes de ressources à partir de fichiers .PO, de fichiers .PHP (que j'utilise) et de la base de données. Pour ce tutoriel, j'ai personnalisé Message / Extract pour la génération de fichier PHP..

J'ai copié et étendu message / extrait dans /console/controllers/TranslateController.php. A cause des règles strictes de PHP 5.6.x, j'ai changé les noms de fonction pour saveMessagesToPHP à saveMessagesToPHPEnhanced et saveMessagesCategoryToPHP à saveMessagesCategoryToPHPEnhanced.

Ici se trouve le saveMessagesToPHPEnhanced () une fonction:

/ ** * Écrit les messages dans des fichiers PHP * * @param array $ messages * @param chaîne $ dirName nom du répertoire à écrire dans * @param boolean $ écraser si le fichier existant doit être écrasé sans sauvegarde * @param boolean $ removeUnused if les traductions obsolètes doivent être supprimées * @param boolean $ sort si les traductions doivent être triées * / protected function saveMessagesToPHPEnhanced ($ messages, $ dirName, $ écraser, $ removeUnused, $ sort, $ language) foreach ($ messages comme $ category => $ msgs) $ file = str_replace ("\\", '/', "$ dirName / $ category.php"); $ path = dirname ($ file); FileHelper :: createDirectory ($ path); $ msgs = array_values ​​(array_unique ($ msgs)); $ coloredFileName = Console :: ansiFormat ($ fichier, [Console :: FG_CYAN]); $ this-> stdout ("Enregistrement de messages dans $ coloredFileName… \ n"); $ this-> saveMessagesCategoryToPHPEnhanced ($ msgs, $ fichier, $ écraser, $ enleverUnused, $ sort, $ catégorie, $ langue);  

Il appelle le saveMessagesCategoryToPHP une fonction:

/ ** * Écrit les messages de catégorie dans le fichier PHP * * @param array $ messages * @param chaîne $ fileName nom du fichier à écrire dans * @param boolean $ écrase si le fichier existant doit être écrasé sans sauvegarde * @param boolean $ removeUnused si les traductions obsolètes doivent être supprimées * @param boolean $ sort si les traductions doivent être triées * @param boolean $ langue à traduire en * @param boolean $ force google traduction * @param chaîne $ catégorie message catégorie * / fonction protégée saveMessagesCategoryToPHPEnhanced ($ messages, $ NomFichier, $ Écraser, $ removeUnused, $ sort, $ catégorie, $ langue, $ force = true) if (is_file ($ NomFichier)) $ existantsMessages = require ($ NomFichier); trier ($ messages); ksort ($ existingMessages); if (! $ force) if (array_keys ($ existingMessages) == $ messages) $ this-> stdout ("Rien de nouveau dans la catégorie \" $ category \ "… Rien à enregistrer. \ n \ n", Console: : FG_GREEN); revenir;  $ merged = []; $ untranslated = []; foreach ($ messages as $ message) if (array_key_exists ($ message, $ existingMessages)) && strlen ($ existingMessages [$ message])> 0) $ merged [$ message] = $ existingMessages [$ message];  else $ untranslated [] = $ message;  ksort ($ merged); sort ($ non traduit); $ todo = []; foreach ($ non traduit en $ message) $ todo [$ message] = $ this-> getGoogleTranslation ($ message, $ language);  ksort ($ existingMessages); foreach ($ existingMessages as $ message => $ translation) if (! isset ($ merged [$ message]) &&! isset ($ todo [$ message]) &&! $ removeUnused) if (! empty ($ translation) && strncmp ($ translation, '@@', 2) === 0 && substr_compare ($ translation, '@@', -2, 2) === 0) $ todo [$ message] = $ translation;  else $ todo [$ message] = '@@'. $ traduction. '@@';  $ merged = array_merge ($ todo, $ merged); if ($ sort) ksort ($ merged);  if (false === $ overwrite) $ fileName. = '.merged';  $ this-> stdout ("Traduction fusionnée. \ n");  else $ merged = []; foreach ($ messages en tant que $ message) $ merged [$ message] = "; ksort ($ merged); $ array = VarDumper :: export ($ merged); $ content = <<id 'commande. * Il contient les messages localisables extraits du code source. * Vous pouvez modifier ce fichier en traduisant les messages extraits. * * Chaque élément du tableau représente la traduction (valeur) d'un message (clé). * Si la valeur est vide, le message est considéré comme non traduit. * Les traductions des messages qui ne nécessitent plus de traduction * sont placées entre une paire de marques '@@'. * * La chaîne de message peut être utilisée avec le format de formulaire pluriel. Consultez la section i18n * du guide pour plus de détails. * * REMARQUE: ce fichier doit être enregistré au format UTF-8. * / return $ array; EOD; fichier_put_contents ($ nomfichier, $ contenu); $ this-> stdout ("Traduction enregistrée. \ n \ n", Console :: FG_GREEN); 

Malheureusement, le code d'origine du message / extrait n'est pas commenté. Bien que certaines améliorations supplémentaires puissent être apportées, j'ai simplement ajouté un appel à l'API Google Translate ici:

foreach ($ non traduit en $ message) $ todo [$ message] = $ this-> getGoogleTranslation ($ message, $ language); 

Et j'ai ajouté un paramètre ($ force = true) forcer la recréation des fichiers de messages:

if (! $ force) if (array_keys ($ existingMessages) == $ messages) $ this-> stdout ("Rien de nouveau dans la catégorie \" $ category \ "… Rien à enregistrer. \ n \ n", Console: : FG_GREEN); revenir; 

Traduire le planificateur de messages

Les tests étant terminés, j'étais impatient de traduire Message Planner dans plusieurs langues. Premièrement, nous ajoutons les nouvelles traductions de langue au /console/config/i18n.php fichier:

 __DIR__. DIRECTORY_SEPARATOR. '…' DIRECTORY_SEPARATOR. '…' DIRECTORY_SEPARATOR, // Répertoire racine contenant les traductions des messages. 'messagePath' => __DIR__. DIRECTORY_SEPARATOR. '…'. DIRECTORY_SEPARATOR. 'messages', // tableau, obligatoire, liste des codes de langue dans lesquels les messages // extraits doivent être traduits. Par exemple, ['zh-CN', 'de']. 'languages' => ['ar', 'es', 'de', 'it', 'iw', 'ja', 'yi', 'zh-CN'], 

Encore une fois, si vous avez besoin d'une prise en charge linguistique plus étendue ou si vous avez un plus grand nombre de chaînes à traduire, vous pouvez passer au client Google Traduction de Travis Tillotson et à un accès payant à l'API..

Ensuite, j'ai ajouté des chaînes de traduction /frontend/views/layouts/main.php et /frontend/views/site/index.php afin de démontrer la traduction de la page d'accueil. Puisque ces pages ne sont pas générées par le générateur de code Gii de Yii, les chaînes de texte ont été laissées en HTML brut. Voici un exemple de ce à quoi ils ressemblent maintenant:

»

Voici à quoi ressemble la page d'accueil en anglais:

Puis j'ai couru google_extract:

./ yii translate / google_extract /Users/Jeff/sites/mp/common/config/i18n.php 

Remarque: lorsque vous effectuez cette opération, assurez-vous que la langue de l'application est définie sur votre langue par défaut, par exemple. Anglais. Ce paramètre est en /common/config/main.php:

 dirname (dirname (__ DIR__)). '/ vendor', // langues disponibles // 'ar', 'de', 'es', 'it', 'iw', 'ja', 'yi', 'zh-CN "language' => 'en ', // english' components '=> [ 

J'ai trouvé qu'il était nécessaire de courir google_extract une fois pour créer le modèle de fichier de message initial et une seconde fois pour lancer les appels vers Google Traduction.

Ensuite, je peux changer le réglage de la langue dans /common/config/main.php pour voir chaque traduction. Les résultats sont assez incroyables pour quelque chose qui peut être généré si rapidement.

Voici la page d'accueil en chinois:

Voici la page d'accueil en arabe:

Voici la page d'accueil en japonais:

Voici la page d'accueil en yiddish:

Voici la page d'accueil en allemand:

Et après?

J'espère que vous avez apprécié ce tutoriel. C'était amusant d'écrire quelque chose qui avait un impact aussi large sur la portée potentielle de mon application Meeting Planner. Si vous souhaitez en savoir plus sur Meeting Planner, consultez les prochains tutoriels de notre série Construire votre démarrage avec PHP. Il y a beaucoup de fonctionnalités amusantes à venir.

N'hésitez pas à ajouter vos questions et commentaires ci-dessous; Je participe généralement aux discussions. Vous pouvez également me joindre sur Twitter @reifman ou m'envoyer un email directement.

Liens connexes

  • Construire votre startup avec PHP: Localisation avec I18n (Tuts +)
  • Programmation avec Yii2: Mise en route (Tuts +)
  • Introduction au framework Yii (Tuts +)
  • Le guide définitif Yii2: Internationalisation