Mehrsprachige Website für Best Practice

Ich habe mich seit einigen Monaten mit dieser Frage herumgeschlagen, aber ich war noch nicht in einer Situation, in der ich alle möglichen Optionen vorher untersuchen musste. Im Moment habe ich das Gefühl, dass es an der Zeit ist, die Möglichkeiten zu kennen und meine persönlichen Vorlieben für meine kommenden Projekte zu entwickeln.

Lassen Sie mich zuerst die Situation skizzieren, nach der ich suche

Ich bin gerade dabei, ein Content-Management-System, das ich schon länger nutze, zu verbessern. Ich fühle jedoch, dass Multi-Sprache eine große Verbesserung für dieses System darstellt. Bevor ich keine Frameworks verwendet habe, werde ich Laraval4 für das kommende Projekt verwenden. Laravel scheint die beste Wahl für eine sauberere Art PHP zu programmieren. Sidenote: Laraval4 should be no factor in your answer . Ich suche nach allgemeinen Wegen der Übersetzung, die plattform- / rahmenunabhängig sind.

Was sollte übersetzt werden?

Da das System, nach dem ich suche, so benutzerfreundlich wie möglich sein sollte, sollte die Methode zur Verwaltung der Übersetzung innerhalb des CMS sein. Es sollte nicht notwendig sein, eine FTP-Verbindung zu starten, um Übersetzungsdateien oder HTML- / PHP-parsierte Vorlagen zu ändern.

Außerdem suche ich nach dem einfachsten Weg, um mehrere databasetabellen zu übersetzen, ohne dass Sie zusätzliche Tabellen erstellen müssen.

Was habe ich selbst gefunden?

Da ich schon selbst gesucht, gelesen und probiert habe. Es gibt ein paar Möglichkeiten, die ich habe. Aber ich habe immer noch nicht das Gefühl, eine Best-Practice-Methode für das zu haben, wonach ich wirklich suche. Genau das habe ich mir vorgenommen, aber diese Methode hat auch Nebenwirkungen.

  1. PHP Parsed Templates : Das Template-System sollte von PHP analysiert werden. Auf diese Weise kann ich die übersetzten Parameter in den HTML-Code einfügen, ohne die Templates öffnen und ändern zu müssen. Abgesehen davon, PHP geparste Templates gibt mir die Möglichkeit, 1 Vorlage für die gesamte Website, statt mit einem Unterordner für jede Sprache (die ich vorher hatte). Die Methode, um dieses Ziel zu erreichen, kann entweder Smarty, TemplatePower, Laravel’s Blade oder ein anderer Template-Parser sein. Wie gesagt, dies sollte unabhängig von der schriftlichen Lösung sein.
  2. databasegesteuert : Vielleicht muss ich das nicht noch einmal erwähnen. Aber die Lösung sollte datenbankgesteuert sein. Das CMS zielt darauf ab, objektorientiert und MVC zu sein, also müsste ich mir eine logische Datenstruktur für die Strings vorstellen. Da meine Vorlagen strukturiert wären: templates / Controller / View.php würde diese Struktur vielleicht am meisten Sinn machen: Controller.View.parameter . Die databasetabelle würde diese Felder lang mit einem value . Innerhalb der Vorlagen könnten wir eine Sortiermethode wie echo __('Controller.View.welcome', array('name', 'Joshua')) und der Parameter enthält Welcome, :name . So ist das Ergebnis Welcome, Joshua . Dies scheint ein guter Weg dies zu tun, da die Parameter wie: name vom Editor leicht zu verstehen sind.
  3. Niedrige databaselast : Natürlich würde das obige System eine Menge an databaselast verursachen, wenn diese Strings unterwegs geladen werden. Daher würde ich ein Caching-System benötigen, das die Sprachdateien neu rendert, sobald sie in der Administrationsumgebung bearbeitet / gespeichert werden. Da Dateien generiert werden, ist auch ein gutes Dateisystem-Layout erforderlich. Ich denke, wir können mit languages/en_EN/Controller/View.php oder .ini gehen, was immer Ihnen am besten passt. Vielleicht wird eine .ini am Ende sogar schneller geparst. Diese Datei sollte die Daten im format parameter=value; . Ich denke, das ist der beste Weg, dies zu tun, da jede Ansicht, die gerendert wird, eine eigene Sprachdatei enthalten kann, falls sie existiert. Sprachparameter sollten dann in eine bestimmte Ansicht und nicht in einen globalen Bereich geladen werden, um zu verhindern, dass sich Parameter gegenseitig überschreiben.
  4. database Table Übersetzung : Das ist die Sache, um die ich mir am meisten Sorgen mache. Ich suche nach einer Möglichkeit, Übersetzungen von News / Pages / etc zu erstellen. schnellstens. News_translations ist eine Option, zwei Tabellen für jedes Modul zu haben (zum Beispiel News und News_translations ), aber es fühlt sich an, als ob viel Arbeit News_translations , um ein gutes System zu bekommen. Eines der Dinge, die ich entwickelt habe, basiert auf einem data versioning , das ich geschrieben habe: Es gibt einen Namen für die databasetabelle Translations , diese Tabelle hat eine einzigartige Kombination aus language , tablename und primarykey . Zum Beispiel: en_En / News / 1 (Bezug auf die englische Version des News-Artikels mit ID = 1). Aber es gibt 2 große Nachteile dieser Methode: erstens neigt diese Tabelle dazu, mit vielen Daten in der database ziemlich lang zu werden, und zweitens wäre es eine höllische Aufgabe, dieses Setup zu verwenden, um den Tisch zu durchsuchen. ZB die Suche nach dem SEO Slug des Artikels wäre eine Volltextsuche, die ziemlich dumm ist. Aber auf der anderen Seite: Es ist ein schneller Weg, schnell übersetzbaren Inhalt in jedem Tisch zu erstellen, aber ich glaube nicht, dass dieser Profi die Nachteile überbewertet.
  5. Front-End-Arbeit : Auch das Front-End müsste etwas nachdenken. Natürlich würden wir die verfügbaren Sprachen in einer database speichern und (de) aktiv diejenigen, die wir brauchen. Auf diese Weise kann das Skript ein Drop-down zur Auswahl einer Sprache generieren, und das Backend kann automatisch entscheiden, welche Übersetzungen mit dem CMS erstellt werden können. Die gewählte Sprache (z. B. en_EN) würde dann verwendet, wenn die Sprachdatei für eine Ansicht abgerufen oder die richtige Übersetzung für einen Inhaltseintrag auf der Website abgerufen werden soll.

Also, da sind sie. Meine bisherigen Ideen. Sie enthalten noch nicht einmal Lokalisierungsoptionen für Datumsangaben usw., aber da mein Server PHP5.3.2 unterstützt, ist die beste Option, die Intl-Erweiterung wie hier erklärt zu verwenden: http://devzone.zend.com/1500/internationalization-in -php-53 / – aber das würde in jedem späteren Stadium der Entwicklung von Nutzen sein. Im Moment geht es vor allem darum, wie man den Inhalt einer Website am besten übersetzen kann.

Neben allem, was ich hier erklärt habe, habe ich noch eine Sache, die ich noch nicht entschieden habe, es sieht wie eine einfache Frage aus, aber in der Tat hat es mir Kopfschmerzen gemacht:

URL-Übersetzung? Sollten wir das tun oder nicht? und auf welche Weise?

Also .. wenn ich diese URL habe: http://www.domain.com/about-us und Englisch ist meine Standardsprache. Sollte diese URL in http://www.domain.com/over-ons übersetzt werden, wenn ich Niederländisch als meine Sprache wähle? Oder sollten wir den einfachen Weg gehen und einfach den Inhalt der Seite ändern, die bei /about sichtbar ist. Die letzte Sache scheint keine gültige Option zu sein, da dies mehrere Versionen derselben URL erzeugen würde. Diese Indexierung des Inhalts würde den richtigen Weg finden.

Eine andere Option ist stattdessen http://www.domain.com/nl/about-us . Dies erzeugt mindestens eine eindeutige URL für jeden Inhalt. Auch wäre es einfacher, zu einer anderen Sprache zu wechseln, zum Beispiel http://www.domain.com/en/about-us und die bereitgestellte URL ist sowohl für Google- als auch für menschliche Besucher einfacher zu verstehen. Mit dieser Option, was machen wir mit den Standardsprachen? Sollte die Standardsprache die standardmäßig ausgewählte Sprache entfernen? Umleiten von http://www.domain.com/en/about-us zu http://www.domain.com/about-us … In meinen Augen ist dies die beste Lösung, denn wenn das CMS eingerichtet ist Nur eine Sprache gibt es keine Notwendigkeit, diese Spracherkennung in der URL zu haben.

Und eine dritte Option ist eine Kombination aus beiden Optionen: Verwenden der “Sprachidentifikations-losen” -URL ( http://www.domain.com/about-us ) für die Hauptsprache. Und verwende eine URL mit einem übersetzten SEO Slug für Subsprachen: http://www.domain.com/nl/over-ons & http://www.domain.com/de/uber-uns

Ich hoffe, dass meine Frage die Köpfe knirscht, sie haben mir sicher geknackt! Es hat mir schon geholfen, hier als Frage zu arbeiten. Gab mir eine Möglichkeit, die Methoden, die ich vorher verwendet habe, zu überprüfen und die Idee, die ich für mein bevorstehendes CMS habe.

Ich möchte Ihnen schon jetzt danken, dass Sie sich die Zeit genommen haben, diesen Text zu lesen!

// Edit #1 :

Ich habe vergessen zu erwähnen: Die function __ () ist ein Alias, um eine gegebene Zeichenkette zu übersetzen. Innerhalb dieser Methode sollte es offensichtlich eine Art Fallback-Methode geben, bei der der Standardtext geladen wird, wenn noch keine Übersetzungen verfügbar sind. Wenn die Übersetzung fehlt, sollte sie entweder eingefügt werden oder die Übersetzungsdatei sollte neu generiert werden.

Thema Prämisse

Auf einer mehrsprachigen Website gibt es drei verschiedene Aspekte:

  • Schnittstellenübersetzung
  • Inhalt
  • URL-Routing

Während sie alle auf unterschiedliche Weise miteinander verbunden sind, werden sie aus Sicht des CMS mit verschiedenen UI-Elementen verwaltet und unterschiedlich gespeichert. Sie scheinen zuversichtlich in Ihre Umsetzung und Verständnis der ersten beiden zu sein. Die Frage bezog sich auf den letztgenannten Aspekt – “URL Translation? Sollten wir das tun oder nicht? Und auf welche Art und Weise?”

Woraus kann die URL bestehen?

Eine sehr wichtige Sache ist, mit IDN nicht schick werden. Statt Transkription bevorzugen (auch: Transkription und Romanisierung). Während auf den ersten Blick IDN eine praktikable Option für internationale URLs zu sein scheint, funktioniert es aus zwei Gründen nicht wie angekündigt:

  • einige Browser werden die Nicht-ASCII-Zeichen wie 'ч' oder 'ž' in '%D1%87' und '%C5%BE'
  • Wenn der Benutzer über benutzerdefinierte Designs verfügt, enthält die Schriftart des Designs höchstwahrscheinlich keine Symbole für diese Buchstaben

Ich habe vor einigen Jahren versucht, IDN in einem Yii-basierten Projekt zu erreichen (Horror Framework, IMHO). Ich stieß auf beide oben genannten Probleme, bevor ich diese Lösung abschabte. Außerdem vermute ich, dass es ein Angriffsvektor sein könnte.

Verfügbare Optionen … wie ich sie sehe.

Grundsätzlich haben Sie zwei Möglichkeiten, die abstrahiert werden könnten als:

  • http://site.tld/[:query] : wobei [:query] sowohl die Sprache als auch die Inhaltsauswahl bestimmt

  • http://site.tld/[:language]/[:query] : wobei [:language] Teil von URL die Wahl der Sprache definiert und [:query] nur verwendet wird, um den Inhalt zu identifizieren

Abfrage ist Α und Ω ..

Nehmen wir an, Sie wählen http://site.tld/[:query] .

In diesem Fall haben Sie eine primäre Sprachquelle: den Inhalt des [:query] -Segments; und zwei zusätzliche Quellen:

  • Wert $_COOKIE['lang'] für diesen bestimmten Browser
  • Liste der Sprachen in HTTP Accept-Language (1) , (2) Kopfzeile

Zuerst müssen Sie die Abfrage mit einem der definierten Routing-Muster abgleichen (wenn Sie die Option Laravel gewählt haben, lesen Sie hier ). Bei einem erfolgreichen Mustervergleich müssen Sie die Sprache finden.

Sie müssten alle Segmente des Musters durchlaufen. Suchen Sie nach den möglichen Übersetzungen für alle Segmente und bestimmen Sie, welche Sprache verwendet wurde. Die zwei zusätzlichen Quellen (Cookie und Header) würden verwendet werden, um Routing-Konflikte aufzulösen, wenn sie (nicht “wenn”) auftreten.

Nehmen Sie zum Beispiel: http://site.tld/blog/novinka .

Das ist die Transliteration von "блог, новинка" , die auf Englisch ungefähr "blog", "latest" .

Wie Sie bereits bemerken, wird auf Russisch “блог” als “Blog” transliteriert. Was bedeutet, dass Sie für den ersten Teil von [:query] (im besten Fall ) mit ['en', 'ru'] Liste möglicher Sprachen enden. Dann nehmen Sie das nächste Segment – “Novinka”. Das könnte nur eine Sprache auf der Liste der Möglichkeiten haben: ['ru'] .

Wenn die Liste einen Eintrag enthält, haben Sie die Sprache erfolgreich gefunden.

Aber wenn Sie mit 2 (Beispiel: Russisch und Ukrainisch) oder mehr Möglichkeiten enden .. oder 0 Möglichkeiten, wie ein Fall sein könnte. Sie müssen Cookie und / oder Header verwenden, um die richtige Option zu finden.

Wenn alles andere fehlschlägt, wählen Sie die Standardsprache der Site aus.

Sprache als Parameter

Die Alternative ist die URL, die als http://site.tld/[:language]/[:query] . In diesem Fall müssen Sie beim Übersetzen der Abfrage die Sprache nicht erraten, da Sie zu diesem Zeitpunkt bereits wissen, welches Sie verwenden sollen.

Es gibt auch eine sekundäre Sprachquelle: den Cookie-Wert. Aber hier hat es keinen Sinn, mit dem Accept-Language-Header herumzuspielen, weil Sie im Falle eines “Kaltstarts” (wenn der Benutzer die Website zum ersten Mal mit einer benutzerdefinierten Abfrage öffnet) nicht mit einer unbekannten Anzahl möglicher Sprachen zu tun haben.

Stattdessen haben Sie 3 einfache, priorisierte Optionen:

  1. Wenn das [:language] -Segment festgelegt ist, verwenden Sie es
  2. Wenn $_COOKIE['lang'] gesetzt ist, benutze es
  3. Verwenden Sie die Standardsprache

Wenn Sie die Sprache haben, versuchen Sie einfach, die Abfrage zu übersetzen, und wenn die Übersetzung fehlschlägt, verwenden Sie den “Standardwert” für dieses bestimmte Segment (basierend auf den Routing-Ergebnissen).

Ist hier nicht eine dritte Option?

Ja, technisch können Sie beide Ansätze kombinieren, aber das würde den process komplizieren und nur Leute aufnehmen, die die URL von http://site.tld/en/news manuell in http://site.tld/en/news ändern http://site.tld/de/news erwarte, dass die Nachrichtenseite auf Deutsch wechselt.

Aber selbst dieser Fall könnte wahrscheinlich mit dem Cookie-Wert abgeschwächt werden (der Informationen über die vorherige Sprachwahl enthalten würde), um mit weniger Magie und Hoffnung umgesetzt zu werden.

Welcher Ansatz ist zu verwenden?

Wie Sie vielleicht schon erraten haben, würde ich http://site.tld/[:language]/[:query] als sinnvollere Option empfehlen.

Auch in echter Wortsituation hätten Sie den 3. Hauptteil in URL: “title”. Wie im Namen des Produkts im Online-Shop oder Überschrift des Artikels in der Nachrichten-Website.

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

In diesem Fall wäre '/news/article/121415' die Abfrage, und die 'EU-as-global-reserve-currency' ist Titel. Rein für SEO-Zwecke.

Kann es in Laravel gemacht werden?

Ein bisschen, aber nicht standardmäßig.

Ich kenne mich nicht allzu gut damit aus, aber von dem, was ich gesehen habe, verwendet Laravel einfachen Muster-basierten Routing-Mechanismus. Um mehrsprachige URLs zu implementieren, müssen Sie wahrscheinlich die coreklasse (n) erweitern , da mehrsprachiges Routing Zugriff auf verschiedene Speicherformen (database, Cache und / oder Konfigurationsdateien) benötigt.

Es ist geroutet. Was jetzt?

Als Ergebnis haben Sie zwei wertvolle Informationen: die aktuelle Sprache und die übersetzten Segmente der Abfrage. Diese Werte können dann verwendet werden, um an die class (n) zu senden, die das Ergebnis erzeugen.

Im Grunde genommen wird die folgende URL: http://site.tld/ru/blog/novinka (oder die Version ohne '/ru' ) in etwas verwandelt

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

Was Sie nur zum Versand verwenden:

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

.. oder eine Variation davon, abhängig von der jeweiligen Implementierung.

Implementieren von i18n ohne Performance Hit mit einem Pre-Processor wie von Thomas Bley vorgeschlagen

Bei der Arbeit haben wir vor kurzem die Implementierung von i18n in einigen unserer Hotels durchgeführt, und eines der Dinge, mit denen wir zu kämpfen hatten, war die performanceseinbußen bei der On-The-Fly-Übersetzung. Dann entdeckte ich diesen großartigen Blog-Beitrag von Thomas Bley Das hat die Art und Weise inspiriert, wie wir i18n verwenden, um große Verkehrslasten mit minimalen performancesproblemen zu bewältigen.

Anstatt functionen für jede Übersetzungsoperation aufzurufen, was, wie wir in PHP wissen, teuer ist, definieren wir unsere Basisdateien mit Platzhaltern und verwenden dann einen Vorprozessor, um diese Dateien zwischenzuspeichern (wir speichern die Dateiänderungszeit, um sicherzustellen, dass wir sie bedienen) der neueste Inhalt zu jeder Zeit).

Die Übersetzung Tags

Thomas verwendet die Tags {tr} und {/tr} , um zu definieren, wo die Übersetzungen beginnen und enden. Aufgrund der Tatsache, dass wir TWIG verwenden, wollen wir nicht { um Verwirrung zu vermeiden, verwenden wir stattdessen [%tr%] und [%/tr%] . Grundsätzlich sieht das so aus:

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

Beachten Sie, dass Thomas vorschlägt, das Basis-Englisch in der Datei zu verwenden. Wir machen das nicht, weil wir nicht alle Übersetzungsdateien modifizieren müssen, wenn wir den Wert in Englisch ändern.

Die INI-Dateien

Dann erstellen wir für jede Sprache eine INI-Datei im 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) 

Es wäre trivial, einem Benutzer zu erlauben, diese innerhalb des CMS zu modifizieren, einfach die Schlüsselpaare durch einen preg_split auf \n oder = und das CMS in die Lage zu preg_split , in die INI-Dateien zu schreiben.

Die Pre-processor-Komponente

Im Wesentlichen schlägt Thomas vor, eine Just-in-Time-Compiler-function zu verwenden, um Ihre Übersetzungsdateien aufzunehmen und statische PHP-Dateien auf der Festplatte zu erstellen. Auf diese Weise speichern wir im Wesentlichen unsere übersetzten Dateien, anstatt eine Übersetzungsfunktion für jede Zeichenfolge in der Datei aufzurufen:

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

Hinweis: Ich habe nicht überprüft, dass die Regex funktioniert, ich habe sie nicht von unserem Unternehmensserver kopiert, aber Sie können sehen, wie die Operation funktioniert.

Wie man es nennt

Auch dieses Beispiel stammt von Thomas Bley, nicht von mir:

 // 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(); 

Wir speichern die Sprache in einem Cookie (oder in einer Sitzungsvariablen, wenn wir keinen Cookie erhalten können) und rufen sie dann bei jeder Anfrage ab. Sie könnten dies mit einem optionalen $_GET Parameter $_GET , um die Sprache zu überschreiben, aber ich empfehle keine Subdomain-pro-Sprache oder Seite-pro-Sprache, da es schwieriger macht zu sehen, welche Seiten beliebt sind und den Wert reduzieren von eingehenden Links, da Sie sie kaum mehr verbreiten werden.

Warum diese Methode verwenden?

Wir mögen diese Methode der Vorverarbeitung aus drei Gründen:

  1. Der große performancesgewinn besteht darin, dass nicht ein ganzes Bündel von functionen für Inhalte aufgerufen wird, die sich selten ändern (bei diesem System werden 100 000 Besucher in Französisch nur noch einmal mit dem Austausch von Übersetzungen fertig).
  2. Es fügt unserer database keine Last hinzu, da es einfache Flat-Files verwendet und eine reine PHP-Lösung ist.
  3. Die Möglichkeit, PHP-Ausdrücke in unseren Übersetzungen zu verwenden.

Übersetzten databaseinhalt abrufen

Wir fügen einfach eine Spalte für den Inhalt in unserer database namens language , dann verwenden wir eine LANG für die LANG Konstante, die wir zuvor definiert haben, so dass unsere SQL-Aufrufe (mit ZF1, leider) folgendermaßen aussehen:

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

Unsere Artikel haben einen zusammengesetzten Primärschlüssel über id und language so dass Artikel 54 in allen Sprachen existieren kann. Unser LANG standardmäßig auf en_US wenn nicht angegeben.

URL-Slug-Übersetzung

Ich würde hier zwei Dinge kombinieren, eine ist eine function in Ihrem Bootstrap, die einen $_GET Parameter für die Sprache akzeptiert und die Cookie-Variable überschreibt, und eine andere ist das Routing, das mehrere Slugs akzeptiert. Dann können Sie so etwas in Ihrem Routing tun:

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

Diese könnten in einer flachen Datei gespeichert werden, die einfach von Ihrem Admin-Panel aus geschrieben werden kann. JSON oder XML bieten möglicherweise eine gute Struktur, um sie zu unterstützen.

Hinweise zu einigen anderen Optionen

PHP-basierte On-The-Fly-Übersetzung

Ich kann nicht sehen, dass diese einen Vorteil gegenüber vorverarbeiteten Übersetzungen bieten.

Front-End-basierte Übersetzungen

Ich habe das schon lange interessant gefunden, aber es gibt ein paar Vorbehalte. Zum Beispiel müssen Sie dem Benutzer die gesamte Liste der Phrasen auf Ihrer Website zur Verfügung stellen, die Sie übersetzen möchten. Dies könnte problematisch sein, wenn Bereiche der Website verborgen sind oder Ihnen der Zugriff darauf verwehrt wurde.

Sie müssen auch davon ausgehen, dass alle Ihre Benutzer bereit und in der Lage sind, Javascript auf Ihrer Website zu verwenden, aber von meinen Statistiken laufen etwa 2,5% unserer Benutzer ohne sie (oder verwenden Noscript, um unsere Seiten daran zu hindern, es zu benutzen) .

databasegetriebene Übersetzungen

Die Geschwindigkeit der PHP-databaseverbindungen ist nichts, worüber man nachdenken könnte, und dies trägt zu dem ohnehin schon hohen Aufwand bei, eine function für jede zu übersetzende Phrase aufzurufen. Die performances- und Skalierbarkeitsprobleme scheinen bei diesem Ansatz überwältigend zu sein.

Ich schlage vor, Sie nicht ein Rad zu erfinden und verwenden gettext und ISO-Sprachen Abkürzungsliste. Haben Sie gesehen, wie i18n / l10n in gängigen CMS oder Frameworks implementiert ist?

Mit gettext haben Sie ein leistungsfähiges Werkzeug, in dem viele Fälle bereits als Pluralformen von Zahlen implementiert sind. In Englisch haben Sie nur 2 Möglichkeiten: Singular und Plural. Aber auf Russisch zum Beispiel gibt es 3 Formen und es ist nicht so einfach wie auf Englisch.

Auch viele Übersetzer haben bereits Erfahrung mit gettext zu arbeiten.

Sieh dir CakePHP oder Drupal an . Beide Sprachen sind mehrsprachig aktiviert. CakePHP als Beispiel für Schnittstellenlokalisierung und Drupal als Beispiel für Inhaltsübersetzung.

Für l10n mit Hilfe der database ist überhaupt nicht der Fall. Es wird Tonnen auf Abfragen sein. Der Standardansatz besteht darin, alle l10n-Daten in einem frühen Stadium (oder während des ersten Aufrufs der i10n-function, wenn Sie lazy loading bevorzugen) im Speicher abzurufen. Es kann aus der .po-Datei oder aus der database alle Daten gleichzeitig lesen. Und dann einfach die angeforderten Strings aus dem Array lesen.

Wenn Sie ein Online-Tool für die Übersetzungsschnittstelle implementieren müssen, können Sie alle Daten in der database speichern, aber dennoch alle Daten in einer Datei speichern, um damit zu arbeiten. Um die Menge an Daten im Speicher zu reduzieren, können Sie alle übersetzten Nachrichten / Strings in Gruppen aufteilen und dann nur die Gruppen laden, die Sie benötigen, wenn dies möglich ist.

Du hast also völlig richtig in deiner # 3. Mit einer Ausnahme: Normalerweise ist es eine große Datei, keine pro-Controller-Datei oder so. Weil es für die performance am besten ist, eine Datei zu öffnen. Sie wissen wahrscheinlich, dass einige hoch geladene Web-Apps den gesamten PHP-Code in einer Datei kompilieren, um Dateioperationen zu vermeiden, wenn include / require aufgerufen wird.

Über URLs Google schlägt indirekt vor , die Übersetzung zu verwenden:

um den französischen Inhalt klar zu kennzeichnen: http://example.ca/fr/vélo-de-montagne.html

Auch ich denke, dass Sie Benutzer zum Standardsprachpräfix redirect müssen, zB http://examlpe.com/about-us wird Weiterleitungen zu http://examlpe.com/en/about-us aber wenn Ihre Seite nur eine Sprache so Sie verwenden Sie benötigen keine Präfixe.

Check out: 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

Das Übersetzen von Inhalten ist eine schwierigere Aufgabe. Ich denke, es wird einige Unterschiede mit verschiedenen Arten von Inhalten geben, zB Artikeln, Menüpunkten usw. Aber in # 4 bist du auf dem richtigen Weg. Schauen Sie in Drupal nach, um mehr Ideen zu haben. Es ist klar genug DB-Schema und gut genug Schnittstelle für die Übersetzung. Wie Sie Artikel erstellen und Sprache dafür auswählen. Und dann kannst du es später in andere Sprachen übersetzen.

Drupal Übersetzungsschnittstelle

Ich denke, es ist kein Problem mit URL-Slugs. Sie können nur separate Tabelle für Schnecken erstellen und es wird die richtige Entscheidung sein. Auch bei Verwendung von richtigen Indizes ist es kein Problem, die Tabelle selbst bei großen Datenmengen abzufragen. Und es war keine Volltextsuche, sondern String-Übereinstimmung, wenn Varchar-Datentyp für Slug verwendet wird, und Sie können auch einen Index für dieses Feld haben.

PS Sorry, mein Englisch ist weit davon entfernt perfekt zu sein.

Es hängt davon ab, wie viele Inhalte Ihre Website hat. Zuerst habe ich hier eine database wie alle anderen Leute benutzt, aber es kann zeitaufwändig sein, alle functionen einer database zu schreiben. Ich sage nicht, dass dies eine ideale Methode ist und vor allem, wenn Sie viel Text haben, aber wenn Sie schnell ohne database arbeiten wollen, könnte diese Methode funktionieren, aber Sie können nicht zulassen, dass Benutzer Daten eingeben welche als Übersetzungsdateien verwendet werden. Aber wenn Sie die Übersetzungen selbst hinzufügen, wird es funktionieren:

Nehmen wir an, Sie haben diesen Text:

 Welcome! 

Sie können dies in einer database mit Übersetzungen eingeben, aber Sie können dies auch tun:

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

Wenn Ihre Website einen Cookie verwendet, haben Sie zum Beispiel Folgendes:

 $_COOKIE['language']; 

Um es einfacher zu machen, wandeln wir es in einen Code um, der leicht verwendet werden kann:

 $language=$_COOKIE['language']; 

Wenn Ihre Cookie-Sprache walisisch ist und Sie diesen Code haben:

 echo $welcome[$language]; 

Das Ergebnis davon wird sein:

 Croeso! 

Wenn Sie viele Übersetzungen für Ihre Website hinzufügen müssen und eine database zu aufwendig ist, kann die Verwendung eines Arrays eine ideale Lösung sein.

Ich schlage vor, Sie nicht wirklich von der database für die Übersetzung abhängen, könnte es wirklich eine chaotische Aufgabe sein und könnte ein extremes Problem im Falle der Datencodierung sein.

Ich hatte vor einiger Zeit ein ähnliches Problem und schrieb folgenden Unterricht, um mein Problem zu lösen

Objekt: Gebietsschema \ Gebietsschema

 < ?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('#^[az]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[az]+_[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; } } 

Verwendung

  < ?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'); 

Wie es funktioniert

{a:1} is replaced by 1st argument passed to method Locale::translate('key_name','arg1') {a:2} is replaced by 2nd argument passed to method Locale::translate('key_name','arg1','arg2')

How detection works

  • By default if geoip is installed then it will return country code by geoip_country_code_by_name and if geoip is not installed the fallback to HTTP_ACCEPT_LANGUAGE header

Just a sub answer: Absolutely use translated urls with a language identifier in front of them: http://www.domain.com/nl/over-ons
Hybride solutions tend to get complicated, so I would just stick with it. Warum? Cause the url is essential for SEO.

About the db translation: Is the number of languages more or less fixed? Or rather unpredictable and dynamic? If it is fixed, I would just add new columns, otherwise go with multiple tables.

But generally, why not use Drupal? I know everybody wants to build their own CMS cause it’s faster, leaner, etc. etc. But that is just really a bad idea!

I had the same probem a while ago, before starting using Symfony framework.

  1. Just use a function __() which has arameters pageId (or objectId, objectTable described in #2), target language and an optional parameter of fallback (default) language. The default language could be set in some global config in order to have an easier way to change it later.

  2. For storing the content in database i used following structure: (pageId, language, content, variables).

    • pageId would be a FK to your page you want to translate. if you have other objects, like news, galleries or whatever, just split it into 2 fields objectId, objectTable.

    • language – obviously it would store the ISO language string EN_en, LT_lt, EN_us etc.

    • content – the text you want to translate together with the wildcards for variable replacing. Example “Hello mr. %%name%%. Your account balance is %%balance%%.”

    • variables – the json encoded variables. PHP provides functions to quickly parse these. Example “name: Laurynas, balance: 15.23”.

    • you mentioned also slug field. you could freely add it to this table just to have a quick way to search for it.

  3. Your database calls must be reduced to minimum with caching the translations. It must be stored in PHP array, because it is the fastest structure in PHP language. How you will make this caching is up to you. From my experience you should have a folder for each language supported and an array for each pageId. The cache should be rebuilt after you update the translation. ONLY the changed array should be regenerated.

  4. i think i answered that in #2

  5. your idea is perfectly logical. this one is pretty simple and i think will not make you any problems.

URLs should be translated using the stored slugs in the translation table.

Final words

it is always good to research the best practices, but do not reinvent the wheel. just take and use the components from well known frameworks and use them.

take a look at Symfony translation component . It could be a good code base for you.

I am not going to attempt to refine the answers already given. Instead I will tell you about the way my own OOP PHP framework handles translations.

Internally, my framework use codes like en, fr, es, cn and so on. An array holds the languages supported by the website: array(‘en’,’fr’,’es’,’cn’) The language code is passed via $_GET (lang=fr) and if not passed or not valid, it is set to the first language in the array. So at any time during program execution and from the very beginning, the current language is known.

It is useful to understand the kind of content that needs to be translated in a typical application:

1) error messages from classes (or procedural code) 2) non-error messages from classes (or procedural code) 3) page content (usually store in a database) 4) site-wide strings (like website name) 5) script-specific strings

The first type is simple to understand. Basically, we are talking about messages like “could not connect to the database …”. These messages only need to be loaded when an error occurs. My manager class receives a call from the other classes and using the information passed as parameters simply goes to relevant the class folder and retrieves the error file.

The second type of error message is more like the messages you get when the validation of a form went wrong. (“You cannot leave … blank” or “please choose a password with more than 5 characters”). The strings need to be loaded before the class runs.I know what is

For the actual page content, I use one table per language, each table prefixed by the code for the language. So en_content is the table with English language content, es_content is for spain, cn_content for China and fr_content is the French stuff.

The fourth kind of string is relevant throughout your website. This is loaded via a configuration file named using the code for the language, that is en_lang.php, es_lang.php and so on. In the global language file you will need to load the translated languages such as array(‘English’,’Chinese’, ‘Spanish’,’French’) in the English global file and array(‘Anglais’,’Chinois’, ‘Espagnol’, ‘Francais’) in the French file. So when you populate a dropdown for language selection, it is in the correct language 😉

Finally you have the script-specific strings. So if you write a cooking application, it might be “Your oven was not hot enough”.

In my application cycle, the global language file is loaded first. In there you will find not just global strings (like “Jack’s Website”) but also settings for some of the classes. Basically anything that is language or culture-dependent. Some of the strings in there include masks for dates (MMDDYYYY or DDMMYYYY), or ISO Language Codes. In the main language file, I include strings for individual classes becaue there are so few of them.

The second and last language file that is read from disk is the script language file. lang_en_home_welcome.php is the language file for the home/welcome script. A script is defined by a mode (home) and an action (welcome). Each script has its own folder with config and lang files.

The script pulls the content from the database naming the content table as explained above.

If something goes wrong, the manager knows where to get the language-dependent error file. That file is only loaded in case of an error.

So the conclusion is obvious. Think about the translation issues before you start developing an application or framework. You also need a development workflow that incorporates translations. With my framework, I develop the whole site in English and then translate all the relevant files.

Just a quick final word on the way the translation strings are implemented. My framework has a single global, the $manager, which runs services available to any other service. So for example the form service gets hold of the html service and uses it to write the html. One of the services on my system is the translator service. $translator->set($service,$code,$string) sets a string for the current language. The language file is a list of such statements. $translator->get($service,$code) retrieves a translation string. The $code can be numeric like 1 or a string like ‘no_connection’. There can be no clash between services because each has its own namespace in the translator’s data area.

I post this here in the hope it will save somebody the task of reinventing the wheel like I had to do a few long years ago.

I’ve been asking myself related questions over and over again, then got lost in formal languages… but just to help you out a little I’d like to share some findings:

I recommend to give a look at advanced CMS

Typo3 for PHP (I know there is a lot of stuff but thats the one I think is most mature)

Plone in Python

If you find out that the web in 2013 should work different then, start from scratch. That would mean to put together a team of highly skilled/experienced people to build a new CMS. May be you’d like to give a look at polymer for that purpose.

If it comes to coding and multilingual websites / native language support, I think every programmer should have a clue about unicode. If you don’t know unicode you’ll most certainly mess up your data. Do not go with the thousands of ISO codes. They’ll only save you some memory. But you can do literally everything with UTF-8 even store chinese chars. But for that you’d need to store either 2 or 4 byte chars that makes it basically a utf-16 or utf-32.

If it’s about URL encoding, again there you shouldn’t mix encodings and be aware that at least for the domainname there are rules defined by different lobbies that provide applications like a browser. eg a Domain could be very similar like:

ьankofamerica.com or bankofamerica.com samesamebutdifferent 😉

Of course you need the filesystem to work with all encodings. Another plus for unicode using utf-8 filesystem.

If its about translations, think about the structure of documents. eg a book or an article. You have the docbook specifications to understand about those structures. But in HTML its just about content blocks. So you’d like to have a translation on that level, also on webpage level or domain level. So if a block doesn’t exist its just not there, if a webpage doesn’t exist you’ll get redirected to the upper navigation level. If a domain should be completely different in navigation structure, then.. its a complete different structure to manage. This can already be done with Typo3.

If its about frameworks, the most mature ones I know, to do the general stuff like MVC(buzzword I really hate it! Like “performance” If you want to sell something, use the word performance and featurerich and you sell… what the hell) is Zend . It has proven to be a good thing to bring standards to php chaos coders. But, typo3 also has a Framework besides the CMS. Recently it has been redeveloped and is called flow3 now. The frameworks of course cover database abstraction, templating and concepts for caching, but have individual strengths.

If its about caching… that can be awefully complicated / multilayered. In PHP you’ll think about accellerator, opcode, but also html, httpd, mysql, xml, css, js … any kinds of caches. Of course some parts should be cached and dynamic parts like blog answers shouldn’t. Some should be requested over AJAX with generated urls. JSON, hashbangs etc.

Then, you’d like to have any little component on your website to be accessed or managed only by certain users , so conceptually that plays a big role.

Also you’d like to make statistics , maybe have distributed system / a facebook of facebooks etc. any software to be built on top of your over the top cms … so you need different type of databases inmemory, bigdata, xml, whatsoever.

well, I think thats enough for now. If you haven’t heard of either typo3 / plone or mentioned frameworks, you have enough to study. On that path you’ll find a lot of solutions for questions you haven’t asked yet.

If then you think, lets make a new CMS because its 2013 and php is about to die anyway, then you r welcome to join any other group of developers hopefully not getting lost.

Viel Glück!

And btw. how about people will not having any websites anymore in the future? and we’ll all be on google+? I hope developers become a little more creative and do something usefull(to not be assimilated by the borgle)

//// Edit /// Just a little thought for your existing application:

If you have a php mysql CMS and you wanted to embed multilang support. you could either use your table with an aditional column for any language or insert the translation with an object id and a language id in the same table or create an identical table for any language and insert objects there, then make a select union if you want to have them all displayed. For the database use utf8 general ci and of course in the front/backend use utf8 text/encoding. I have used url path segments for urls in the way you already explaned like

domain.org/en/about you can map the lang ID to your content table. anyway you need to have a map of parameters for your urls so you’d like to define a parameter to be mapped from a pathsegment in your URL that would be eg

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

lookup configuration

pageid| url

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

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

map parameters to 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; } 

per say, thats been covered already in upper post.

And to not forget, you’d need to “rewrite” the url to your generating php file that would in most cases be index.php

Database work:

Create Language Table ‘languages’:

Fields:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Create a table in database ‘content’:

Fields:

content_id(primary and auto increamented)

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

Front End Work:

When user selects any language from dropdown or any area then save selected language id in session like,

$_SESSION[‘language’]=1;

Now fetch data from database table ‘content’ based on language id stored in session.

Detail may found here http://skillrow.com/multilingual-website-in-php-2/

As a person who live in Quebec where almost all site is french and english… i have try many if not most multilanguage plugin for WP… the one an only usefull solution that work nive with all my site is mQtranslate… i live and die with it !

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

A really simple option that works with any website where you can upload Javascript is http://www.multilingualizer.com

It lets you put all text for all languages onto one page and then hides the languages the user doesn’t need to see. functioniert gut.

What about WORDPRESS + MULTI-LANGUAGE SITE BASIS (plugin) ? the site will have structure:

  • example.com/ eng /category1/….
  • example.com/ eng /my-page….
  • example.com/ rus /category1/….
  • example.com/ rus /my-page….

The plugin provides Interface for Translation all phrases, with simple logic:

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

then it can be outputed:
echo translate('my_title', LNG); // LNG is auto-detected

ps however, check, if the plugin is still active.