it-swarm.com.de

Best Practice mehrsprachige Website

Ich habe seit einigen Monaten mit dieser Frage zu kämpfen, aber ich war noch nicht in einer Situation, in der ich zuvor alle möglichen Optionen ausloten musste. Momentan ist es an der Zeit, die Möglichkeiten kennenzulernen und meine persönlichen Vorlieben für meine anstehenden Projekte zu entwickeln.

Lassen Sie mich zuerst die Situation skizzieren, die ich suche

Ich bin dabei, ein Content-Management-System zu aktualisieren/neu zu entwickeln, das ich bereits seit einiger Zeit verwende. Ich bin jedoch der Meinung, dass Mehrsprachigkeit eine große Verbesserung dieses Systems darstellt. Vorher habe ich keine Frameworks verwendet, aber ich werde Laraval4 für das kommende Projekt verwenden. Laravel scheint die beste Wahl für eine sauberere Codierung von PHP zu sein. Sidenote: Laraval4 should be no factor in your answer. Ich suche nach allgemeinen Übersetzungsmöglichkeiten, die plattform-/rahmenunabhängig sind.

Was soll übersetzt werden

Da das von mir gesuchte System so benutzerfreundlich wie möglich sein muss, sollte sich die Methode zur Verwaltung der Übersetzung im CMS befinden. Es sollte nicht notwendig sein, eine FTP-Verbindung zu starten, um Übersetzungsdateien oder durch HTML/PHP geparste Vorlagen zu ändern.

Außerdem suche ich nach der einfachsten Möglichkeit, mehrere Datenbanktabellen zu übersetzen, ohne zusätzliche Tabellen erstellen zu müssen.

Was habe ich mir ausgedacht

Da habe ich schon selber gesucht, gelesen und probiert. Ich habe ein paar Möglichkeiten. Aber ich habe immer noch nicht das Gefühl, eine Best-Practice-Methode für das zu finden, was ich wirklich suche. Im Moment habe ich mir das ausgedacht, aber diese Methode hat auch Nebenwirkungen.

  1. Mit PHP geparste Templates : Das Template-System sollte mit PHP geparst werden. Auf diese Weise kann ich die übersetzten Parameter in den HTML-Code einfügen, ohne die Vorlagen öffnen und ändern zu müssen. Außerdem kann ich mit PHP geparsten Vorlagen 1 Vorlage für die gesamte Website haben, anstatt einen Unterordner für jede Sprache (die ich zuvor hatte). Die Methode, um dieses Ziel zu erreichen Dies kann entweder Smarty, TemplatePower, Laravel's Blade oder ein beliebiger anderer Template-Parser sein.
  2. Datenbankgetrieben : Vielleicht muss ich das nicht noch einmal erwähnen. Die Lösung sollte jedoch datenbankgesteuert sein. Das CMS soll objektorientiert und MVC sein, daher müsste ich mir eine logische Datenstruktur für die Zeichenfolgen überlegen. Da meine Templates strukturiert wären: templates/Controller/View.php, wäre diese Struktur vielleicht am sinnvollsten: Controller.View.parameter. Die Datenbanktabelle würde diese Felder mit einem value -Feld lang haben. In den Vorlagen könnten wir eine Sortiermethode wie echo __('Controller.View.welcome', array('name', 'Joshua')) verwenden und der Parameter enthält Welcome, :name. Das Ergebnis ist also Welcome, Joshua. Dies scheint eine gute Möglichkeit zu sein, da die Parameter wie: name für den Editor leicht verständlich sind.
  3. Geringe Datenbanklast : Natürlich würde das obige System eine Menge Datenbanklast verursachen, wenn diese Zeichenfolgen unterwegs geladen werden. Aus diesem Grund benötige ich ein Caching-System, das die Sprachdateien neu rendert, sobald sie in der Administrationsumgebung bearbeitet/gespeichert werden. Da Dateien generiert werden, ist auch ein gutes Dateisystemlayout erforderlich. Ich denke, wir können mit languages/en_EN/Controller/View.php Oder .ini gehen, was auch immer am besten zu Ihnen passt. Vielleicht wird eine .ini am Ende sogar schneller analysiert. Dies sollte die Daten im format parameter=value; Enthalten. Ich denke, dies ist die beste Möglichkeit, da jede gerenderte Ansicht eine eigene Sprachdatei enthalten kann, falls vorhanden. Sprachparameter sollten dann in eine bestimmte Ansicht und nicht in einen globalen Bereich geladen werden, um zu verhindern, dass sich die Parameter gegenseitig überschreiben.
  4. Übersetzung von Datenbanktabellen : Das ist das, worüber ich mir am meisten Sorgen mache. Ich suche nach einer Möglichkeit, Übersetzungen von News/Pages/etc. Zu erstellen. so schnell wie möglich. Es ist eine Option, zwei Tabellen für jedes Modul zu haben (zum Beispiel News und News_translations), Aber es ist zu viel Arbeit, um ein gutes System zu bekommen. Eines der Dinge, die ich mir ausgedacht habe, basiert auf einem data versioning - System, das ich geschrieben habe: Es gibt einen Datenbanktabellennamen Translations, diese Tabelle hat eine eindeutige Kombination von language, tablename und primarykey. Zum Beispiel: en_En/News/1 (Bezieht sich auf die englische Version der Nachricht mit der ID = 1). Diese Methode hat jedoch zwei große Nachteile: Erstens wird diese Tabelle mit vielen Daten in der Datenbank sehr lang und zweitens wäre es eine harte Arbeit, diese Konfiguration zum Durchsuchen der Tabelle zu verwenden. Z.B. Das Suchen nach dem SEO-Slug des Artikels wäre eine Volltextsuche, die ziemlich dumm ist. Aber auf der anderen Seite: Es ist eine schnelle Möglichkeit, übersetzbaren Inhalt in jeder Tabelle sehr schnell zu erstellen, aber ich glaube nicht, dass dieser Profi die Nachteile überwiegt.
  5. Frontend-Arbeit : Auch das Frontend würde einige Überlegungen erfordern. Natürlich würden wir die verfügbaren Sprachen in einer Datenbank speichern und diejenigen, die wir brauchen, (de) aktivieren. Auf diese Weise kann das Skript ein Dropdown-Menü zur Auswahl einer Sprache erstellen und das Back-End kann automatisch entscheiden, welche Übersetzungen mit dem CMS erstellt werden können. Die ausgewählte Sprache (z. B. en_EN) wird dann verwendet, wenn die Sprachdatei für eine Ansicht abgerufen oder die richtige Übersetzung für ein Inhaltselement auf der Website abgerufen wird.

Also, da sind sie. Meine bisherigen Ideen. Sie enthalten noch nicht einmal Lokalisierungsoptionen für Daten usw., aber da mein Server PHP5.3.2 + unterstützt, ist es die beste Option, die intl-Erweiterung zu verwenden, wie hier erklärt: http://devzone.zend.com/1500/internationalization-in-php-53 / - aber dies wäre in jedem späteren Stadium der Entwicklung von Nutzen. Im Moment geht es vor allem darum, wie Sie die besten Praktiken für die Übersetzung von Inhalten auf einer Website erzielen.

Abgesehen von allem, was ich hier erklärt habe, habe ich noch eine andere Sache, für die ich mich noch nicht entschieden habe. Es sieht nach einer einfachen Frage aus, aber tatsächlich bereitet sie mir Kopfschmerzen:

URL-Übersetzung? Sollen wir das machen oder nicht? und wie?

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, der unter /about Angezeigt wird. Das Letzte scheint keine gültige Option zu sein, da dadurch mehrere Versionen derselben URL generiert werden und die Indizierung des Inhalts fehlschlägt.

Eine andere Option ist die Verwendung von http://www.domain.com/nl/about-us. Dadurch wird mindestens eine eindeutige URL für jeden Inhalt generiert. Dies ist auch einfacher, wenn Sie in eine andere Sprache wechseln, z. B. http://www.domain.com/en/about-us, Und die angegebene URL ist sowohl für Google- als auch für Human-Besucher einfacher zu verstehen. Was machen wir mit dieser Option mit den Standardsprachen? Sollte die Standardsprache die standardmäßig ausgewählte Sprache entfernen? Leiten Sie also http://www.domain.com/en/about-us Zu http://www.domain.com/about-us Um ... In meinen Augen ist dies die beste Lösung, denn wenn das CMS nur für eine Sprache eingerichtet ist, muss diese Sprachidentifikation nicht in der URL enthalten sein.

Und eine dritte Option ist eine Kombination aus beiden Optionen: Verwenden der "language-identification-less" -URL (http://www.domain.com/about-us) Für die Hauptsprache. Und verwenden Sie eine URL mit einem übersetzten SEO-Slug für Untersprachen: http://www.domain.com/nl/over-ons & http://www.domain.com/de/uber-uns

Ich hoffe meine Frage bringt deine Köpfe zum Knacken, sie haben meine mit Sicherheit geknackt! Es hat mir schon geholfen, hier Fragen zu klären. Ich erhielt die Möglichkeit, die Methoden, die ich zuvor verwendet habe, und die Ideen für mein zukünftiges CMS zu überprüfen.

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

// Edit #1:

Ich habe vergessen zu erwähnen: Die Funktion __ () ist ein Alias ​​für die Übersetzung eines bestimmten Strings. Innerhalb dieser Methode sollte es natürlich 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 oder die Übersetzungsdatei neu generiert werden.

164
Joshua - Pendo

Die Prämisse des Themas

In einer mehrsprachigen Site gibt es drei verschiedene Aspekte:

  • interface-Übersetzung
  • inhalt
  • uRL-Routing

Während sie alle auf unterschiedliche Weise miteinander verbunden sind, werden sie aus CMS-Sicht mithilfe verschiedener UI-Elemente verwaltet und unterschiedlich gespeichert. Sie scheinen zuversichtlich zu sein, die ersten beiden zu implementieren und zu verstehen. Die Frage betraf den letzteren Aspekt - "URL-Übersetzung? Sollten wir dies tun oder nicht? Und auf welche Weise?"

Woraus kann die URL bestehen?

Eine sehr wichtige Sache ist, keine Lust auf IDN . Bevorzugen Sie stattdessen Transliteration (auch: Transkription und Romanisierung). Auf den ersten Blick scheint IDN eine praktikable Option für internationale URLs zu sein, funktioniert jedoch aus zwei Gründen nicht wie angekündigt:

  • einige Browser wandeln Nicht-ASCII-Zeichen wie 'ч' oder 'ž' in '%D1%87' und '%C5%BE' um.
  • wenn der Benutzer benutzerdefinierte Designs hat, enthält die Schriftart des Designs wahrscheinlich keine Symbole für diese Buchstaben

Ich habe vor ein paar Jahren tatsächlich versucht, IDN in einem Projekt auf der Basis von Yii=) (horrible framework, IMHO) anzusprechen ein Angriffsvektor sein.

Verfügbare Optionen ... wie ich sie sehe.

Grundsätzlich haben Sie zwei Möglichkeiten, die wie folgt abstrahiert werden können:

  • http://site.tld/[:query]: Wobei [:query] Sowohl die Sprach- als auch die Inhaltsauswahl bestimmt

  • http://site.tld/[:language]/[:query]: Wobei [:language] Teil der URL die Wahl der Sprache definiert und [:query] Nur zur Identifizierung des Inhalts verwendet wird

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 Segments [:query]; und zwei weitere Quellen:

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

Zuerst müssen Sie die Abfrage einem der definierten Routing-Muster zuordnen (wenn Sie sich für Laravel entscheiden, dann lesen Sie hier ). Bei erfolgreicher Übereinstimmung des Musters müssen Sie dann die Sprache finden.

Sie müssten alle Segmente des Musters durchgehen. Finden Sie die möglichen Übersetzungen für alle diese Segmente und bestimmen Sie, welche Sprache verwendet wurde. Die zwei zusätzlichen Quellen (Cookie und Header) werden verwendet, um Routing-Konflikte zu lösen, wenn (nicht "wenn") sie auftreten.

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

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

Wie Sie bereits feststellen können, wird "блог" auf Russisch als "Blog" transliteriert. Was bedeutet, dass Sie für den ersten Teil von [:query] (Im besten Fall ) ['en', 'ru'] Erhalten. Liste der möglichen Sprachen. 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 am Ende 2 (Beispiel: Russisch und Ukrainisch) oder mehr Möglichkeiten haben, oder 0 Möglichkeiten, kann dies der Fall sein. Sie müssen Cookies und/oder Header verwenden, um die richtige Option zu finden.

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

Sprache als Parameter

Die Alternative ist die Verwendung einer URL, die als http://site.tld/[:language]/[:query] Definiert werden kann. In diesem Fall müssen Sie beim Übersetzen von Abfragen die Sprache nicht erraten, da Sie zu diesem Zeitpunkt bereits wissen, welche Sprache Sie verwenden müssen.

Es gibt auch eine sekundäre Sprachquelle: den Cookie-Wert. Aber hier macht es keinen Sinn, sich mit dem Accept-Language-Header herumzuschlagen, da Sie im Falle eines "Kaltstarts" (wenn der Benutzer die Site 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 Segment [:language] eingestellt ist, verwenden Sie es
  2. wenn $_COOKIE['lang'] eingestellt ist, verwenden Sie es
  3. standardsprache verwenden

Wenn Sie über die Sprache verfügen, versuchen Sie einfach, die Abfrage zu übersetzen. 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 gesehen können Sie beide Ansätze kombinieren, aber das würde den Prozess erschweren und nur Personen unterbringen, die die URL von http://site.tld/en/news In http://site.tld/de/news Manuell ändern möchten und erwarten, dass die Nachrichtenseite auf Deutsch wechselt.

Aber auch dieser Fall könnte wahrscheinlich mithilfe eines Cookie-Werts (der Informationen zur vorherigen Sprachauswahl enthält) entschärft werden, um ihn mit weniger Magie und Hoffnung umzusetzen.

Welchen Ansatz verwenden?

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

Auch in einer realen Word-Situation hätten Sie den dritten Hauptteil in der URL: "title". Wie im Namen des Produkts im Online-Shop oder in der Überschrift des Artikels auf der Nachrichtenseite.

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

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

Kann man das in Laravel machen?

Ein bisschen, aber nicht standardmäßig.

Ich bin nicht sehr vertraut damit, aber nach allem, was ich gesehen habe, verwendet Laravel einen einfachen musterbasierten Routing-Mechanismus. Um mehrsprachige URLs zu implementieren, müssen Sie wahrscheinlich Kernklasse erweitern ( es) , da mehrsprachiges Routing Zugriff auf verschiedene Speicherformen (Datenbank, Cache und/oder Konfigurationsdateien) benötigt.

Es ist geroutet. Was jetzt?

Infolgedessen würden Sie zwei wertvolle Informationen erhalten: die aktuelle Sprache und übersetzte Abfragesegmente. Diese Werte können dann verwendet werden, um an die Klasse (n) zu versenden, die das Ergebnis erzeugen werden.

Grundsätzlich wird die folgende URL: http://site.tld/ru/blog/novinka (Oder die Version ohne '/ru') In so etwas wie verwandelt

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

Was Sie gerade für den Versand verwenden:

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

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

103
tereško

Implementierung von i18n ohne Leistungseinbußen mit einem Vorprozessor, wie von Thomas Bley vorgeschlagen

Bei der Arbeit haben wir kürzlich die Implementierung von i18n auf einigen unserer Immobilien durchlaufen. Eines der Dinge, mit denen wir immer wieder zu kämpfen hatten, war der Leistungseffekt bei der fliegenden Übersetzung. Dann entdeckte ich diesen großartigen Blog) Beitrag von Thomas Bley was uns inspiriert hat, wie wir mit i18n große Verkehrslasten mit minimalen Leistungsproblemen bewältigen.

Anstatt Funktionen für jede Übersetzungsoperation aufzurufen, die bekanntermaßen in PHP 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 jederzeit den neuesten Inhalt bereitstellen).

Die Übersetzungs-Tags

Thomas verwendet die Tags {tr} Und {/tr}, Um zu definieren, wo Übersetzungen beginnen und enden. Aufgrund der Tatsache, dass wir TWIG verwenden, möchten wir nicht { Verwenden, um Verwirrung zu vermeiden. Daher 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 tun dies nicht, weil wir nicht alle Übersetzungsdateien ändern müssen, wenn wir den Wert in Englisch ändern.

Die INI Dateien

Dann erstellen wir eine INI Datei für jede Sprache 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 ändern, indem er die Schlüsselpaare mit einem preg_split Auf \n Oder = Abruft und das CMS in die Lage versetzt, auf das CMS zu schreiben INI Dateien.

Die Pre-Processor-Komponente

Im Wesentlichen schlägt Thomas vor, einen Just-in-Time-Compiler (obwohl es sich in Wahrheit um einen Präprozessor handelt) zu verwenden, um Ihre Übersetzungsdateien zu übernehmen und statische PHP Dateien auf der Festplatte zu erstellen. Dies Auf diese Weise werden unsere übersetzten Dateien im Wesentlichen zwischengespeichert, anstatt für jede Zeichenfolge in der Datei eine Übersetzungsfunktion 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, ob der reguläre Ausdruck funktioniert, ich habe ihn nicht von unserem Unternehmensserver kopiert, aber Sie können sehen, wie der Vorgang 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 einer Sitzungsvariablen, wenn wir kein Cookie erhalten können) und rufen es dann bei jeder Anfrage ab. Sie können dies mit einem optionalen $_GET - Parameter kombinieren, um die Sprache zu überschreiben. Ich empfehle jedoch keine Unterdomäne pro Sprache oder Seite pro Sprache, da es dadurch schwieriger wird, festzustellen, welche Seiten populär und beliebt sind verringert den Wert von eingehenden Links, da diese weniger verbreitet sind.

Warum diese Methode anwenden?

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

  1. Der enorme Leistungsgewinn besteht darin, dass nicht eine ganze Reihe von Funktionen für Inhalte aufgerufen werden, die sich selten ändern (mit diesem System werden 100.000 Besucher auf Französisch immer noch nur einmal einen Übersetzungsersatz ausführen).
  2. Es fügt unserer Datenbank 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 Datenbankinhalt abrufen

Wir fügen einfach eine Spalte für den Inhalt in unserer Datenbank mit dem Namen language hinzu und verwenden dann eine Zugriffsmethode für die LANG -Konstante, die wir zuvor definiert haben, damit unsere SQL-Aufrufe (unter Verwendung von ZF1, leider) aussehen so was:

$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, sodass Artikel 54 In allen Sprachen existieren kann. Unser LANG ist standardmäßig en_US, Wenn nichts angegeben ist.

URL Slug Translation

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

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

Diese können in einer Flat-Datei gespeichert werden, in die Sie über Ihr Admin-Panel problemlos schreiben können. JSON oder XML bieten möglicherweise eine gute Struktur für deren Unterstützung.

Hinweise zu einigen anderen Optionen

PHP-basierte On-The-Fly-Übersetzung

Ich sehe keinen Vorteil gegenüber vorverarbeiteten Übersetzungen.

Front-End-basierte Übersetzungen

Ich habe diese interessant gefunden, aber es gibt ein paar Vorbehalte. Beispielsweise müssen Sie dem Benutzer die gesamte Liste der Phrasen auf Ihrer Website zur Verfügung stellen, die Sie übersetzen möchten. Dies kann problematisch sein, wenn Bereiche der Website verborgen bleiben oder nicht zugänglich sind.

Sie müssen auch davon ausgehen, dass alle Ihre Benutzer bereit und in der Lage sind, Javascript auf Ihrer Website zu verwenden. Meiner Statistik zufolge werden jedoch rund 2,5% unserer Benutzer ohne JavaScript ausgeführt (oder blockieren die Verwendung unserer Websites mithilfe von Noscript). .

Datenbankgesteuerte Übersetzungen

Die Konnektivitätsgeschwindigkeiten von PHP-Datenbanken sind nichts, worüber man sich Gedanken machen muss. Dies erhöht den ohnehin schon hohen Aufwand, eine Funktion für jede zu übersetzende Phrase aufzurufen. Die Probleme mit Leistung und Skalierbarkeit scheinen bei diesem Ansatz überwältigend zu sein.

50
Glitch Desire

Ich empfehle Ihnen, kein Rad zu erfinden und die Abkürzungsliste für gettext und ISO-Sprachen zu verwenden. Haben Sie gesehen, wie i18n/l10n in gängigen CMS oder Frameworks implementiert ist?

Mit gettext haben Sie ein leistungsfähiges Werkzeug, bei dem viele Fälle bereits wie 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.

Schauen Sie sich CakePHP oder Drupal an. Beide mehrsprachig aktiviert. CakePHP als Beispiel für die Schnittstellenlokalisierung und Drupal als Beispiel für die Übersetzung von Inhalten.

Für l10n ist die Verwendung einer Datenbank überhaupt nicht der Fall. Es wird Tonnen von Anfragen geben. Der Standardansatz besteht darin, alle l10n-Daten in einem frühen Stadium im Speicher abzufragen (oder beim ersten Aufruf der i10n-Funktion, wenn Sie das verzögerte Laden bevorzugen). Es können alle Daten gleichzeitig aus der .po-Datei oder aus der DB gelesen werden. Und als nur angeforderte Zeichenfolgen aus dem Array lesen.

Wenn Sie ein Online-Tool zum Übersetzen der Schnittstelle implementieren müssen, können Sie alle diese Daten in der Datenbank haben, aber trotzdem alle Daten in einer Datei speichern, um damit zu arbeiten. Um die Datenmenge im Speicher zu reduzieren, können Sie alle Ihre übersetzten Nachrichten/Zeichenfolgen in Gruppen aufteilen und dann nur die Gruppen laden, die Sie benötigen, wenn dies möglich ist.

Also hast du vollkommen recht in deiner # 3. Mit einer Ausnahme: Normalerweise handelt es sich bei einer großen Datei nicht um eine Datei pro Controller. Weil es für die Leistung am besten ist, eine Datei zu öffnen. Sie wissen wahrscheinlich, dass einige hochgeladene Webanwendungen den gesamten PHP Code in einer Datei kompilieren, um Dateivorgänge zu vermeiden, wenn include/require aufgerufen wird.

Über URLs. Google schlägt indirekt vor Übersetzung verwenden:

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

Ich denke auch, dass Sie Benutzer auf Standard-Sprachpräfix umleiten müssen, z. http://examlpe.com/about-us leitet weiter zu http://examlpe.com/en/about-us Aber wenn Ihre Site nur eine Sprache verwendet, dann Sie brauchen überhaupt keine Präfixe.

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

Das Übersetzen von Inhalten ist eine schwierigere Aufgabe. Ich denke, es wird einige Unterschiede mit verschiedenen Arten von Inhalten geben, z. Artikel, Menüpunkte usw. Aber in # 4 sind Sie auf dem richtigen Weg. Schauen Sie in Drupal um mehr Ideen zu haben. Es hat ein klares DB-Schema und eine gute Oberfläche zum Übersetzen. Wie Sie einen Artikel erstellen und die Sprache dafür auswählen. Und dann können Sie ihn später übersetzen andere Sprachen.

Drupal translation interface

Ich denke, es ist kein Problem mit URL-Slugs. Sie können einfach eine separate Tabelle für Schnecken erstellen, und die Entscheidung ist richtig. Auch bei Verwendung von richtigen Indizes ist es kein Problem, Tabellen auch bei großen Datenmengen abzufragen. Es war keine Volltextsuche, aber die Zeichenfolge stimmt überein, wenn der Datentyp varchar für slug verwendet wird, und Sie können auch einen Index für dieses Feld erstellen.

PS Sorry, mein Englisch ist aber alles andere als perfekt.

14
Yaroslav

Dies hängt davon ab, wie viel Inhalt Ihre Website enthält. Anfangs habe ich eine Datenbank verwendet, wie alle anderen hier, aber es kann zeitaufwändig sein, alle Funktionen einer Datenbank zu skripten. Ich sage nicht, dass dies eine ideale Methode ist, insbesondere wenn Sie viel Text haben. Wenn Sie dies jedoch schnell tun möchten, ohne eine Datenbank zu verwenden, kann diese Methode funktionieren. Sie können Benutzern jedoch nicht erlauben, Daten einzugeben welches als übersetzungsdatei verwendet wird. Wenn Sie die Übersetzungen jedoch selbst hinzufügen, funktioniert Folgendes:

Angenommen, Sie haben diesen Text:

Welcome!

Sie können dies in eine Datenbank 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 ein Cookie verwendet, haben Sie beispielsweise Folgendes:

$_COOKIE['language'];

Um es einfacher zu machen, transformieren wir es in einen Code, der einfach verwendet werden kann:

$language=$_COOKIE['language'];

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

echo $welcome[$language];

Das Ergebnis wird sein:

Croeso!

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

11
user3749746

Ich schlage vor, dass Sie sich bei der Übersetzung nicht wirklich auf die Datenbank verlassen müssen. Dies kann eine sehr schwierige Aufgabe sein und bei der Datenkodierung ein extremes Problem darstellen.

Ich hatte vor einiger Zeit ein ähnliches Problem und schrieb folgenden Kurs, 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('#^[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;
  }
}

Verwendungszweck

 <?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} wird durch das erste Argument ersetzt, das an die Methode Locale::translate('key_name','arg1') übergeben wird {a:2} wird durch das zweite Argument ersetzt, das an die Methode Locale::translate('key_name','arg1','arg2') übergeben wird

Wie funktioniert die Erkennung?

  • Wenn geoip installiert ist, wird standardmäßig der Ländercode von geoip_country_code_by_name zurückgegeben, und wenn geoip nicht installiert ist, wird der Fallback-to-Header auf HTTP_ACCEPT_LANGUAGE gesetzt
7
Shushant

Nur eine Unterantwort: Verwenden Sie unbedingt übersetzte URLs mit einem vorangestellten Sprachbezeichner: http://www.domain.com/nl/over-ons
Hybride-Lösungen werden in der Regel kompliziert, deshalb halte ich mich einfach daran. Warum? Denn die URL ist für SEO unerlässlich.

Über die DB-Übersetzung: Ist die Anzahl der Sprachen mehr oder weniger festgelegt? Oder eher unvorhersehbar und dynamisch? Wenn es behoben ist, würde ich nur neue Spalten hinzufügen, ansonsten mit mehreren Tabellen gehen.

Aber generell, warum nicht Drupal benutzen? Ich weiß, dass jeder sein eigenes CMS erstellen möchte, weil es schneller, schlanker usw. usw. ist. Aber das ist wirklich eine schlechte Idee!

5
Remy

Ich werde nicht versuchen, die bereits gegebenen Antworten zu verfeinern. Stattdessen erzähle ich Ihnen, wie mein eigenes OOP PHP) - Framework mit Übersetzungen umgeht.

Intern verwendet mein Framework Codes wie en, fr, es, cn und so weiter. Ein Array enthält die von der Website unterstützten Sprachen: array ('en', 'fr', 'es', 'cn') Der Sprachcode wird über $ _GET (lang = fr) übergeben und falls nicht übergeben oder ungültig wird auf die erste Sprache im Array gesetzt. Während der Programmausführung und von Anfang an ist also die aktuelle Sprache bekannt.

Es ist hilfreich zu verstehen, welche Art von Inhalten in einer typischen Anwendung übersetzt werden muss:

1) Fehlermeldungen von Klassen (oder Prozedurcode) 2) Nicht-Fehlermeldungen von Klassen (oder Prozedurcode) 3) Seiteninhalt (normalerweise in einer Datenbank gespeichert) 4) Site-weite Zeichenfolgen (wie der Name der Website) 5) Skript- bestimmte Zeichenfolgen

Der erste Typ ist einfach zu verstehen. Grundsätzlich geht es um Nachrichten wie "Konnte keine Verbindung zur Datenbank herstellen ...". Diese Nachrichten müssen nur geladen werden, wenn ein Fehler auftritt. Meine Managerklasse erhält einen Aufruf von den anderen Klassen, und unter Verwendung der als Parameter übergebenen Informationen wird einfach der entsprechende Klassenordner aufgerufen und die Fehlerdatei abgerufen.

Die zweite Art von Fehlermeldung ähnelt eher den Meldungen, die Sie erhalten, wenn die Validierung eines Formulars fehlgeschlagen ist. ("Sie können nicht ... leer lassen" oder "Bitte wählen Sie ein Passwort mit mehr als 5 Zeichen"). Die Zeichenfolgen müssen geladen werden, bevor die Klasse ausgeführt wird. Ich weiß, was ist

Für den eigentlichen Seiteninhalt verwende ich eine Tabelle pro Sprache, wobei jeder Tabelle der Code für die Sprache vorangestellt wird. En_content ist also die Tabelle mit Inhalten in englischer Sprache, es_content ist für Spanien, cn_content für China und fr_content ist das französische Zeug.

Die vierte Art von Zeichenfolge ist für Ihre Website relevant. Dies wird über eine Konfigurationsdatei geladen, die mit dem Code für die Sprache, also en_lang.php, es_lang.php usw., benannt ist. In der globalen Sprachdatei müssen Sie die übersetzten Sprachen wie Array ('Englisch', 'Chinesisch', 'Spanisch', 'Französisch') in die globale englische Datei und das Array ('Anglais', 'Chinois', ' Espagnol ',' Francais ') in der französischen Akte. Wenn Sie ein Dropdown-Menü für die Sprachauswahl ausfüllen, wird es in der richtigen Sprache angezeigt.

Schließlich haben Sie die script-spezifischen Zeichenfolgen. Wenn Sie also eine Kochanwendung schreiben, könnte dies "Ihr Ofen war nicht heiß genug" sein.

In meinem Bewerbungszyklus wird zuerst die globale Sprachdatei geladen. Dort finden Sie nicht nur globale Zeichenfolgen (wie "Jacks Website"), sondern auch Einstellungen für einige Klassen. Grundsätzlich alles, was sprach- oder kulturabhängig ist. Einige der dort enthaltenen Zeichenfolgen enthalten Masken für Datumsangaben (MMDDYYYY oder DDMMYYYY) oder ISO-Sprachcodes. In der Hauptsprachendatei füge ich Zeichenfolgen für einzelne Klassen ein, da es so wenige davon gibt.

Die zweite und letzte Sprachdatei, die von der Festplatte gelesen wird, ist die Skriptsprachendatei. lang_en_home_welcome.php ist die Sprachdatei für das home/welcome-Skript. Ein Skript wird durch einen Modus (home) und eine Aktion (welcome) definiert. Jedes Skript hat einen eigenen Ordner mit config- und lang-Dateien.

Das Skript ruft den Inhalt aus der Datenbank ab und benennt die Inhaltstabelle wie oben erläutert.

Wenn etwas schief geht, weiß der Manager, wo er die sprachabhängige Fehlerdatei erhält. Diese Datei wird nur im Fehlerfall geladen.

Die Schlussfolgerung liegt also auf der Hand. Denken Sie über die Übersetzungsprobleme nach, bevor Sie mit der Entwicklung einer Anwendung oder eines Frameworks beginnen. Sie benötigen auch einen Entwicklungsworkflow mit Übersetzungen. Mit meinem Framework entwickle ich die gesamte Site auf Englisch und übersetze dann alle relevanten Dateien.

Nur ein kurzes abschließendes Wort zur Implementierung der Übersetzungszeichenfolgen. Mein Framework hat eine einzige globale Instanz, den $ manager, der Dienste ausführt, die für jeden anderen Dienst verfügbar sind. So erhält beispielsweise der Formulardienst den HTML-Dienst und verwendet ihn zum Schreiben des HTML. Einer der Dienste auf meinem System ist der Übersetzerdienst. $ translator-> set ($ service, $ code, $ string) setzt einen String für die aktuelle Sprache. Die Sprachdatei ist eine Liste solcher Aussagen. $ translator-> get ($ service, $ code) ruft eine Übersetzungszeichenfolge ab. Der $ code kann eine Zahl wie 1 oder eine Zeichenfolge wie 'no_connection' sein. Es kann keine Kollision zwischen Diensten geben, da jeder im Datenbereich des Übersetzers einen eigenen Namespace hat.

Ich poste dies hier in der Hoffnung, dass es jemandem die Aufgabe erspart, das Rad neu zu erfinden, wie ich es vor einigen langen Jahren tun musste.

4
JG Estiot

Ich hatte vor einiger Zeit das gleiche Problem, bevor ich das Symfony Framework verwendete.

  1. Verwenden Sie einfach eine Funktion __ () mit den Parametern pageId (oder objectId, objectTable, beschrieben in # 2), Zielsprache und einem optionalen Parameter für die Fallback-Sprache (Standardsprache). Die Standardsprache kann in einigen globalen Konfigurationen festgelegt werden, um sie später leichter ändern zu können.

  2. Zum Speichern des Inhalts in der Datenbank habe ich folgende Struktur verwendet: (pageId, Sprache, Inhalt, Variablen).

    • pageId wäre ein FK für Ihre Seite, die Sie übersetzen möchten. Wenn Sie andere Objekte haben, wie Nachrichten, Galerien oder was auch immer, teilen Sie es einfach in 2 Felder objectId, objectTable.

    • language - offensichtlich würde es die ISO-Sprachzeichenfolge EN_en, LT_lt, EN_us usw. speichern.

    • content - der Text, den Sie zusammen mit den Platzhaltern für das Ersetzen von Variablen übersetzen möchten. Beispiel "Hallo Herr %% Name %%. Ihr Kontostand ist %% Saldo %%."

    • variablen - Die von JSON codierten Variablen. PHP bietet Funktionen, um diese schnell zu analysieren. Beispiel "name: Laurynas, balance: 15.23".

    • du hast auch Schneckenfeld erwähnt. Sie können es beliebig zu dieser Tabelle hinzufügen, um schnell danach suchen zu können.

  3. Ihre Datenbankaufrufe müssen durch das Zwischenspeichern der Übersetzungen auf ein Minimum reduziert werden. Es muss im Array PHP) gespeichert werden, da es die schnellste Struktur in der Sprache PHP) ist. Wie Sie dieses Caching durchführen, liegt bei Ihnen. Aus meiner Erfahrung Sie sollten einen Ordner für jede unterstützte Sprache und ein Array für jede PageId haben. Der Cache sollte neu erstellt werden, nachdem Sie die Übersetzung aktualisiert haben. NUR das geänderte Array sollte neu generiert werden.

  4. ich glaube, ich habe das in # 2 beantwortet

  5. ihre Idee ist absolut logisch. Dieser ist ziemlich einfach und ich denke, Sie werden keine Probleme machen.

URLs sollten mit den in der Übersetzungstabelle gespeicherten Slugs übersetzt werden.

Schlussworte

es ist immer gut, die besten Praktiken zu erforschen, aber das Rad nicht neu zu erfinden. Nehmen Sie einfach die Komponenten von bekannten Frameworks und verwenden Sie sie.

schauen Sie sich Symfony-Übersetzungskomponente an. Es könnte eine gute Codebasis für Sie sein.

Ich habe mir immer wieder verwandte Fragen gestellt und mich dann in den offiziellen Sprachen verlaufen. Um Ihnen ein wenig zu helfen, möchte ich einige Ergebnisse mitteilen:

Ich empfehle, einen Blick auf Advanced CMS zu werfen

Typo3 für PHP (Ich weiß, dass es eine Menge Zeug gibt, aber das ist das, von dem ich denke, dass es am ausgereiftesten ist)

Plone in Python

Wenn Sie herausfinden, dass das Web 2013 anders funktionieren sollte, fangen Sie von vorne an. Das würde bedeuten, ein Team von hochqualifizierten/erfahrenen Mitarbeitern zusammenzustellen, um ein neues CMS aufzubauen. Vielleicht möchten Sie zu diesem Zweck einen Blick auf polymer werfen.

Wenn es um das Codieren und die Unterstützung mehrsprachiger Websites/Muttersprachen geht, sollte jeder Programmierer eine Ahnung von Unicode haben. Wenn Sie Unicode nicht kennen, werden Sie Ihre Daten mit Sicherheit durcheinander bringen. Gehen Sie nicht mit den Tausenden von ISO-Codes. Sie werden dir nur etwas Gedächtnis sparen. Aber mit UTF-8 können Sie buchstäblich alles machen, sogar chinesische Zeichen speichern. Aber dafür müssten Sie entweder 2 oder 4-Byte-Zeichen speichern, was es im Grunde genommen zu einem utf-16 oder utf-32 macht.

Wenn es um URL-Codierung geht, sollten Sie auch hier keine Codierungen mischen und sich darüber im Klaren sein, dass es zumindest für den Domainnamen Regeln gibt, die von verschiedenen Lobbys definiert werden und Anwendungen wie einen Browser bereitstellen. z.B. Eine Domain könnte sehr ähnlich sein wie:

ьankofamerica.com oder bankofamerica.com samesamebutdifferent;)

Natürlich benötigen Sie das Dateisystem, um mit allen Kodierungen arbeiten zu können. Ein weiteres Plus für Unicode mit utf-8-Dateisystem.

Wenn es um Übersetzungen geht, denken Sie an die Struktur von Dokumenten. z.B. ein Buch oder ein Artikel. Sie haben die docbook Spezifikationen, um diese Strukturen zu verstehen. Aber in HTML geht es nur um Inhaltsblöcke. Sie möchten also eine Übersetzung auf dieser Ebene haben, auch auf Webseiten- oder Domainebene. Wenn also ein Block nicht existiert, ist er einfach nicht da. Wenn eine Webseite nicht existiert, werden Sie zur oberen Navigationsebene weitergeleitet. Wenn eine Domain in der Navigationsstruktur völlig anders sein sollte, dann ist eine völlig andere Struktur zu verwalten. Dies ist bereits mit Typo3 möglich.

Wenn es um Frameworks geht, die ausgereiftesten, die ich kenne, um das allgemeine Zeug wie MVC zu machen (Modewort, ich hasse es wirklich! Wie "Leistung" Wenn Sie etwas verkaufen möchten, verwenden Sie die Word-Leistung und featurerich und Sie verkaufen ... was zum Teufel) ist Zend. Es hat sich als eine gute Sache erwiesen, Standards für PHP-Chaos-Codierer zu schaffen. Typo3 hat aber neben dem CMS auch ein Framework. Vor kurzem wurde es neu entwickelt und heißt jetzt flow3. Die Frameworks umfassen natürlich Datenbankabstraktion, Templating und Konzepte für das Caching, haben aber individuelle Stärken.

Wenn es um Caching geht ... kann das furchtbar kompliziert/vielschichtig sein. In PHP) werden Sie über Accellerator, Opcode, aber auch über HTML, HTTPD, MySQL, XML, CSS, JS ... über alle Arten von Caches nachdenken. Natürlich sollten einige Teile zwischengespeichert und dynamisch sein Teile wie Blog-Antworten sollten nicht. Einige sollten angefordert werden über AJAX mit generierten URLs. JSON, Hashbangs etc.

Dann möchten Sie eine kleine Komponente auf Ihrer Website haben, auf die nur bestimmte Benutzer zugreifen oder die nur von ihnen verwaltet werden. Dies spielt also konzeptionell eine große Rolle.

Sie möchten auch Statistik erstellen, haben vielleicht System/Facebook von Facebooks usw. verteilt, jede Software, die auf Ihren Over-the-Top-CMS erstellt werden soll ... Sie benötigen also verschiedene Arten von Datenbanken inmemory, bigdata, xml, was auch immer.

nun, ich denke, das ist genug für den Moment. Wenn Sie weder von typo3/plone noch von den erwähnten Frameworks gehört haben, haben Sie genug zu lernen. Auf diesem Weg finden Sie viele Lösungen für Fragen, die Sie noch nicht gestellt haben.

Wenn Sie dann denken, lassen Sie uns ein neues CMS machen, weil sein 2013 und PHP sowieso sterben werden, dann sind Sie herzlich eingeladen, sich einer anderen Gruppe von Entwicklern anzuschließen, die sich hoffentlich nicht verlaufen.

Viel Glück!

Und übrigens. Wie wäre es, wenn die Leute in Zukunft keine Websites mehr haben? und wir werden alle auf google + sein? Ich hoffe, die Entwickler werden ein bisschen kreativer und tun etwas Nützliches (um sich nicht von der Borgle assimilieren zu lassen)

//// Bearbeiten /// Nur ein kleiner Gedanke für Ihre bestehende Anwendung:

Wenn Sie ein PHP-MySQL-CMS haben und Multilang-Unterstützung einbinden möchten. Sie können entweder Ihre Tabelle mit einer zusätzlichen Spalte für eine beliebige Sprache verwenden oder die Übersetzung mit einer Objekt-ID und einer Sprach-ID in dieselbe Tabelle einfügen oder eine identische Tabelle für eine beliebige Sprache erstellen und dort Objekte einfügen und dann eine Auswahlvereinigung vornehmen, wenn Sie möchten um sie alle anzeigen zu lassen. Verwenden Sie für die Datenbank utf8 general ci und natürlich im Front-/Backend utf8 text/encoding. Ich habe URL-Pfadsegmente für URLs verwendet, wie Sie es bereits erläutert haben

domain.org/en/about Sie können die Sprach-ID Ihrer Inhaltstabelle zuordnen. Auf jeden Fall benötigen Sie eine Parameterkarte für Ihre URLs, damit Sie einen Parameter definieren können, der aus einem Pfadsegment in Ihrer URL, das z.

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

lookup-Konfiguration

pageid | url

1 | /über/mitarbeiter/../ ..

1 | /../Über/Mitarbeiter../../

ordne Parameter dem URL-Pfadsegment "" zu

$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, das wurde schon im oberen post behandelt.

Und um nicht zu vergessen, müssten Sie die URL zu Ihrer generierenden PHP-Datei "umschreiben", die in den meisten Fällen index.php wäre

1
Dr. Dama