Site Web multilingue des meilleures pratiques

179

Je me débat avec cette question depuis plusieurs mois maintenant, mais je n'ai pas été dans une situation où j'avais besoin d'explorer toutes les options possibles auparavant. En ce moment, j'ai l'impression qu'il est temps de connaître les possibilités et de créer ma propre préférence personnelle à utiliser dans mes projets à venir.

Laissez-moi d'abord esquisser la situation que je recherche

Je suis sur le point de mettre à niveau / redévelopper un système de gestion de contenu que j'utilise depuis un certain temps maintenant. Cependant, je pense que le multilinguisme est une grande amélioration de ce système. Avant, je n'utilisais aucun framework mais je vais utiliser Laraval4 pour le projet à venir. Laravel semble le meilleur choix d'une manière plus propre de coder PHP. Sidenote: Laraval4 should be no factor in your answer. Je recherche des moyens de traduction généraux indépendants de la plateforme / du framework.

Que faut-il traduire

Comme le système que je recherche doit être aussi convivial que possible, la méthode de gestion de la traduction doit se trouver à l'intérieur du CMS. Il ne devrait pas être nécessaire de démarrer une connexion FTP pour modifier les fichiers de traduction ou tout autre modèle analysé html / php.

De plus, je recherche le moyen le plus simple de traduire plusieurs tables de base de données, peut-être sans avoir besoin de créer des tables supplémentaires.

Qu'est-ce que je suis venu avec moi-même

Comme j'ai déjà cherché, lu et essayé des choses moi-même. J'ai plusieurs options. Mais je n'ai toujours pas l'impression d'avoir atteint une méthode des meilleures pratiques pour ce que je recherche vraiment. Pour le moment, c'est ce que j'ai proposé, mais cette méthode a aussi des effets secondaires.

  1. Modèles PHP analysés : le système de modèles doit être analysé par PHP. De cette façon, je suis capable d'insérer les paramètres traduits dans le HTML sans avoir à ouvrir les modèles et à les modifier. En plus de cela, les modèles PHP analysés me donnent la possibilité d'avoir 1 modèle pour le site Web complet au lieu d'avoir un sous-dossier pour chaque langue (ce que j'avais auparavant). La méthode pour atteindre cette cible peut être Smarty, TemplatePower, Laravel's Blade ou tout autre analyseur de modèle. Comme je l'ai dit, cela devrait être indépendant de la solution écrite.
  2. Database Driven : peut-être que je n'ai pas besoin de le mentionner à nouveau. Mais la solution doit être basée sur une base de données. Le CMS est destiné à être orienté objet et MVC, je devrais donc penser à une structure de données logique pour les chaînes. Comme mes modèles seraient structurés: templates / contrôleur / view.php peut - être cette structure serait le plus logique: Controller.View.parameter. La table de base de données aurait ces champs un long avec un valuechamp. Dans les modèles, nous pourrions utiliser une méthode de tri comme echo __('Controller.View.welcome', array('name', 'Joshua'))et le paramètre contient Welcome, :name. Ainsi le résultat étant Welcome, Joshua. Cela semble être un bon moyen de le faire, car les paramètres tels que: nom sont faciles à comprendre par l'éditeur.
  3. Faible charge de la base de données: Bien sûr, le système ci-dessus entraînerait des charges de base de données si ces chaînes sont chargées en déplacement. Par conséquent, j'aurais besoin d'un système de mise en cache qui restitue les fichiers de langue dès qu'ils sont modifiés / enregistrés dans l'environnement d'administration. Étant donné que les fichiers sont générés, une bonne disposition du système de fichiers est également nécessaire. Je suppose que nous pouvons utiliser languages/en_EN/Controller/View.phpou .ini, ce qui vous convient le mieux. Peut-être qu'un .ini est même analysé plus rapidement à la fin. Ce fichier doit contenir les données du fichier format parameter=value; . Je suppose que c'est la meilleure façon de le faire, car chaque vue rendue peut inclure son propre fichier de langue s'il existe. Les paramètres de langage doivent alors être chargés dans une vue spécifique et non dans une portée globale pour éviter que les paramètres ne s'écrasent les uns les autres.
  4. Traduction de la table de base de données : c'est en fait ce qui m'inquiète le plus. Je cherche un moyen de créer des traductions de News / Pages / etc. aussi vite que possible. Avoir deux tables pour chaque module (par exemple Newset News_translations) est une option, mais cela demande beaucoup de travail pour obtenir un bon système. L'une des choses que j'ai proposées est basée sur un data versioningsystème que j'ai écrit: il y a un nom de table de base de données Translations, cette table a une combinaison unique de language, tablenameetprimarykey. Par exemple: en_En / News / 1 (Se référant à la version anglaise de l'actualité avec ID = 1). Mais il y a 2 énormes inconvénients à cette méthode: tout d'abord, cette table a tendance à devenir assez longue avec beaucoup de données dans la base de données et deuxièmement, ce serait un sacré travail d'utiliser cette configuration pour rechercher la table. Par exemple, la recherche du slug SEO de l'élément serait une recherche en texte intégral, ce qui est assez stupide. Mais d'un autre côté: c'est un moyen rapide de créer très rapidement du contenu traduisible dans chaque table, mais je ne pense pas que ce pro surpasse les inconvénients.
  5. Travail frontal: Le front-end nécessiterait également une réflexion. Bien sûr, nous stockerions les langues disponibles dans une base de données et (dés) activerions celles dont nous avons besoin. De cette façon, le script peut générer une liste déroulante pour sélectionner une langue et le back-end peut décider automatiquement quelles traductions peuvent être effectuées à l'aide du CMS. La langue choisie (par exemple en_EN) serait alors utilisée lors de l'obtention du fichier de langue pour une vue ou pour obtenir la bonne traduction pour un élément de contenu sur le site Web.

Alors, les voilà. Mes idées jusqu'à présent. Ils n'incluent même pas encore d'options de localisation pour les dates, etc., mais comme mon serveur prend en charge PHP5.3.2 +, la meilleure option est d'utiliser l'extension intl comme expliqué ici: http://devzone.zend.com/1500/internationalization-in -php-53 / - mais cela serait utile dans tout stade de développement ultérieur. Pour l'instant, le principal problème est de savoir comment disposer des meilleures pratiques de traduction du contenu d'un site Web.

Outre tout ce que j'ai expliqué ici, j'ai encore une autre chose que je n'ai pas encore décidé, cela ressemble à une question simple, mais en fait cela me donne des maux de tête:

Traduction d'URL? Devrions-nous faire cela ou pas? et de quelle manière?

Donc .. si j'ai cette url: http://www.domain.com/about-uset l'anglais est ma langue par défaut. Cette URL doit-elle être traduite http://www.domain.com/over-onslorsque je choisis le néerlandais comme langue? Ou devrions-nous emprunter la voie facile et simplement changer le contenu de la page visible sur /about. La dernière chose ne semble pas être une option valable car cela générerait plusieurs versions de la même URL, cette indexation du contenu échouera dans le bon sens.

Une autre option consiste à utiliser à la http://www.domain.com/nl/about-usplace. Cela génère au moins une URL unique pour chaque contenu. De plus, il serait plus facile d'aller dans une autre langue, par exemple, http://www.domain.com/en/about-uset l'URL fournie est plus facile à comprendre pour les visiteurs Google et humains. En utilisant cette option, que faisons-nous avec les langues par défaut? La langue par défaut doit-elle supprimer la langue sélectionnée par défaut? Donc, rediriger http://www.domain.com/en/about-usvers http://www.domain.com/about-us... A mes yeux, c'est la meilleure solution, car lorsque le CMS est configuré pour une seule langue, il n'est pas nécessaire d'avoir cette identification de langue dans l'URL.

Et une troisième option est une combinaison des deux options: en utilisant le "language-identification-less" -URL ( http://www.domain.com/about-us) pour la langue principale. Et utilisez une URL avec un slug SEO traduit pour les sous-langues: http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

J'espère que ma question vous fera craquer, ils ont craqué la mienne à coup sûr! Cela m'a aidé déjà à régler les choses comme une question ici. M'a donné la possibilité de revoir les méthodes que j'ai utilisées auparavant et l'idée que j'ai pour mon prochain CMS.

Je voudrais déjà vous remercier d'avoir pris le temps de lire ce tas de texte!

// Edit #1:

J'ai oublié de mentionner: la fonction __ () est un alias pour traduire une chaîne donnée. Dans cette méthode, il devrait évidemment y avoir une sorte de méthode de secours où le texte par défaut est chargé lorsqu'il n'y a pas encore de traductions disponibles. Si la traduction est manquante, elle doit être insérée ou le fichier de traduction doit être régénéré.

Joshua - Pendo
la source
3
qu'en est-il de gettext? php.net/manual/en/book.gettext.php
Francois Bourgeois

Réponses:

115

Prémisse du sujet

Il y a trois aspects distincts dans un site multilingue:

  • traduction d'interface
  • contenu
  • routage d'URL

Bien qu'ils soient tous interconnectés de différentes manières, du point de vue CMS, ils sont gérés à l'aide de différents éléments d'interface utilisateur et stockés différemment. Vous semblez confiant dans votre mise en œuvre et dans votre compréhension des deux premiers. La question portait sur ce dernier aspect - "Traduction d'URL? Devrions-nous faire cela ou pas? Et de quelle manière?"

De quoi l'URL peut-elle être faite?

Une chose très importante est de ne pas avoir envie d' IDN . Préférez plutôt la translittération (aussi: transcription et romanisation). Bien qu'à première vue, l'IDN semble une option viable pour les URL internationales, il ne fonctionne pas comme annoncé pour deux raisons:

  • certains navigateurs transformeront les caractères non ASCII comme 'ч'ou 'ž'en '%D1%87'et'%C5%BE'
  • si l'utilisateur a des thèmes personnalisés, la police du thème est très susceptible de ne pas avoir de symboles pour ces lettres

J'ai en fait essayé d'approcher IDN il y a quelques années dans un projet basé sur Yii (cadre horrible, à mon humble avis). J'ai rencontré les deux problèmes mentionnés ci-dessus avant de gratter cette solution. Aussi, je soupçonne que cela pourrait être un vecteur d'attaque.

Options disponibles ... comme je les vois.

Fondamentalement, vous avez deux choix, qui pourraient être résumés comme suit:

  • http://site.tld/[:query]: où [:query]détermine à la fois le choix de la langue et du contenu

  • http://site.tld/[:language]/[:query]: où une [:language]partie de l'URL définit le choix de la langue et [:query]n'est utilisée que pour identifier le contenu

La requête est Α et Ω ..

Disons que vous choisissez http://site.tld/[:query].

Dans ce cas, vous avez une source principale de langue: le contenu du [:query]segment; et deux sources supplémentaires:

  • valeur $_COOKIE['lang']pour ce navigateur particulier
  • liste des langues dans l'en- tête HTTP Accept-Language (1) , (2)

Tout d'abord, vous devez faire correspondre la requête à l'un des modèles de routage définis (si votre choix est Laravel, lisez ici ). En cas de correspondance réussie du modèle, vous devez ensuite trouver la langue.

Vous auriez à parcourir tous les segments du modèle. Trouvez les traductions potentielles pour tous ces segments et déterminez la langue utilisée. Les deux sources supplémentaires (cookie et en-tête) seraient utilisées pour résoudre les conflits de routage, quand (et non "si") ils surviennent.

Prenons par exemple: http://site.tld/blog/novinka.

C'est la translittération de "блог, новинка", cela en anglais signifie approximativement "blog", "latest".

Comme vous pouvez déjà le remarquer, en russe "блог" sera translittéré comme "blog". Ce qui signifie que pour la première partie de [:query]vous (dans le meilleur des cas ) se retrouvera avec une ['en', 'ru']liste de langues possibles. Ensuite, vous prenez le segment suivant - "novinka". Cela pourrait avoir une seule langue sur la liste des possibilités: ['ru'].

Lorsque la liste contient un élément, vous avez trouvé la langue avec succès.

Mais si vous vous retrouvez avec 2 (exemple: russe et ukrainien) ou plus de possibilités .. ou 0 possibilités, selon le cas. Vous devrez utiliser un cookie et / ou un en-tête pour trouver la bonne option.

Et si tout le reste échoue, vous choisissez la langue par défaut du site.

Langue comme paramètre

L'alternative consiste à utiliser une URL, qui peut être définie comme http://site.tld/[:language]/[:query]. Dans ce cas, lors de la traduction d'une requête, vous n'avez pas besoin de deviner la langue, car à ce stade, vous savez déjà laquelle utiliser.

Il existe également une source secondaire de langage: la valeur du cookie. Mais ici, il ne sert à rien de jouer avec l'en-tête Accept-Language, car vous n'avez pas affaire à une quantité inconnue de langues possibles en cas de "démarrage à froid" (lorsque l'utilisateur ouvre le site pour la première fois avec une requête personnalisée).

Au lieu de cela, vous avez 3 options simples et prioritaires:

  1. si le [:language]segment est défini, utilisez-le
  2. si $_COOKIE['lang']est défini, utilisez-le
  3. utiliser la langue par défaut

Lorsque vous avez la langue, vous essayez simplement de traduire la requête, et si la traduction échoue, utilisez la «valeur par défaut» pour ce segment particulier (en fonction des résultats du routage).

N'est-ce pas là une troisième option?

Oui, techniquement , vous pouvez combiner les deux approches, mais cela compliquerait le processus et accueillir que les personnes qui veulent URL changer manuellement de http://site.tld/en/newsà http://site.tld/de/newset attendre la page de nouvelles du changement vers le Allemand.

Mais même ce cas pourrait probablement être atténué en utilisant la valeur du cookie (qui contiendrait des informations sur le choix précédent de la langue), à ​​mettre en œuvre avec moins de magie et d'espoir.

Quelle approche utiliser?

Comme vous l'avez peut-être déjà deviné, je recommanderais http://site.tld/[:language]/[:query]l'option la plus judicieuse.

Aussi dans une situation de mots réels, vous auriez la 3e partie majeure dans l'URL: "titre". Comme au nom du produit dans la boutique en ligne ou titre d'article dans le site d'actualités.

Exemple: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency

Dans ce cas, ce '/news/article/121415'serait la requête et le 'EU-as-global-reserve-currency'titre. Uniquement à des fins de référencement.

Cela peut-il être fait à Laravel?

Kinda, mais pas par défaut.

Je ne suis pas trop familier avec cela, mais d'après ce que j'ai vu, Laravel utilise un mécanisme de routage simple basé sur des modèles. Pour implémenter des URL multilingues, vous devrez probablement étendre la (les) classe (s) principale (s) , car le routage multilingue nécessite l'accès à différentes formes de stockage (base de données, cache et / ou fichiers de configuration).

Il est acheminé. Et maintenant?

En conséquence, vous vous retrouveriez avec deux informations précieuses: la langue actuelle et les segments traduits de la requête. Ces valeurs peuvent ensuite être utilisées pour envoyer à la ou aux classes qui produiront le résultat.

Fondamentalement, l'URL suivante: http://site.tld/ru/blog/novinka(ou la version sans '/ru') est transformée en quelque chose comme

$parameters = [
   'language' => 'ru',
   'classname' => 'blog',
   'method' => 'latest',
];

Que vous utilisez uniquement pour l'envoi:

$instance = new {$parameter['classname']};
$instance->{'get'.$parameters['method']}( $parameters );

.. ou une variation de celui-ci, en fonction de la mise en œuvre particulière.

tereško
la source
1
Merci pour encore un autre aperçu! Très réfléchi! Je pensais également avoir le paramètre de langue dans l'URL. Cela semble tout simplement le meilleur moyen d'identifier une langue particulière, non seulement pour l'utilisateur, mais également à des fins de référencement. Dans le cas où un utilisateur change / en / news en / de / news, mon idée était de faire une redirection 301 (permanente) vers / de / nachrichten par exemple. Juste pour s'assurer que chaque langue n'a qu'une seule URL par page (encore une fois à des fins de référencement)
Joshua - Pendo
Il devient de plus en plus difficile de sélectionner la meilleure réponse, actuellement il y a environ 3/4 des réponses qui méritent au moins une partie de la prime chacune. Combinés, ils deviennent une réponse solide à tout ce que je voulais éclaircir ensemble :)
Joshua - Pendo
J'ai accepté votre réponse pour vous donner au moins un représentant supplémentaire pour la réponse détaillée que vous avez donnée sur la traduction d'URL. Grandement apprécié! Cependant, la prime est attribuée à la personne en dessous de vous, car elle a répondu à tous les aspects de ma question d'une manière indépendante de la plate-forme.
Joshua - Pendo
52

Implémentation d'i18n sans impact sur les performances en utilisant un pré-processeur comme suggéré par Thomas Bley

Au travail, nous avons récemment mis en œuvre i18n sur quelques-unes de nos propriétés, et l'une des choses avec lesquelles nous avons continué à lutter était le succès de la gestion de la traduction à la volée, puis j'ai découvert ce super article de blog de Thomas Bley. qui a inspiré la façon dont nous utilisons i18n pour gérer des charges de trafic importantes avec des problèmes de performances minimaux.

Au lieu d'appeler des fonctions pour chaque opération de traduction, ce qui, comme nous le savons en PHP, coûte cher, nous définissons nos fichiers de base avec des espaces réservés, puis utilisons un pré-processeur pour mettre en cache ces fichiers (nous stockons l'heure de modification des fichiers pour nous assurer que nous servons le dernier contenu à tout moment).

Les balises de traduction

Thomas utilise {tr}et des {/tr}balises pour définir le début et la fin des traductions. En raison du fait que nous utilisons TWIG, nous ne voulons pas utiliser {pour éviter toute confusion, nous utilisons donc [%tr%]et à la [%/tr%]place. En gros, cela ressemble à ceci:

`return [%tr%]formatted_value[%/tr%];`

Notez que Thomas suggère d'utiliser l'anglais de base dans le fichier. Nous ne faisons pas cela parce que nous ne voulons pas avoir à modifier tous les fichiers de traduction si nous changeons la valeur en anglais.

Les fichiers INI

Ensuite, nous créons un fichier INI pour chaque langue, au format placeholder = translated:

// lang/fr.ini
formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'

// lang/en_gb.ini
formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())

// lang/en_us.ini
formatted_value = '$' . number_format($value)

Il serait trivial de permettre à un utilisateur de les modifier à l'intérieur du CMS, il suffit d'obtenir les paires de clés par un preg_spliton \nou =et de rendre le CMS capable d'écrire dans les fichiers INI.

Le composant pré-processeur

Essentiellement, Thomas suggère d'utiliser une fonction de «compilateur» juste à temps (même si, en vérité, c'est un préprocesseur) comme celle-ci pour prendre vos fichiers de traduction et créer des fichiers PHP statiques sur le disque. De cette façon, nous mettons essentiellement en cache nos fichiers traduits au lieu d'appeler une fonction de traduction pour chaque chaîne du fichier:

// This function was written by Thomas Bley, not by me
function translate($file) {
  $cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
  // (re)build translation?
  if (!file_exists($cache_file)) {
    $lang_file = 'lang/'.LANG.'.ini';
    $lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';

    // convert .ini file into .php file
    if (!file_exists($lang_file_php)) {
      file_put_contents($lang_file_php, '<?php $strings='.
        var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
    }
    // translate .php into localized .php file
    $tr = function($match) use (&$lang_file_php) {
      static $strings = null;
      if ($strings===null) require($lang_file_php);
      return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
    };
    // replace all {t}abc{/t} by tr()
    file_put_contents($cache_file, preg_replace_callback(
      '/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
  }
  return $cache_file;
}

Remarque: je n'ai pas vérifié que le regex fonctionne, je ne l'ai pas copié à partir de notre serveur d'entreprise, mais vous pouvez voir comment l'opération fonctionne.

Comment l'appeler

Encore une fois, cet exemple est de Thomas Bley, pas de moi:

// instead of
require("core/example.php");
echo (new example())->now();

// we write
define('LANG', 'en_us');
require(translate('core/example.php'));
echo (new example())->now();

Nous stockons la langue dans un cookie (ou une variable de session si nous ne pouvons pas obtenir de cookie) puis la récupérons à chaque demande. Vous pouvez combiner cela avec un $_GETparamètre facultatif pour remplacer la langue, mais je ne suggère pas de sous-domaine par langue ou page par langue car cela rendra plus difficile de voir quelles pages sont populaires et réduira la valeur de l'entrée liens car vous les aurez à peine diffusés.

Pourquoi utiliser cette méthode?

Nous aimons cette méthode de prétraitement pour trois raisons:

  1. L'énorme gain de performances de ne pas appeler tout un tas de fonctions pour un contenu qui change rarement (avec ce système, 100000 visiteurs en français ne finiront par exécuter qu'une seule fois le remplacement de la traduction).
  2. Il n'ajoute aucune charge à notre base de données, car il utilise de simples fichiers plats et est une solution purement PHP.
  3. La possibilité d'utiliser des expressions PHP dans nos traductions.

Obtenir le contenu de la base de données traduit

Nous ajoutons simplement une colonne pour le contenu de notre base de données appelée language, puis nous utilisons une méthode d'accesseur pour la LANGconstante que nous avons définie précédemment, donc nos appels SQL (en utilisant ZF1, malheureusement) ressemblent à ceci:

$query = select()->from($this->_name)
                 ->where('language = ?', User::getLang())
                 ->where('id       = ?', $articleId)
                 ->limit(1);

Nos articles ont une clé primaire composée over idet languageainsi l'article 54peut exister dans toutes les langues. Notre valeur par LANGdéfaut est en_USsi non spécifié.

Traduction de slug d'URL

Je combinerais deux choses ici, l'une est une fonction dans votre bootstrap qui accepte un $_GETparamètre de langue et remplace la variable de cookie, et une autre est le routage qui accepte plusieurs slugs. Ensuite, vous pouvez faire quelque chose comme ça dans votre routage:

"/wilkommen" => "/welcome/lang/de"
... etc ...

Ceux-ci peuvent être stockés dans un fichier plat qui peut être facilement écrit à partir de votre panneau d'administration. JSON ou XML peuvent fournir une bonne structure pour les prendre en charge.

Remarques concernant quelques autres options

Traduction à la volée basée sur PHP

Je ne vois pas que ceux-ci offrent un avantage par rapport aux traductions prétraitées.

Traductions frontales

J'ai longtemps trouvé cela intéressant, mais il y a quelques mises en garde. Par exemple, vous devez mettre à la disposition de l'utilisateur la liste complète des phrases de votre site Web que vous prévoyez de traduire, cela peut être problématique s'il y a des zones du site que vous gardez cachées ou auxquelles vous ne leur avez pas autorisé l'accès.

Vous devriez également supposer que tous vos utilisateurs sont disposés et capables d'utiliser Javascript sur votre site, mais d'après mes statistiques, environ 2,5% de nos utilisateurs fonctionnent sans lui (ou utilisent Noscript pour empêcher nos sites de l'utiliser) .

Traductions basées sur une base de données

Les vitesses de connectivité de la base de données PHP n'ont rien d'extraordinaire, et cela ajoute à la surcharge déjà élevée de l'appel d'une fonction sur chaque phrase à traduire. Les problèmes de performances et d'évolutivité semblent accablants avec cette approche.

Désir de glitch
la source
Je vois que je vous ai confondu avec "Traductions frontales", ce que je voulais dire était un moyen d'analyser les chaînes traduites à l'écran. Je ne cherche certainement pas un moyen de le traduire côté client! Ce que je voulais dire, c'était le moyen le plus simple de changer de langue sur le front-end, mais c'est évidemment en utilisant un cookie ou un paramètre utilisateur :)
Joshua - Pendo
Oh, et par Database-Driven, je visais davantage la méthode de gestion de toutes les traductions, donc ma solution idéale serait un back-end qui écrit les traductions dans une base de données suivi d'une fonction qui génère le composant de prétraitement qui génère le PHP fichier. Why?: simple .. Je ne veux pas être dérangé par de petits changements de texte, les utilisateurs devraient pouvoir le faire eux-mêmes sans utiliser d'éditeur de code et / ou de programme ftp :)
Joshua - Pendo
@PENDO Je sais que vous ne parliez pas de traductions frontales, c'était un commentaire à peine voilé envers l'utilisateur qui suggérait des frameworks de traduction frontale utilisant JS. ;)
Glitch Desire
@PENDO Je suis d'accord, j'utiliserais le backend comme vous l'avez suggéré, mais au lieu d'une base de données, j'utiliserais un fichier plat pour des raisons de performances. Bien sûr, la suggestion de base est ici prérendu les modèles lors d'un changement de sorte que vous pouvez remplacer les .INIfichiers avec une table de base de données 3 colonnes avec placeholder, replacement, language. Touche composée sur placeholderet language. Ensuite, ayez un autre 2-col avec tempfile(chemin vers le modèle) et modified(DATETIME).
Glitch Desire
1
@PENDO Merci. J'en ai remis 250 et je prévois de l'attribuer à teresko dans les 24 heures lorsque le site me le permettra, car vous avez sélectionné les deux réponses comme correctes, et je pense qu'une scission représenterait le mieux vos intentions.
Glitch Desire
15

Je vous suggère de ne pas inventer une roue et d'utiliser la liste des abréviations des langues gettext et ISO. Avez-vous vu comment i18n / l10n est implémenté dans des CMS ou des frameworks populaires?

En utilisant gettext, vous aurez un outil puissant où de nombreux cas sont déjà implémentés comme des formes plurielles de nombres. En anglais, vous n'avez que 2 options: singulier et pluriel. Mais en russe par exemple, il existe 3 formes et ce n'est pas aussi simple qu'en anglais.

De plus, de nombreux traducteurs ont déjà une expérience de travail avec gettext.

Jetez un œil à CakePHP ou Drupal . Les deux multilingues sont activés. CakePHP comme exemple de localisation d'interface et Drupal comme exemple de traduction de contenu.

Pour l10n, l'utilisation de la base de données n'est pas du tout le cas. Ce sera des tonnes sur les requêtes. L'approche standard est d'obtenir toutes les données l10n en mémoire à un stade précoce (ou lors du premier appel à la fonction i10n si vous préférez le chargement paresseux). Il peut lire à partir d'un fichier .po ou de la base de données toutes les données à la fois. Et que de simplement lire les chaînes demandées à partir du tableau.

Si vous avez besoin d'implémenter un outil en ligne pour traduire l'interface, vous pouvez avoir toutes ces données dans la base de données, tout en enregistrant toutes les données dans un fichier pour les utiliser. Pour réduire la quantité de données en mémoire, vous pouvez diviser tous vos messages / chaînes traduits en groupes et charger uniquement les groupes dont vous avez besoin si cela est possible.

Donc, vous avez tout à fait raison dans votre # 3. À une exception près: il s'agit généralement d'un gros fichier et non d'un fichier par contrôleur. Parce qu'il est préférable pour les performances d'ouvrir un fichier. Vous savez probablement que certaines applications Web très chargées compilent tout le code PHP dans un seul fichier pour éviter les opérations sur les fichiers lors de l'appel de include / require.

À propos des URL. Google suggère indirectement d'utiliser la traduction:

pour indiquer clairement le contenu français: http://example.ca/fr/vélo-de-montagne.html

Je pense également que vous devez rediriger l'utilisateur vers le préfixe de langue par défaut, par exemple http://examlpe.com/about-us redirigera vers http://examlpe.com/en/about-us Mais si votre site n'utilise qu'une seule langue, vous n'ont pas du tout besoin de préfixes.

Consultez: http://www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 http: / /de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925

La traduction du contenu est une tâche plus difficile. Je pense qu'il y aura des différences avec différents types de contenu, par exemple des articles, des éléments de menu, etc. Mais au n ° 4, vous êtes dans la bonne voie. Jetez un œil à Drupal pour avoir plus d'idées. Il a un schéma de base de données suffisamment clair et une interface suffisamment bonne pour la traduction. Comme vous créez un article et sélectionnez sa langue. Et que vous pourrez plus tard le traduire dans d'autres langues.

Interface de traduction Drupal

Je pense que ce n'est pas un problème avec les slugs d'URL. Vous pouvez simplement créer une table séparée pour les slugs et ce sera la bonne décision. En utilisant également les bons index, il n'est pas difficile d'interroger la table même avec une énorme quantité de données. Et ce n'était pas une recherche de texte intégral, mais une correspondance de chaîne si utilisera le type de données varchar pour slug et vous pouvez également avoir un index sur ce champ.

PS Désolé, mon anglais est loin d'être parfait.

Yaroslav
la source
Merci pour le temps pris pour répondre à ma question. Votre anglais est assez bien pour que je le comprenne! Je vais déjà vous attribuer +1 pour vos efforts!
Joshua - Pendo
Yaroslav, encore une fois, merci pour votre réponse. Cependant, je suis allé avec 2 autres réponses qui étaient un peu plus complètes et expliquaient les méthodes utilisées derrière le code au lieu de souligner qu'il est déjà là.
Joshua - Pendo
2
Aucun problème. En effet cela répond plus complet et intéressant à lire pour moi aussi. Mais j'espère que ma réponse vous a également apporté quelque chose d'utile.
Yaroslav
12

Cela dépend de la quantité de contenu de votre site Web. Au début, j'ai utilisé une base de données comme toutes les autres personnes ici, mais cela peut prendre du temps pour scénariser tout le fonctionnement d'une base de données. Je ne dis pas que c'est une méthode idéale et surtout si vous avez beaucoup de texte, mais si vous voulez le faire rapidement sans utiliser de base de données, cette méthode pourrait fonctionner, cependant, vous ne pouvez pas permettre aux utilisateurs de saisir des données qui seront utilisés comme fichiers de traduction. Mais si vous ajoutez les traductions vous-même, cela fonctionnera:

Disons que vous avez ce texte:

Welcome!

Vous pouvez saisir ceci dans une base de données avec des traductions, mais vous pouvez également le faire:

$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");

Maintenant, si votre site Web utilise un cookie, vous avez ceci par exemple:

$_COOKIE['language'];

Pour vous faciliter la tâche, transformons-le en un code facilement utilisable:

$language=$_COOKIE['language'];

Si votre langue de cookie est le gallois et que vous disposez de ce morceau de code:

echo $welcome[$language];

Le résultat sera:

Croeso!

Si vous avez besoin d'ajouter beaucoup de traductions pour votre site Web et qu'une base de données est trop consommatrice, l'utilisation d'un tableau peut être une solution idéale.

user3749746
la source
1
Ce n'est nulle part près de la réponse que je demandais. De plus, plutôt que d'avoir toutes les langues disponibles sur chaque page, vous feriez mieux de créer des fichiers comme ceux lang.en.phpqui seront inclus et d'utiliser $lang['welcome']ce qui est déclaré dans chaque fichier.
Joshua - Pendo
7

Je vous suggère de ne pas vraiment dépendre de la base de données pour la traduction, cela pourrait être une tâche vraiment compliquée et pourrait être un problème extrême en cas de codage des données.

J'ai été confronté à un problème similaire il y a quelque temps et j'ai écrit la classe suivante pour résoudre mon problème

Objet: Locale \ Locale

<?php

  namespace Locale;

  class Locale{

// Following array stolen from Zend Framework
public $country_to_locale = array(
    'AD' => 'ca_AD',
    'AE' => 'ar_AE',
    'AF' => 'fa_AF',
    'AG' => 'en_AG',
    'AI' => 'en_AI',
    'AL' => 'sq_AL',
    'AM' => 'hy_AM',
    'AN' => 'pap_AN',
    'AO' => 'pt_AO',
    'AQ' => 'und_AQ',
    'AR' => 'es_AR',
    'AS' => 'sm_AS',
    'AT' => 'de_AT',
    'AU' => 'en_AU',
    'AW' => 'nl_AW',
    'AX' => 'sv_AX',
    'AZ' => 'az_Latn_AZ',
    'BA' => 'bs_BA',
    'BB' => 'en_BB',
    'BD' => 'bn_BD',
    'BE' => 'nl_BE',
    'BF' => 'mos_BF',
    'BG' => 'bg_BG',
    'BH' => 'ar_BH',
    'BI' => 'rn_BI',
    'BJ' => 'fr_BJ',
    'BL' => 'fr_BL',
    'BM' => 'en_BM',
    'BN' => 'ms_BN',
    'BO' => 'es_BO',
    'BR' => 'pt_BR',
    'BS' => 'en_BS',
    'BT' => 'dz_BT',
    'BV' => 'und_BV',
    'BW' => 'en_BW',
    'BY' => 'be_BY',
    'BZ' => 'en_BZ',
    'CA' => 'en_CA',
    'CC' => 'ms_CC',
    'CD' => 'sw_CD',
    'CF' => 'fr_CF',
    'CG' => 'fr_CG',
    'CH' => 'de_CH',
    'CI' => 'fr_CI',
    'CK' => 'en_CK',
    'CL' => 'es_CL',
    'CM' => 'fr_CM',
    'CN' => 'zh_Hans_CN',
    'CO' => 'es_CO',
    'CR' => 'es_CR',
    'CU' => 'es_CU',
    'CV' => 'kea_CV',
    'CX' => 'en_CX',
    'CY' => 'el_CY',
    'CZ' => 'cs_CZ',
    'DE' => 'de_DE',
    'DJ' => 'aa_DJ',
    'DK' => 'da_DK',
    'DM' => 'en_DM',
    'DO' => 'es_DO',
    'DZ' => 'ar_DZ',
    'EC' => 'es_EC',
    'EE' => 'et_EE',
    'EG' => 'ar_EG',
    'EH' => 'ar_EH',
    'ER' => 'ti_ER',
    'ES' => 'es_ES',
    'ET' => 'en_ET',
    'FI' => 'fi_FI',
    'FJ' => 'hi_FJ',
    'FK' => 'en_FK',
    'FM' => 'chk_FM',
    'FO' => 'fo_FO',
    'FR' => 'fr_FR',
    'GA' => 'fr_GA',
    'GB' => 'en_GB',
    'GD' => 'en_GD',
    'GE' => 'ka_GE',
    'GF' => 'fr_GF',
    'GG' => 'en_GG',
    'GH' => 'ak_GH',
    'GI' => 'en_GI',
    'GL' => 'iu_GL',
    'GM' => 'en_GM',
    'GN' => 'fr_GN',
    'GP' => 'fr_GP',
    'GQ' => 'fan_GQ',
    'GR' => 'el_GR',
    'GS' => 'und_GS',
    'GT' => 'es_GT',
    'GU' => 'en_GU',
    'GW' => 'pt_GW',
    'GY' => 'en_GY',
    'HK' => 'zh_Hant_HK',
    'HM' => 'und_HM',
    'HN' => 'es_HN',
    'HR' => 'hr_HR',
    'HT' => 'ht_HT',
    'HU' => 'hu_HU',
    'ID' => 'id_ID',
    'IE' => 'en_IE',
    'IL' => 'he_IL',
    'IM' => 'en_IM',
    'IN' => 'hi_IN',
    'IO' => 'und_IO',
    'IQ' => 'ar_IQ',
    'IR' => 'fa_IR',
    'IS' => 'is_IS',
    'IT' => 'it_IT',
    'JE' => 'en_JE',
    'JM' => 'en_JM',
    'JO' => 'ar_JO',
    'JP' => 'ja_JP',
    'KE' => 'en_KE',
    'KG' => 'ky_Cyrl_KG',
    'KH' => 'km_KH',
    'KI' => 'en_KI',
    'KM' => 'ar_KM',
    'KN' => 'en_KN',
    'KP' => 'ko_KP',
    'KR' => 'ko_KR',
    'KW' => 'ar_KW',
    'KY' => 'en_KY',
    'KZ' => 'ru_KZ',
    'LA' => 'lo_LA',
    'LB' => 'ar_LB',
    'LC' => 'en_LC',
    'LI' => 'de_LI',
    'LK' => 'si_LK',
    'LR' => 'en_LR',
    'LS' => 'st_LS',
    'LT' => 'lt_LT',
    'LU' => 'fr_LU',
    'LV' => 'lv_LV',
    'LY' => 'ar_LY',
    'MA' => 'ar_MA',
    'MC' => 'fr_MC',
    'MD' => 'ro_MD',
    'ME' => 'sr_Latn_ME',
    'MF' => 'fr_MF',
    'MG' => 'mg_MG',
    'MH' => 'mh_MH',
    'MK' => 'mk_MK',
    'ML' => 'bm_ML',
    'MM' => 'my_MM',
    'MN' => 'mn_Cyrl_MN',
    'MO' => 'zh_Hant_MO',
    'MP' => 'en_MP',
    'MQ' => 'fr_MQ',
    'MR' => 'ar_MR',
    'MS' => 'en_MS',
    'MT' => 'mt_MT',
    'MU' => 'mfe_MU',
    'MV' => 'dv_MV',
    'MW' => 'ny_MW',
    'MX' => 'es_MX',
    'MY' => 'ms_MY',
    'MZ' => 'pt_MZ',
    'NA' => 'kj_NA',
    'NC' => 'fr_NC',
    'NE' => 'ha_Latn_NE',
    'NF' => 'en_NF',
    'NG' => 'en_NG',
    'NI' => 'es_NI',
    'NL' => 'nl_NL',
    'NO' => 'nb_NO',
    'NP' => 'ne_NP',
    'NR' => 'en_NR',
    'NU' => 'niu_NU',
    'NZ' => 'en_NZ',
    'OM' => 'ar_OM',
    'PA' => 'es_PA',
    'PE' => 'es_PE',
    'PF' => 'fr_PF',
    'PG' => 'tpi_PG',
    'PH' => 'fil_PH',
    'PK' => 'ur_PK',
    'PL' => 'pl_PL',
    'PM' => 'fr_PM',
    'PN' => 'en_PN',
    'PR' => 'es_PR',
    'PS' => 'ar_PS',
    'PT' => 'pt_PT',
    'PW' => 'pau_PW',
    'PY' => 'gn_PY',
    'QA' => 'ar_QA',
    'RE' => 'fr_RE',
    'RO' => 'ro_RO',
    'RS' => 'sr_Cyrl_RS',
    'RU' => 'ru_RU',
    'RW' => 'rw_RW',
    'SA' => 'ar_SA',
    'SB' => 'en_SB',
    'SC' => 'crs_SC',
    'SD' => 'ar_SD',
    'SE' => 'sv_SE',
    'SG' => 'en_SG',
    'SH' => 'en_SH',
    'SI' => 'sl_SI',
    'SJ' => 'nb_SJ',
    'SK' => 'sk_SK',
    'SL' => 'kri_SL',
    'SM' => 'it_SM',
    'SN' => 'fr_SN',
    'SO' => 'sw_SO',
    'SR' => 'srn_SR',
    'ST' => 'pt_ST',
    'SV' => 'es_SV',
    'SY' => 'ar_SY',
    'SZ' => 'en_SZ',
    'TC' => 'en_TC',
    'TD' => 'fr_TD',
    'TF' => 'und_TF',
    'TG' => 'fr_TG',
    'TH' => 'th_TH',
    'TJ' => 'tg_Cyrl_TJ',
    'TK' => 'tkl_TK',
    'TL' => 'pt_TL',
    'TM' => 'tk_TM',
    'TN' => 'ar_TN',
    'TO' => 'to_TO',
    'TR' => 'tr_TR',
    'TT' => 'en_TT',
    'TV' => 'tvl_TV',
    'TW' => 'zh_Hant_TW',
    'TZ' => 'sw_TZ',
    'UA' => 'uk_UA',
    'UG' => 'sw_UG',
    'UM' => 'en_UM',
    'US' => 'en_US',
    'UY' => 'es_UY',
    'UZ' => 'uz_Cyrl_UZ',
    'VA' => 'it_VA',
    'VC' => 'en_VC',
    'VE' => 'es_VE',
    'VG' => 'en_VG',
    'VI' => 'en_VI',
    'VN' => 'vn_VN',
    'VU' => 'bi_VU',
    'WF' => 'wls_WF',
    'WS' => 'sm_WS',
    'YE' => 'ar_YE',
    'YT' => 'swb_YT',
    'ZA' => 'en_ZA',
    'ZM' => 'en_ZM',
    'ZW' => 'sn_ZW'
);

/**
 * Store the transaltion for specific languages
 *
 * @var array
 */
protected $translation = array();

/**
 * Current locale
 *
 * @var string
 */
protected $locale;

/**
 * Default locale
 *
 * @var string
 */
protected $default_locale;

/**
 *
 * @var string
 */
protected $locale_dir;

/**
 * Construct.
 *
 *
 * @param string $locale_dir            
 */
public function __construct($locale_dir)
{
    $this->locale_dir = $locale_dir;
}

/**
 * Set the user define localte
 *
 * @param string $locale            
 */
public function setLocale($locale = null)
{
    $this->locale = $locale;

    return $this;
}

/**
 * Get the user define locale
 *
 * @return string
 */
public function getLocale()
{
    return $this->locale;
}

/**
 * Get the Default locale
 *
 * @return string
 */
public function getDefaultLocale()
{
    return $this->default_locale;
}

/**
 * Set the default locale
 *
 * @param string $locale            
 */
public function setDefaultLocale($locale)
{
    $this->default_locale = $locale;

    return $this;
}

/**
 * Determine if transltion exist or translation key exist
 *
 * @param string $locale            
 * @param string $key            
 * @return boolean
 */
public function hasTranslation($locale, $key = null)
{
    if (null == $key && isset($this->translation[$locale])) {
        return true;
    } elseif (isset($this->translation[$locale][$key])) {
        return true;
    }

    return false;
}

/**
 * Get the transltion for required locale or transtion for key
 *
 * @param string $locale            
 * @param string $key            
 * @return array
 */
public function getTranslation($locale, $key = null)
{
    if (null == $key && $this->hasTranslation($locale)) {
        return $this->translation[$locale];
    } elseif ($this->hasTranslation($locale, $key)) {
        return $this->translation[$locale][$key];
    }

    return array();
}

/**
 * Set the transtion for required locale
 *
 * @param string $locale
 *            Language code
 * @param string $trans
 *            translations array
 */
public function setTranslation($locale, $trans = array())
{
    $this->translation[$locale] = $trans;
}

/**
 * Remove transltions for required locale
 *
 * @param string $locale            
 */
public function removeTranslation($locale = null)
{
    if (null === $locale) {
        unset($this->translation);
    } else {
        unset($this->translation[$locale]);
    }
}

/**
 * Initialize locale
 *
 * @param string $locale            
 */
public function init($locale = null, $default_locale = null)
{
    // check if previously set locale exist or not
    $this->init_locale();
    if ($this->locale != null) {
        return;
    }

    if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
        $this->detectLocale();
    } else {
        $this->locale = $locale;
    }

    $this->init_locale();
}

/**
 * Attempt to autodetect locale
 *
 * @return void
 */
private function detectLocale()
{
    $locale = false;

    // GeoIP
    if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

        $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

        if ($country) {

            $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
        }
    }

    // Try detecting locale from browser headers
    if (! $locale) {

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

            $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

            foreach ($languages as $lang) {

                $lang = str_replace('-', '_', trim($lang));

                if (strpos($lang, '_') === false) {

                    if (isset($this->country_to_locale[strtoupper($lang)])) {

                        $locale = $this->country_to_locale[strtoupper($lang)];
                    }
                } else {

                    $lang = explode('_', $lang);

                    if (count($lang) == 3) {
                        // language_Encoding_COUNTRY
                        $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                    } else {
                        // language_COUNTRY
                        $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                    }

                    return;
                }
            }
        }
    }

    // Resort to default locale specified in config file
    if (! $locale) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Check if config for selected locale exists
 *
 * @return void
 */
private function init_locale()
{
    if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Load a Transtion into array
 *
 * @return void
 */
private function loadTranslation($locale = null, $force = false)
{
    if ($locale == null)
        $locale = $this->locale;

    if (! $this->hasTranslation($locale)) {
        $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
    }
}

/**
 * Translate a key
 *
 * @param
 *            string Key to be translated
 * @param
 *            string optional arguments
 * @return string
 */
public function translate($key)
{
    $this->init();
    $this->loadTranslation($this->locale);

    if (! $this->hasTranslation($this->locale, $key)) {

        if ($this->locale !== $this->default_locale) {

            $this->loadTranslation($this->default_locale);

            if ($this->hasTranslation($this->default_locale, $key)) {

                $translation = $this->getTranslation($this->default_locale, $key);
            } else {
                // return key as it is or log error here
                return $key;
            }
        } else {
            return $key;
        }
    } else {
        $translation = $this->getTranslation($this->locale, $key);
    }
    // Replace arguments
    if (false !== strpos($translation, '{a:')) {
        $replace = array();
        $args = func_get_args();
        for ($i = 1, $max = count($args); $i < $max; $i ++) {
            $replace['{a:' . $i . '}'] = $args[$i];
        }
        // interpolate replacement values into the messsage then return
        return strtr($translation, $replace);
    }

    return $translation;
  }
}

Usage

 <?php
    ## /locale/en.php

    return array(
       'name' => 'Hello {a:1}'
       'name_full' => 'Hello {a:1} {a:2}'
   );

$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');

echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');

Comment ça fonctionne

{a:1}est remplacé par le 1er argument passé à la méthode Locale::translate('key_name','arg1') {a:2}est remplacé par le 2ème argument passé à la méthodeLocale::translate('key_name','arg1','arg2')

Comment fonctionne la détection

  • Par défaut, si geoipest installé, il retournera le code du pays par geoip_country_code_by_nameet si geoip n'est pas installé, le retour à l'en- HTTP_ACCEPT_LANGUAGEtête
Shushant
la source
En quoi une base de données serait-elle désordonnée? En raison des caractères possibles dans différentes langues? Jusqu'à présent, j'ai principalement des sites Web en anglais, français, néerlandais et allemand, donc ce n'est pas un problème pour le moment. Merci pour la réponse, mais comme ce n'est qu'une partie de la réponse, cela ne gagnera pas la prime.
Joshua - Pendo
Eh bien, je suppose que votre question ne vous est utile que s'il y a des gars qui envisageraient d'utiliser des langues comme l'hindi, le thaï, le chinois et l'arabe (ces langues prendront plus d'un octet pour représenter un caractère) contre vos langues requises. si vous utilisez db, le utf8_general_ciclassement est une manière appropriée de le faire.
Shushant
Je suis d'accord, j'ai moi-même un peu de piste là-bas. Merci de l'avoir signalé, les caractères multi bits sont également suffisamment importants pour être mentionnés dans cette question :)
Joshua - Pendo
5

Juste une réponse secondaire: utilisez absolument des URL traduites avec un identifiant de langue devant elles: http://www.domain.com/nl/over-ons Les
solutions hybrides ont tendance à se compliquer, alors je m'en tiens simplement à cela. Pourquoi? Parce que l'URL est essentielle pour le référencement.

A propos de la traduction de base de données: le nombre de langues est-il plus ou moins fixe? Ou plutôt imprévisible et dynamique? S'il est corrigé, je voudrais simplement ajouter de nouvelles colonnes, sinon aller avec plusieurs tables.

Mais généralement, pourquoi ne pas utiliser Drupal? Je sais que tout le monde veut créer son propre CMS car c'est plus rapide, plus léger, etc. etc. Mais c'est vraiment une mauvaise idée!

Remy
la source
1
Merci pour votre réponse. La raison pour laquelle je ne veux pas utiliser Drupal / Joomla est simple: je veux m'assurer que je connais tous les tenants et aboutissants de mon système, les failles pausibles, comment le code est construit (et important: ne pas construire par 300 programmeurs ensemble) . J'ai plus que suffisamment de raisons pour ne pas choisir l'open source. En plus de cela, je veux que mon entreprise soit un facteur important pour mes clients, c'est une mauvaise chose qu'ils puissent s'adresser à n'importe quel autre développeur et me laisser sans rien.
Joshua - Pendo
7
Je pense que toutes ces raisons sont contestées dans des tonnes d'articles. Espérons que vos clients ne vous choisiront pas exactement car vous disposez d'un CMS propriétaire que personne d'autre ne peut maintenir. Mais de toute façon, c'est une discussion totalement différente.
Remy
1
Je comprends votre point de vue, mais je préfère un système pour lequel je connais tous les tenants et les aboutissants et je ne ressens rien pour compter sur quelqu'un d'autre fonctionne lorsque j'utilise un plugin.
Joshua - Pendo
1
De plus, j'ai tendance à bien documenter mon travail, puisque je suis une "armée d'un homme", les gens qui travaillent pour moi ne devraient pas avoir de mal à connaître le système.
Joshua - Pendo
La mauvaise idée est de choisir Drupal et même Google dit qu'ils ne se soucient pas de savoir si l'URL est traduite ou non. Il doit cependant contenir un identifiant de paramètres régionaux.
undefinedman le
5

Je ne vais pas tenter d’affiner les réponses déjà données. Au lieu de cela, je vais vous parler de la façon dont mon propre framework PHP OOP gère les traductions.

En interne, mon framework utilise des codes comme en, fr, es, cn et ainsi de suite. Un tableau contient les langues prises en charge par le site Web: array ('en', 'fr', 'es', 'cn') Le code de langue est passé via $ _GET (lang = fr) et s'il n'est pas passé ou non valide, il est défini sur la première langue du tableau. Ainsi, à tout moment de l'exécution du programme et dès le début, la langue actuelle est connue.

Il est utile de comprendre le type de contenu qui doit être traduit dans une application typique:

1) messages d'erreur de classes (ou code de procédure) 2) messages non d'erreur de classes (ou code de procédure) 3) contenu de la page (généralement stocké dans une base de données) 4) chaînes à l'échelle du site (comme le nom du site Web) 5) script- chaînes spécifiques

Le premier type est simple à comprendre. Fondamentalement, nous parlons de messages comme "Impossible de se connecter à la base de données ...". Ces messages ne doivent être chargés que lorsqu'une erreur se produit. Ma classe de manager reçoit un appel des autres classes et en utilisant les informations passées en tant que paramètres, va simplement dans le dossier de classe pertinent et récupère le fichier d'erreur.

Le deuxième type de message d'erreur ressemble plus aux messages que vous recevez lorsque la validation d'un formulaire a mal tourné. ("Vous ne pouvez pas laisser ... vide" ou "Veuillez choisir un mot de passe de plus de 5 caractères"). Les chaînes doivent être chargées avant l'exécution de la classe, je sais ce que c'est

Pour le contenu réel de la page, j'utilise une table par langue, chaque table étant préfixée par le code de la langue. Donc en_content est la table avec le contenu en anglais, es_content est pour l'Espagne, cn_content pour la Chine et fr_content est le contenu français.

Le quatrième type de chaîne est pertinent sur l'ensemble de votre site Web. Ceci est chargé via un fichier de configuration nommé en utilisant le code de la langue, c'est-à-dire en_lang.php, es_lang.php et ainsi de suite. Dans le fichier de langue global, vous devrez charger les langues traduites telles que array ('Anglais', 'Chinois', 'Espagnol', 'Français') dans le fichier global anglais et array ('Anglais', 'Chinois', ' Espagnol ',' Francais ') dans le fichier français. Ainsi, lorsque vous remplissez une liste déroulante pour la sélection de la langue, elle est dans la bonne langue;)

Enfin, vous avez les chaînes spécifiques au script. Donc, si vous écrivez une application de cuisson, cela pourrait être "Votre four n'était pas assez chaud".

Dans mon cycle d'application, le fichier de langue global est chargé en premier. Vous y trouverez non seulement des chaînes globales (comme "Jack's Website") mais également des paramètres pour certaines classes. Fondamentalement, tout ce qui dépend de la langue ou de la culture. Certaines de ces chaînes contiennent des masques pour les dates (MMJJAAAA ou JJMMAAAAA) ou des codes de langue ISO. Dans le fichier de langue principal, j'inclus des chaînes pour les classes individuelles car il y en a si peu.

Le deuxième et dernier fichier de langue lu à partir du disque est le fichier de langage de script. lang_en_home_welcome.php est le fichier de langue pour le script home / welcome. Un script est défini par un mode (home) et une action (welcome). Chaque script a son propre dossier avec les fichiers de configuration et de langue.

Le script extrait le contenu de la base de données en nommant la table de contenu comme expliqué ci-dessus.

En cas de problème, le gestionnaire sait où obtenir le fichier d'erreur dépendant de la langue. Ce fichier n'est chargé qu'en cas d'erreur.

La conclusion est donc évidente. Réfléchissez aux problèmes de traduction avant de commencer à développer une application ou un framework. Vous avez également besoin d'un flux de travail de développement qui intègre des traductions. Avec mon framework, je développe l'ensemble du site en anglais puis je traduis tous les fichiers pertinents.

Juste un dernier mot sur la façon dont les chaînes de traduction sont implémentées. Mon framework a un seul global, le $ manager, qui exécute les services disponibles pour tout autre service. Ainsi, par exemple, le service de formulaire s'empare du service html et l'utilise pour écrire le html. L'un des services de mon système est le service de traduction. $ Translator-> set ($ service, $ code, $ string) définit une chaîne pour la langue courante. Le fichier de langue est une liste de telles déclarations. $ Translator-> get ($ service, $ code) récupère une chaîne de traduction. Le $ code peut être numérique comme 1 ou une chaîne comme 'no_connection'. Il ne peut y avoir de conflit entre les services car chacun a son propre espace de noms dans la zone de données du traducteur.

Je poste ceci ici dans l'espoir que cela épargnera à quelqu'un la tâche de réinventer la roue comme je devais le faire il y a quelques années.

JG Estiot
la source
4

J'ai eu le même problème il y a quelque temps, avant de commencer à utiliser le framework Symfony .

  1. Utilisez simplement une fonction __ () qui a des aramètres pageId (ou objectId, objectTable décrit dans # 2), la langue cible et un paramètre facultatif de la langue de secours (par défaut). La langue par défaut pourrait être définie dans une configuration globale afin d'avoir un moyen plus simple de la changer plus tard.

  2. Pour stocker le contenu dans la base de données, j'ai utilisé la structure suivante: (pageId, langue, contenu, variables).

    • pageId serait un FK de votre page que vous souhaitez traduire. si vous avez d'autres objets, comme des actualités, des galeries ou autre, divisez-les simplement en 2 champs objectId, objectTable.

    • language - évidemment, il stockerait la chaîne de langue ISO EN_en, LT_lt, EN_us etc.

    • content - le texte que vous souhaitez traduire avec les caractères génériques pour le remplacement des variables. Exemple "Bonjour monsieur. %% name %%. Le solde de votre compte est de %% balance %%."

    • variables - les variables codées json. PHP fournit des fonctions pour les analyser rapidement. Exemple "nom: Laurynas, solde: 15,23".

    • vous avez également mentionné le champ de limaces. vous pouvez l'ajouter librement à ce tableau juste pour avoir un moyen rapide de le rechercher.

  3. Vos appels à la base de données doivent être réduits au minimum avec la mise en cache des traductions. Il doit être stocké dans un tableau PHP, car c'est la structure la plus rapide en langage PHP. La manière dont vous réaliserez cette mise en cache dépend de vous. D'après mon expérience, vous devriez avoir un dossier pour chaque langue prise en charge et un tableau pour chaque pageId. Le cache doit être reconstruit après la mise à jour de la traduction. SEULE la matrice modifiée doit être régénérée.

  4. je pense avoir répondu à cela dans # 2

  5. votre idée est parfaitement logique. celui-ci est assez simple et je pense que cela ne vous posera aucun problème.

Les URL doivent être traduites à l'aide des slugs stockés dans la table de traduction.

Derniers mots

il est toujours bon de rechercher les meilleures pratiques, mais ne réinventez pas la roue. il suffit de prendre et d'utiliser les composants de frameworks bien connus et de les utiliser.

jetez un œil au composant de traduction Symfony . Cela pourrait être une bonne base de code pour vous.

Laurynas Mališauskas
la source
Merci pour le commentaire, aswel un +1 pour votre temps pris. Laravel (dans mon cas) utilise des pièces Symfony si je ne me trompe pas, vous avez donc parfaitement raison de ne pas réinventer la roue. J'ai commencé cette question (et cette prime) pour avoir un aperçu de la façon dont les autres font les traductions, je commence à croire qu'il existe de nombreuses meilleures pratiques :-)
Joshua - Pendo
1

Je me suis posé des questions liées à maintes reprises, puis je me suis perdu dans les langues formelles ... mais juste pour vous aider un peu, j'aimerais partager quelques résultats:

Je recommande de jeter un œil aux CMS avancés

Typo3pour PHP (je sais qu'il y a beaucoup de trucs mais c'est celui que je pense être le plus mature)

Plone dans Python

Si vous découvrez que le Web en 2013 devrait fonctionner différemment, partez de zéro. Cela signifierait constituer une équipe de personnes hautement qualifiées / expérimentées pour construire un nouveau CMS. Vous aimeriez peut-être jeter un coup d'œil au polymère à cette fin.

S'il s'agit de codage et de sites Web multilingues / prise en charge de la langue maternelle, je pense que chaque programmeur devrait avoir une idée de l'unicode. Si vous ne connaissez pas l'Unicode, vous gâcherez certainement vos données. N'allez pas avec les milliers de codes ISO. Ils ne vous feront économiser que de la mémoire. Mais vous pouvez tout faire avec UTF-8, même stocker des caractères chinois. Mais pour cela, vous devez stocker des caractères de 2 ou 4 octets, ce qui en fait essentiellement un utf-16 ou un utf-32.

S'il s'agit d'encodage d'URL, là encore, il ne faut pas mélanger les encodages et être conscient qu'au moins pour le nom de domaine, il existe des règles définies par différents lobbies qui fournissent des applications comme un navigateur. par exemple, un domaine pourrait être très similaire comme:

ьankofamerica.com ou bankofamerica.com même nom mais différent;)

Bien sûr, vous avez besoin que le système de fichiers fonctionne avec tous les encodages. Un autre avantage pour unicode utilisant le système de fichiers utf-8.

S'il s'agit de traductions, pensez à la structure des documents. par exemple un livre ou un article. Vous avez les docbookspécifications pour comprendre ces structures. Mais en HTML, il ne s'agit que de blocs de contenu. Vous aimeriez donc avoir une traduction à ce niveau, également au niveau de la page Web ou du domaine. Donc, si un bloc n'existe pas, il n'est tout simplement pas là, si une page Web n'existe pas, vous serez redirigé vers le niveau de navigation supérieur. Si un domaine doit être complètement différent dans la structure de navigation, alors .. c'est une structure complètement différente à gérer. Cela peut déjà être fait avec Typo3.

S'il s'agit de frameworks, les plus matures que je connaisse, pour faire des trucs généraux comme MVC (mot à la mode, je déteste vraiment ça! Comme "performance" Si vous voulez vendre quelque chose, utilisez le mot performance et featurerich et vous vendez ... quoi l'enfer) est Zend. Il s'est avéré être une bonne chose d'apporter des standards aux codeurs php chaos. Mais, typo3 a également un Framework en plus du CMS. Récemment, il a été réaménagé et s'appelle maintenant flow3. Les frameworks couvrent bien sûr l'abstraction de base de données, la création de modèles et les concepts de mise en cache, mais ont des atouts individuels.

S'il s'agit de la mise en cache ... cela peut être extrêmement compliqué / multicouche. En PHP, vous penserez à accellerator, opcode, mais aussi html, httpd, mysql, xml, css, js ... toutes sortes de caches. Bien sûr, certaines parties doivent être mises en cache et les parties dynamiques comme les réponses de blog ne le devraient pas. Certains doivent être demandés via AJAX avec des URL générées. JSON, hashbangs etc.

Ensuite, vous aimeriez que n'importe quel petit composant de votre site Web soit accessible ou géré uniquement par certains utilisateurs , donc conceptuellement cela joue un grand rôle.

Vous aimeriez aussi faire des statistiques , peut-être avoir un système distribué / un facebook de facebook, etc. tout logiciel à construire au-dessus de vos cms over the top ... donc vous avez besoin de différents types de bases de données en mémoire, bigdata, xml, que ce soit .

eh bien, je pense que c'est assez pour le moment. Si vous n'avez pas entendu parler de typo3 / plone ou de frameworks mentionnés, vous en avez assez pour étudier. Sur ce chemin, vous trouverez de nombreuses solutions aux questions que vous n'avez pas encore posées.

Si alors vous pensez, faisons un nouveau CMS parce que son 2013 et php sont sur le point de mourir de toute façon, alors vous êtes invités à rejoindre tout autre groupe de développeurs, espérons ne pas vous perdre.

Bonne chance!

Et btw. Et si les gens n'auront plus de sites Web à l'avenir? et nous serons tous sur google +? J'espère que les développeurs deviendront un peu plus créatifs et feront quelque chose d'utile (pour ne pas être assimilé par le borgle)

//// Edit /// Juste une petite pensée pour votre application existante:

Si vous avez un CMS php mysql et que vous vouliez intégrer le support multilang. vous pouvez soit utiliser votre table avec une colonne supplémentaire pour n'importe quelle langue ou insérer la traduction avec un identifiant d'objet et un identifiant de langue dans le même tableau ou créer une table identique pour n'importe quelle langue et y insérer des objets, puis faire une union de sélection si vous le souhaitez pour les afficher tous. Pour la base de données, utilisez utf8 general ci et bien sûr dans le front / backend, utilisez le texte / encodage utf8. J'ai utilisé des segments de chemin d'URL pour les URL de la manière que vous avez déjà expliquée, comme

domain.org/en/about, vous pouvez mapper l'ID de langue à votre table de contenu. de toute façon, vous devez avoir une carte des paramètres pour vos URL afin que vous souhaitiez définir un paramètre à mapper à partir d'un segment de chemin dans votre URL qui serait par exemple

domain.org/en/about/employees/IT/administrators/

configuration de recherche

pageid | URL

1 | /about/employees/../ ..

1 | /../about/employees../../

mapper les paramètres au segment de chemin de l'URL ""

$parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
$parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
$parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result 

$websiteconfig[]=$userwhatever;
$websiteconfig[]=$parameterlist;
$someparameterlist[] = array("branch"=>$someid);
$someparameterlist[] = array("employertype"=>$someid);
function getURL($someparameterlist){ 
// todo foreach someparameter lookup pathsegment 
return path;
}

par exemple, cela a déjà été couvert au poste supérieur.

Et pour ne pas oublier, vous devez "réécrire" l'url dans votre fichier php générant qui serait dans la plupart des cas index.php

Dr Dama
la source
Merci pour le commentaire, il y a certainement des choses auxquelles je devrais réfléchir. J'utilise l'encodage utf8 depuis quelques années déjà, j'avais l'habitude de lutter avec le personnage une fois ;-) De l'autre côté, le type de CMS / Framework ne devait pas être un facteur dans votre réponse car je cherchais un méthode indépendante de la plate-forme comme si nous codions à partir de zéro.
Joshua - Pendo
si vous voulez vraiment coder à partir de zéro, je vous recommande de jeter un oeil à Dartlang et polymère. Étant donné que dartlang fonctionne dans le navigateur et prend en charge 32 et 64 bits, il peut être utilisé dans la plupart des cas côté serveur et dispose d'un compilateur dart2js, il vaut vraiment la peine d'être étudié. Si les gens parlent d'indépendance de la plateforme, ils pensent à java ... nous savons ce que cela signifie. Buildprocess ... Je pense que j'utiliserais JSON pour l'échange. site Web généré côté client avec hashbangs et côté serveur .. faites tout ce que vous voulez pour assurer la collaboration.
Dr Dama
La logique de mise en page et de génération de la base de données est la tâche principale. Nobodys va faire ça ici pour vous ... mais l'idée elle-même est ce qui compte. Comme je ne me soucie pas des lobbies mais de faire avancer les choses, j'espère que vous pourrez créer des modèles et partager des choses. Je travaille actuellement sur des tâches similaires. Mais je suis toujours en train de planifier. Je considère Typo3 comme un backend et crée une nouvelle structure client. Le modèle multilingue est résolu dans le backend et partagera des informations de manière dédiée pour les moteurs de recherche / webservices. Quoi qu'il en soit, tout est sensible au contexte et une tâche de construction continue
Dr Dama
-1

Travail de base de données:

Créer une table de langues 'languages':

Des champs:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Créez une table dans la base de données 'content':

Des champs:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

Travail frontal:

Lorsque l'utilisateur sélectionne une langue dans la liste déroulante ou dans n'importe quelle zone, enregistrez l'identifiant de la langue sélectionnée dans la session comme,

$_SESSION['language']=1;

Récupérez maintenant les données de la table de base de données «contenu» en fonction de l'ID de langue stocké dans la session.

Les détails peuvent être trouvés ici http://skillrow.com/multilingual-website-in-php-2/

user3445130
la source
1
C'est un moyen d'intégration linguistique simple alors nécessaire, avez-vous même essayé de lire les articles complets et les réponses données?
Joshua - Pendo
-2

En tant que personne vivant au Québec où presque tout le site est français et anglais ... j'ai essayé beaucoup sinon la plupart des plugins multilingues pour WP ... la seule solution utile qui fonctionne avec tout mon site est mQtranslate ... je vis et meurs avec!

https://wordpress.org/plugins/mqtranslate/

ménardmam
la source
1
ouais bien, WP n'était pas un facteur de la question. Cela aurait pu être un commentaire aswel
Joshua - Pendo
-3

Qu'en est-il de WORDPRESS + MULTI-LANGUAGE SITE BASIS(plugin)? le site aura une structure:

  • example.com/ fra / category1 / ....
  • example.com/ fra / ma-page ....
  • example.com/ rus / category1 / ....
  • exemple.com/ rus / ma-page ....

Le plugin fournit une interface pour la traduction de toutes les phrases, avec une logique simple:

(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"

alors il peut être sorti:
echo translate('my_title', LNG); // LNG is auto-detected

ps cependant, vérifiez si le plugin est toujours actif.

T.Todua
la source
3
et n'est pas "Holla userio" en espagnol est "Hola
Usuario
1
Lol Holla userio, c'était drôle!
spekdrum
pour la raison que je ne connaissais pas l'espagnol (juste exemple utilisé), dépêchez-vous de voter! :)
T.Todua
-5

Une option très simple qui fonctionne avec n'importe quel site Web où vous pouvez télécharger du Javascript est www.multilingualizer.com

Il vous permet de mettre tout le texte de toutes les langues sur une seule page, puis de masquer les langues que l'utilisateur n'a pas besoin de voir. Fonctionne bien.

Paul Martin
la source
Attention, le référencement serait très mauvais! De plus, vous chargez tout le contenu alors que vous n'en avez besoin que d'une partie, ce qui est vraiment une mauvaise pratique.
Hafenkranich
trucs bizarres que le site est uniquement en anglais ... pourquoi ils n'utilisent pas leur solution ??
eduardo.lopes