web-dev-qa-db-fra.com

Site Web multilingue sur les meilleures pratiques

Cela fait plusieurs mois que je me bats avec cette question, mais je n’étais pas dans une situation dans laquelle je devais explorer toutes les options possibles auparavant. En ce moment, j’ai le sentiment 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.

Permettez-moi d'abord de dessiner 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 longtemps. 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 méthode plus propre pour coder PHP. Sidenote: Laraval4 should be no factor in your answer. Je recherche des méthodes générales de traduction indépendantes de la plate-forme/du framework.

Ce qui devrait être traduit

Comme le système que je recherche doit être aussi convivial que possible, la méthode de gestion de la traduction doit être intégrée au système de gestion de contenu. Il ne devrait pas être nécessaire de démarrer une connexion FTP pour modifier les fichiers de traduction ou les modèles analysés html/php.

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

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

Comme j'ai cherché, lu et essayé des choses moi-même déjà. Il y a plusieurs options que j'ai. Mais je n'ai toujours pas l'impression d'avoir atteint la meilleure méthode pour ce que je recherche vraiment. À l'heure actuelle, c'est ce que j'ai proposé, mais cette méthode a également des effets secondaires.

  1. Modèles analysés par PHP : le système de modèles doit être analysé par PHP. De cette façon, je peux insérer les paramètres traduits dans le HTML sans avoir à ouvrir les modèles et à les modifier. De plus, PHP modèles analysés me donne la possibilité d’avoir un seul modèle pour l’ensemble du site Web au lieu d’avoir un sous-dossier pour chaque langue (que j’avais eu auparavant). La méthode pour atteindre cette cible peut être soit 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 : je n'ai peut-être pas besoin de le mentionner à nouveau. Mais la solution devrait être basée sur une base de données. Le CMS a pour objectif d'être orienté objet et MVC. Il me faudrait donc penser à une structure de données logique pour les chaînes. Tandis que mes modèles seraient structurés: modèles/Contrôleur/View.php, cette structure serait peut-être plus logique: Controller.View.parameter. La table de base de données aurait ces champs longs avec un champ value. 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: name sont faciles à comprendre pour l'éditeur.
  3. Charge de base de données faible : Bien entendu, le système ci-dessus provoquerait des charges de charge de base de données si ces chaînes étaient en cours de chargement. Par conséquent, il me faudrait un système de mise en cache qui restitue les fichiers de langue dès qu'ils sont édités/enregistrés dans l'environnement d'administration. Les fichiers étant générés, une bonne configuration du système de fichiers est également nécessaire. Je suppose que nous pouvons utiliser languages/en_EN/Controller/View.php ou .ini, ce qui vous convient le mieux. Peut-être qu'un .ini sera même analysé plus rapidement à la fin. Ce fould devrait contenir les données dans le format parameter=value;. J'imagine 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 étendue globale afin d'éviter que les paramètres ne se écrasent.
  4. Traduction de la table de base de données : c'est 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, News et News_translations) est une option, mais il faut beaucoup de travail pour obtenir un bon système. Une des choses que j'ai suggérées est basée sur un système data versioning que j'ai écrit: il existe un nom de table de base de données Translations, cette table a une combinaison unique de language, tablename et primarykey. Par exemple: en_En/News/1 (en référence à la version anglaise du journal avec ID = 1). Mais cette méthode présente deux inconvénients majeurs: tout d’abord, cette table a tendance à être 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 dans la table. Par exemple. la recherche du slug SEO de l'article 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 crois pas que ce professionnel surpondère les inconvénients.
  5. Travail front-end : le front-end aurait également besoin d'un peu de 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 manière, le script peut générer un menu déroulant pour sélectionner une langue et le système de gestion peut décider automatiquement des traductions pouvant ê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 d’une vue ou de la traduction correcte d’un élément de contenu sur le site Web.

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

Outre tout ce que j'ai expliqué ici, il me reste encore quelque chose que je n'ai pas encore décidé, cela ressemble à une simple question, 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-us et l'anglais est ma langue par défaut. Cette URL doit-elle être traduite en http://www.domain.com/over-ons lorsque je choisis le néerlandais comme langue? Ou devrions-nous aller facilement et changer simplement le contenu de la page visible à /about. La dernière chose ne semble pas une option valide, car cela générerait plusieurs versions de la même URL. Cette indexation du contenu échouera de la bonne manière.

Une autre option consiste à utiliser http://www.domain.com/nl/about-us à la place. 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-us et 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-us vers http://www.domain.com/about-us ... À mes yeux, c'est la meilleure solution car, lorsque le CMS est configuré pour une seule langue, il n'est pas nécessaire que cette identification de langue se trouve dans l'URL.

Et une troisième option est une combinaison des deux options: utiliser l’URL "_ (ID-langage sans identification)" (http://www.domain.com/about-us) pour la langue principale. Et utilisez une URL avec un slug SEO traduit pour les sous-langages: http://www.domain.com/nl/over-ons & http://www.domain.com/de/uber-uns

J'espère que ma question vous fait craquer la tête, ils ont craqué la mienne à coup sûr! Cela m'a déjà aidé à résoudre les problèmes en tant que question ici. M'a donné la possibilité de passer en revue les méthodes que j'ai déjà utilisées et les idées que j'ai pour mon prochain CMS.

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

// Edit #1:

J'ai oublié de mentionner: la fonction __ () est un alias permettant de traduire une chaîne donnée. Au sein de cette méthode, il devrait évidemment y avoir une sorte de méthode de secours dans laquelle le texte par défaut est chargé lorsqu'il n'y a pas encore de traduction. Si la traduction est manquante, insérez-la ou régénérez le fichier de traduction.

164
Joshua - Pendo

Sujet du sujet

Un site multilingue présente trois aspects distincts:

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

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

De quoi l'URL peut-elle être composée?

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

  • certains navigateurs transforment les caractères non-ASCII tels que _'ч'_ ou _'ž'_ en _'%D1%87'_ et _'%C5%BE'_
  • si l'utilisateur a des thèmes personnalisés, il est fort probable que la police du thème ne comporte pas de symboles pour ces lettres

En fait, j’ai essayé d’approcher les IDN il ya quelques années dans le cadre d’un projet basé sur Yii (cadre épouvantable, IMHO). J'ai rencontré les deux problèmes mentionnés ci-dessus avant de supprimer cette solution. En outre, je soupçonne que cela pourrait être un vecteur d’attaque.

Options disponibles ... comme je les vois.

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

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

  • _http://site.tld/[:language]/[:query]_: où _[:language]_ une partie de l'URL définit le choix de la langue et _[:query]_ sert uniquement à 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 segment _[:query]_; et deux sources supplémentaires:

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

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

Vous devrez parcourir tous les segments du motif. Recherchez les traductions possibles pour tous ces segments et déterminez quelle langue a été utilisée. Les deux sources supplémentaires (cookie et en-tête) seraient utilisées pour résoudre les conflits de routage lorsqu'ils surviennent (et non "si").

Prenons par exemple: _http://site.tld/blog/novinka_.

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

Comme vous pouvez déjà le constater, en russe "блог" sera translittéré en "blog". Ce qui signifie que pour la première partie de _[:query]_, dans le meilleur des cas ), vous obtiendrez la liste _['en', 'ru']_ langues possibles. Ensuite, vous prenez le segment suivant - "novinka". La liste des possibilités n’a peut-être qu’une langue: _['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 échoue, vous choisissez la langue par défaut du site.

Langue en paramètre

L'alternative consiste à utiliser une URL pouvant être définie comme étant _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 vous savez déjà quelle langue utiliser.

Il existe également une source de langue secondaire: la valeur du cookie. Mais ici, il est inutile de jouer avec l'en-tête Accept-Language, car vous ne traitez pas un nombre inconnu de langues possibles en cas de "démarrage à froid" (lorsque l'utilisateur ouvre pour la première fois le site avec une requête personnalisée).

Au lieu de cela, vous avez 3 options simples et hiérarchisées:

  1. si le segment _[:language]_ 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 de routage).

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

Oui, techniquement, vous pouvez combiner les deux approches, mais cela compliquerait le processus et ne concernerait que les personnes souhaitant modifier manuellement l'URL de _http://site.tld/en/news_ à _http://site.tld/de/news_ et s'attendant à ce que la page de news passe en allemand.

Mais même ce cas pourrait probablement être atténué en utilisant une valeur de cookie (qui contiendrait des informations sur le choix précédent de 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]_ comme option la plus judicieuse.

De plus, dans la situation réelle de Word, vous auriez une 3ème partie majeure dans l'URL: "titre". Comme dans le nom du produit dans la boutique en ligne ou le titre de l'article dans le site d'informations.

Exemple: _http://site.tld/en/news/article/121415/EU-as-global-reserve-currency_

Dans ce cas, _'/news/article/121415'_ correspond à la requête et _'EU-as-global-reserve-currency'_ correspond à title. Purement à des fins de référencement.

Peut-on le faire à Laravel?

Un peu, mais pas par défaut.

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

C'est acheminé. Et maintenant?

En conséquence de tout cela, vous vous retrouveriez avec deux informations précieuses: le langage actuel et les segments de requête traduits. Ces valeurs peuvent ensuite être utilisées pour envoyer à la classe (s) qui produira le résultat.

En gros, 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',
];
_

Ce que vous utilisez simplement 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.

103
tereško

L'implémentation d'i18n sans impact sur les performances à l'aide d'un pré-processeur, comme suggéré par Thomas Bley

Au travail, nous avons récemment implémenté i18n sur quelques-unes de nos propriétés, et l’une des choses avec lesquelles nous avons toujours lutté était le succès de la traduction à la volée, puis j’ai découvert ce blog génial post de Thomas Bley qui a inspiré la façon dont nous utilisons i18n pour traiter des charges de trafic importantes avec des problèmes de performances minimes.

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

Les balises de traduction

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

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

Notez que Thomas suggère d'utiliser la base anglaise dans le fichier. Nous ne le faisons pas car 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 facile de permettre à un utilisateur de les modifier à l'intérieur du CMS, il suffit d'obtenir les paires de clés par un preg_split sur \n ou = et de permettre au CMS d'écrire sur le INI fichiers.

Le composant de pré-traitement

Thomas suggère essentiellement d'utiliser un "compilateur" "juste à temps" (bien que ce soit en fait un préprocesseur) comme celui-ci pour récupérer vos fichiers de traduction et créer des fichiers statiques PHP sur 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 l'expression régulière fonctionne, je ne l'ai pas copiée à partir du serveur de notre société, mais vous pouvez voir comment l'opération fonctionne.

Comment l'appeler

Encore une fois, cet exemple vient 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 nous le récupérons à chaque demande. Vous pouvez combiner cela avec un paramètre facultatif $_GET pour remplacer la langue, mais je ne suggère pas de sous-domaine par langue ou de page par langue car il sera plus difficile de voir quelles pages sont populaires et ce qui le sera. réduisez la valeur des liens entrants car vous les aurez moins diffusés.

Pourquoi utiliser cette méthode?

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

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

Obtenir le contenu traduit de la base de données

Nous ajoutons simplement une colonne pour le contenu dans notre base de données appelée language, puis nous utilisons une méthode d'accesseur pour la constante LANG que nous avons définie précédemment, de sorte que nos appels SQL (avec 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 sur id et language afin que l'article 54 puisse exister dans toutes les langues. Notre valeur par défaut LANG est en_US si elle n'est pas spécifiée.

URL Slug Translation

Je combinerais deux choses ici, l'une est une fonction dans votre bootstrap qui accepte un paramètre $_GET pour la langue et remplace la variable cookie, et une autre est le routage qui accepte plusieurs slug. 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 sur lequel vous pouvez facilement écrire depuis votre panneau d'administration. JSON ou XML peut constituer 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 cela offre un avantage par rapport aux traductions prétraitées.

Traductions frontales

Je trouve cela intéressant depuis longtemps, mais il y a quelques mises en garde. Par exemple, vous devez mettre à la disposition de l'utilisateur la liste complète des phrases que vous souhaitez traduire sur votre site Web. Cela pourrait poser problème si vous gardez caché certaines zones du site ou si vous ne leur avez pas autorisé l'accès.

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

Traductions pilotées par base de données

Les vitesses de connectivité des bases de données PHP ne sont pas un sujet de préoccupation, et cela ajoute à la charge de travail déjà lourde d'appeler une fonction sur chaque phrase à traduire. Les problèmes de performance et d’évolutivité semblent insurmontables avec cette approche.

50
Glitch Desire

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

En utilisant gettext, vous aurez un outil puissant dans lequel de nombreux cas sont déjà implémentés, comme des formes plurielles de nombres. En anglais, vous avez seulement 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à l'expérience de travailler avec gettext.

Jetez un oeil à CakePHP ou Drupal . Les deux multilingue activé. CakePHP comme exemple de localisation d’interface et Drupal comme exemple de traduction de contenu.

Pour l10n, utiliser une base de données n’est pas du tout le cas. Ce sera des tonnes de requêtes. L’approche standard consiste à mettre 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 un chargement paresseux). Il peut lire à partir d'un fichier .po ou d'une base de données toutes les données à la fois. Et que de simplement lire les chaînes demandées dans array.

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, mais vous pouvez néanmoins enregistrer toutes les données dans un fichier pour pouvoir les utiliser. Pour réduire la quantité de données en mémoire, vous pouvez fractionner tous vos messages/chaînes traduits en groupes et ne charger que les groupes dont vous avez besoin, si cela est possible.

Donc, vous avez parfaitement 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 que les performances ouvrent un fichier. Vous savez probablement que certaines applications Web surchargées compilent tout le code PHP dans un fichier pour éviter les opérations sur les fichiers lorsque include/require est appelé.

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

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

De plus, je pense 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 utilise une seule langue, vous n'avez pas du tout besoin de préfixes.

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

Traduire le contenu est une tâche plus difficile. Je pense que ce sera quelques différences avec différents types de contenu, par exemple. articles, éléments de menu, etc. Mais en n ° 4, vous êtes dans le bon sens. Jetez un coup d'œil dans Drupal pour avoir plus d'idées. Il possède 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 la langue pour celui-ci. Et que vous pourrez ensuite traduire dans d'autres langues.

Drupal translation interface

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

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

14
Yaroslav

Cela dépend du 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 beaucoup de temps de scripter tout le fonctionnement d'une base de données. Je ne dis pas que c'est une méthode idéale, 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 entrer ceci dans une base de données avec des traductions, mais vous pouvez aussi 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 le rendre facile, transformons-le en un code facile à utiliser:

$language=$_COOKIE['language'];

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

echo $welcome[$language];

Le résultat sera:

Croeso!

Si vous devez ajouter de nombreuses traductions pour votre site Web et qu'une base de données est trop consommatrice, utiliser un tableau peut être une solution idéale.

11
user3749746

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

J’ai eu à faire face au même problème il ya 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 premier argument transmis à la méthode Locale::translate('key_name','arg1'){a:2} est remplacé par le deuxième argument transmis à la méthode Locale::translate('key_name','arg1','arg2')

Comment fonctionne la détection

  • Par défaut si geoip est installé, il retournera le code pays par geoip_country_code_by_name et si geoip n'est pas installé, le repli sur l'en-tête HTTP_ACCEPT_LANGUAGE
7
Shushant

Juste une sous-réponse: utilisez absolument les 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 à cela. Pourquoi? Parce que l'URL est essentielle pour le référencement.

À propos de la traduction de la base de données: le nombre de langues est-il plus ou moins déterminé? Ou plutôt imprévisible et dynamique? Si cela 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 construire son propre CMS car c'est plus rapide, plus léger, etc. etc. Mais c'est vraiment une mauvaise idée!

5
Remy

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

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

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

1) messages d'erreur des classes (ou code de procédure) 2) messages autres que des erreurs de classe (ou code de procédure) 3) contenu de la page (généralement stocké dans une base de données) 4) chaînes de caractères couvrant l'ensemble 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 du type "Impossible de se connecter à la base de données ...". Ces messages doivent uniquement être chargés en cas d'erreur. Ma classe de responsable reçoit un appel des autres classes et, à l'aide des informations transmises en tant que paramètres, il suffit simplement d'accéder au dossier de la classe concerné et de récupérer le fichier d'erreur.

Le deuxième type de message d'erreur est plus semblable 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 la classe. Je sais ce qui se passe.

Pour le contenu de la page, j’utilise une table par langue, chaque table précédée du code correspondant à la langue. Donc en_content est la table avec le contenu en langue anglaise, es_content est pour l'Espagne, cn_content pour la Chine et fr_content est le français.

Le quatrième type de chaîne est pertinent sur tout votre site Web. Celui-ci est chargé via un fichier de configuration nommé en utilisant le code de la langue, à savoir en_lang.php, es_lang.php, etc. 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 le tableau ('Anglais', 'Chinois', ' Espagnol ',' Francais ') dans le dossier français. Ainsi, lorsque vous remplissez une liste déroulante pour la sélection de la langue, celle-ci est dans la langue correcte;)

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 "Le site Web de Jack"), mais également des paramètres pour certaines classes. Fondamentalement, tout ce qui dépend de la langue ou de la culture. Certaines des chaînes incluses incluent des masques pour les dates (MMJJAAAA ou JJMMAAAA) ou les codes de langue ISO. Dans le fichier de langue principale, j'inclus des chaînes pour des classes individuelles car elles sont si peu nombreuses.

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 du 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 config et lang.

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 responsable sait où obtenir le fichier d'erreur dépendant de la langue. Ce fichier n'est chargé qu'en cas d'erreur.

Donc la conclusion est é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 intégrant des traductions. Avec mon framework, je développe l’ensemble du site en anglais puis je traduis tous les fichiers pertinents.

Juste un mot final rapide sur la manière dont les chaînes de traduction sont implémentées. Mon framework a un seul global, le $ manager, qui exécute des services disponibles pour tout autre service. Ainsi, par exemple, le service de formulaire récupère le service HTML et l'utilise pour écrire le code 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 publie cela ici dans l’espoir que cela évitera à quelqu'un de réinventer la roue, comme je le faisais il ya quelques années.

4
JG Estiot

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

  1. Utilisez simplement une fonction __ () qui a arameters pageId (ou objectId, objectTable décrit au n ° 2), le langage cible et un paramètre facultatif du langage de secours (par défaut). La langue par défaut pourrait être définie dans certaines configurations globales afin de pouvoir la changer plus facilement ultérieurement.

  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 pour votre page que vous souhaitez traduire. si vous avez d'autres objets, tels que des actualités, des galeries ou autre, séparez-les simplement en deux champs objectId, objectTable.

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

    • content - le texte que vous souhaitez traduire ainsi que les caractères génériques pour le remplacement de variable. Exemple "Bonjour mr. %% name %%. Le solde de votre compte est %% 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 librement l'ajouter à ce tableau pour avoir un moyen rapide de le rechercher.

  3. Vos appels de base de données doivent être réduits au minimum avec la mise en cache des traductions. Il doit être stocké dans le tableau PHP, car il s'agit de la structure la plus rapide en langage PHP. Comment vous allez faire cette mise en cache est à 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. SEUL le tableau modifié doit être régénéré.

  4. je pense avoir répondu à cela en # 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 slug stockés dans la table de traduction.

Mots finaux

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 oeil à composant de traduction Symfony . Ce pourrait être une bonne base de code pour vous.

4

Je me suis posé des questions connexes encore et encore, puis je me suis perdu dans les langages formels ... mais juste pour vous aider un peu, j'aimerais partager quelques résultats:

Je recommande de jeter un coup d'oeil aux CMS avancés

Typo3 pour PHP (Je sais qu'il y a beaucoup de choses mais c'est celle que je pense la plus mature)

Plone dans Python

Si vous découvrez que le Web en 2013 devrait fonctionner différemment, recommencez à zéro. Cela impliquerait de constituer une équipe de personnes hautement qualifiées et expérimentées pour construire un nouveau système de gestion de contenu. Peut-être aimeriez-vous jeter un oeil à polymer à cette fin.

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

S'il s'agit du codage d'URL, là encore, vous ne devriez pas mélanger les codages et sachez 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 à:

ьankofamerica.com ou bankofamerica.com samesamebutdifferent;)

Bien entendu, le système de fichiers doit fonctionner avec tous les encodages. Un autre avantage pour Unicode utilisant le système de fichiers utf-8.

S'il s'agit de traductions, réfléchissez à la structure des documents. par exemple. un livre ou un article. Vous avez les spécifications docbook à comprendre à propos de ces structures. Mais en HTML, ce sont des blocs de contenu. Vous souhaitez donc une traduction à ce niveau, également au niveau de la page Web ou du domaine. Donc, si un bloc n'existe pas, ce 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, sa structure est complètement différente à gérer. Cela peut déjà être fait avec Typo3.

S'il s'agit de frameworks, les plus mûrs que je connaisse, pour faire des choses générales comme MVC (mot à la mode, je le déteste vraiment! Comme "performance" Si vous voulez vendre quelque chose, utilisez les performances Word et featurerich et vous vendez ... quoi l'enfer) est Zend. Il s’est avéré utile d’apporter des normes aux codeurs php chaos. Mais typo3 a aussi un framework en plus du CMS. Récemment, il a été réaménagé et s'appelle maintenant flow3. Les cadres de cours couvrent l’abstraction de la base de données, la modélisation et les concepts de mise en cache, mais présentent des avantages individuels.

S'il s'agit de la mise en cache ... cela peut être extrêmement compliqué/multicouche. Dans PHP, vous allez penser à accellerator, opcode, mais aussi html, httpd, mysql, xml, css, js ... à tous types de caches. Bien sûr, certaines parties doivent être mises en cache et les parties dynamiques telles que les réponses de blog ne doivent pas. Certains doivent être demandés sur AJAX avec les URL générées. JSON, hashbangs etc.

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

Aussi, vous voudriez faire statistiques, peut-être un système distribué/un facebook de facebooks etc. tout logiciel devant être construit sur vos cms supérieurs ... vous avez donc besoin de différents types de bases de données en mémoire, bigdata, xml, que ce soit.

eh bien, je pense que ça suffit 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 beaucoup de solutions aux questions que vous n'avez pas encore posées.

Si vous envisagez de créer un nouveau CMS car ses versions 2013 et php sont sur le point de mourir de toute façon, vous pouvez vous joindre à tout autre groupe de développeurs, espérons-le, ne vous perdez pas.

Bonne chance!

Et d'ailleurs. que diriez-vous des personnes qui 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és par le borgle)

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

Si vous avez un CMS php mysql et que vous souhaitez intégrer un support multilingue. vous pouvez soit utiliser votre table avec une colonne supplémentaire pour n'importe quelle langue, soit insérer la traduction avec un identifiant d'objet et un identifiant de langue dans la même table ou créer une table identique pour n'importe quelle langue et y insérer des objets, puis créer une union de sélection si vous le souhaitez de tous les afficher. Pour la base de données, utilisez utf8 general ci et, bien sûr, dans ut/front, utilisez utf8 text/encoding. 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 sur votre table de contenu. Dans tous les cas, vous devez disposer d’une mappe de paramètres pour vos URL. Par conséquent, vous souhaitez définir un paramètre à mapper à partir d’un pathsegment dans votre URL, par exemple.

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

configuration de recherche

pageid | url

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

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

mapper les paramètres sur l'url pathsegment ""

$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 en poste supérieure.

Et pour ne pas oublier, vous aurez besoin de "réécrire" l'URL dans votre fichier php générateur qui serait dans la plupart des cas index.php

1
Dr. Dama