Lokalisierung von größeren Webanwendungen

Sir Robin

Erfahrenes Mitglied
Hallo,

ich überlege nun schon ein wenig hin und her und hab ein paar Ansätze gesammelt wie man eine große Webapplikation (in dem Fall in PHP und unter Benutzung von MySQL realisiert) lokalisiert. Ich denke das die Programmier/Scriptsprache als solches für diese Betrachtung eher unerheblich ist (auch wenn ich von ASP .NET weiß das man dort einiges über Ressourcen lösen kann, aber hilft mir halt in dem Moment nicht :))

Hintergrund ist der, dass ich von einem Kunden den Auftrag habe seine Applikation um einige Funktionen zu erweitern. Sinnvolle Multilingualität steht dabei als eine der wichtigsten Punkte auf der Liste.

Das Grundproblem ist folgendes: Neben der reinen Textübersetzung muss man ja auch gewisse andere Formen beachten. So ist zum Beispiel das Standarddatumsformat in Deutschland ein anderes als in den USA, nur um ein Beispiel zu nennen. Es gibt natürlich noch weitere Beispiele.

Ich habe spontan 3 Ansätze die mir so einfallen. Allen drei gleich ist die Datenbanktabelle "languages". In dieser gibt es einige Felder, ich führe sie mal an:

  • id (ist klar denke ich)
  • languagename (der Name der Sprache, sprich "Deutsch", "Englisch" usw.)
  • dateformat (das Format des Datums)
  • ... weitere Spalten für die jeweiligen Einstellungen

wobei ich am Überlegen bin, ob man die landesspezifischen Einstellungen nicht eher in eine XML-Datei packen sollte.

Der erste Ansatz:

man hat eine Tabelle translations, die folgende Spalten enthält:

  • id (hmm, ja)
  • languages_id (Foreign Key aus languages)
  • reference_table (tabelle mit dem zu übersetzendem Inhalt)
  • reference_field (das zu übersetzende Feld)
  • reference_id (Id für den zu übersetzenden DB-Eintrag)
  • translation (Übersetzung)

nehmen wir an in unserer Applikation existiert folgende Datenbanktabelle für ein Newsmodul:

news
  • id
  • news_title
  • news_text

Wenn man jetzt den News-Titel mit der ID "2" anzeigen will, checkt man ob in der
translation - Tabelle für die reference_table "news" und der reference_id "2" und
dem reference_field "news_title" ein Eintrag vorhanden ist. Ist er vorhanden, zeigt man
ihn an, wenn nicht, dann nimmt man den Original Text.

Vorteil dieses Ansatzes ist natürlich das er nicht schwer zu implementieren ist und sich gut in vorhandene Applikationen einfügen lässt.

Nachteile sind die Bedenken in der Performance (ein Gros mehr an Anfragen), außerdem wird die Defaultsprache nicht so richtig festgelegt. Soll heißen: Im Original können alle möglichen Sprachen stehen, und man weiß nicht welche. Man könnte das Konzept anpassen und alle Texte in die translation-Tabelle verschieben (noch mehr Datenwust)

Okay, kommen wir zum nächsten Ansatz:

Ansatz Nummer 2:

es existiert eine Tabelle die, die Texte für die Seite enthält. Also der Aufbau wäre wie folgt:

Tabelle texte
  • id (ist klar)
  • language_id (Foreign Key auf languages.id)
  • module (enthält den Namen des Moduls (oder die ID, aber das ist in dem Fall egal))
  • original_text (der original text)
  • translated_text (der übersetzte Text)

jetzt hat man einfach textblöcke, etwa "Hallo %s" für die Begrüßung. Man guckt dann einfach in der Datenbank ob es für "Hallo %s" in der Applikation eine entsprechende Übersetzung gibt, und nimmt die dann. Wenn nicht, default-Eintrag anzeigen. Auch klar.

Vorteil ist natürlich mal wieder die extrem einfache Implementierung.

Nachteil ist mal wieder die, so denke ich, etwas heftige Datenbanklast (hohes Datenaufkommen im Query etc.)

so, letzter Ansatz nun

Ansatz Nummer 3:

Der "Modulebene"-Ansatz. Soll heißen, man löst das alles auf Modulebene. Ein simples Beispiel dieser Art an Hand des Newsmoduls:

Tabelle news
  • id
  • news_titel
  • news_text
  • language_id (Foreign Key auf languages.id)

So, ist eigentlich klar wie das dann aussieht, man hat halt für jede Sprache einen seperaten Newseintrag.

Vorteile:
  • simpel zu implementieren
  • man kann bewusst gewisse Einträge für gewisse Sprachen ausblenden (ist das ein Vorteil? Keine Ahnung, der Gedanke kam mir gerade) in dem man halt einfach für die jeweilige Sprach-ID nichts hinterlegt (ginge natürlich auch mit den anderen Ansätzen)

Nachteile:
  • Modulentwickler muss sich um die Lokalisierungsschicht kümmern (kann man eventuell abfangen durch sinnvolle Vererbungen der Klassen und APIs)
  • "Verteilen" der Sprachinformationen, keine zentrale Stelle wo die Übersetzungsdaten liegen. Ist aber auch ein Vorteil: Löscht man ein Modul mit Daten, sind auch die Übersetzungsdaten weg.

So, das waren die 3 Gedankengänge die ich dazu habe. Meine Frage an euch:

Welchen Ansatz findet ihr am besten? Kennt ihr sogar noch einen anderen Ansatz? Welche Ansätze verwendet ihr (bessere vielleicht?)? Seht ihr noch weitere Vor- oder Nachteile in meinen Ansätzen als die aufgeführten (die sehr mager ausfallen)? Kennt ihr Webprojekte die das richtig gut gelöst haben? Ich hoffe auf eine angeregte und fruchtbare Diskussion.

Grüße,
- Robin

Update:

Man kann das Datenaufkommen in den Queries natürlich durch Techniken wie Prepared Statements etc. ein wenig verringern, das ist klar.
 
Zuletzt bearbeitet:
Also wenn Dir die Extension zur Verfuegung steht wuerde ich dazu GetText waehlen. Dies ist ja dafuer gemacht Programme zu internationalisieren und es geht im Grunde auch ziemlich einfach. Man programmiert die Seite in einer Sprache und direkten Ausgaben laufen halt durch gettext().
Z.B. wird dann aus
PHP:
echo 'Meine Mutti backt lecker Kuchen';
dies:
PHP:
echo gettext('Meine Mutti backt lecker Kuchen');
Im Anschluss laesst man diese Texte ausfiltern und macht sich an die Uebersetzung fuer die einzelnen Sprachen.
Welche Sprache dann letztendlich genutzt wird kann dann z.B. ueber eine Config-Datei, eine Datenbankeinstellung oder auch in der Session geregelt werden. Und die selbe Einstellung kann man dann ja auch noch nutzen um das Datumsformat zu waehlen, und selbst das koennte man ueber GetText realisieren denk ich, z.B. so koennte das gehen:
PHP:
echo date(gettext('d.M.Y'),time());
 
Ja, GetText wäre auch so eine Variante, das habe ich auch schon gesehen.

Das Problem das sich da aber stellt (sofern ich das richtig verstanden habe) ist das es dabei nicht nur auf die Lokalisierung von Begriffen der Applikation selbst ankommt (also etwa der immer wieder auftretende Begriff "Login") sondern eben auch die Lokalisierung des normalen Contents die vom User eingegeben werden. Also meinetwegen die Index-Seite oder was auch immer. Dann müsste der User jedes Mal entsprechende .mo Dateien anlegen, was für Otto-Normal-Verbraucher vielleicht etwas heftig werden könnte. Wobei man ihm da vielleicht einfach ein sinnvolles Interface für bieten kann.

Muss mich mal damit beschäftigen (jetzt weißt du auch warum ich deine SQL-Klasse noch nicht näher bestaunen konnte ;)) danke schon mal für den Tipp (warum bin ich da nicht selbst drauf gekommen?)
 
Hmm, das mit dem Content wird sicher so eine Sache. Wenn der in verschiedenen Sprachen sein soll muss dieser natuerlich in allen Sprachen eingegeben werden, eine On-the-fly-Uebersetzung wird da wohl keine wirklich tollen Ergebnisse liefern.
 
Man koennte auch die statischen Texte per gettext uebersetzen (halte ich fuer sehr sinvoll, genau dafuer ist gettext ja da) und die dynamischen evtl. ueber eine API.

Ich stell mir das so vor:
Es gibt das Interface IMultilingual, das die Methoden getLanguage() und setLanguage() bereitstellt. Serialisierbare Objekte, die spaeter in mehreren Sprachen verfasst/gespeichert werden sollen (z.B. News), implementieren dann dieses Interface und verwalten die Sprache, die mit setLanguage() gesetzt wird in einem privaten Attribut. Soll das Objekt nun (de)serialisiert werden, wird entweder
a) dem Serializer die gewaehlte Sprache als Argument mit uebergeben, falls das serialisierbare Objekt die Serialisierung selbst uebernimmt oder
b) holt sich der Serialisierer die gewuenschte Sprache mit Hilfe der getLanguage() Methode.

Code:
                                                    +------------------------+
+------------------------+                          | <<interface>>          |
| News                   |                          | IMultilingual          |
+------------------------+                          +------------------------+
|-m_sLanguage: string    |                          |+setLanguage(ln: string)|
+------------------------+                          |+getLanguage(): string  |
|+setLanguage(ln: string)|                          +------------------------+
|+getLanguage(): string  |---o IMultilingual
+------------------------+

Eine weitere Ueberlegung waere, dass vorher festgelegt wird, welche Attribute ueberhaupt lokaliert werden sollen (der Newstext z.B., das Datum hingegen nicht).

Auf Implementierungsebene koennte das dann so aussehen, dass genau die Attribute, die lokalisiert werden sollen, in einer extra Datenbanktabelle gespeichert werden.

object_id, language, field1, field2, fieldn

Weiterhin koennte koennte man auch dem Serialisierer eine Sprache zuweisen, dann werden automatisch alle Objekte, bei denen getLanguage() NULL zurueckgibt, in der entsprechenden Sprache gespeichert, das spart nochmal etwas aufwand, weil man sich auf Modulebene nicht um eine Standardsprache kuemmern muss.
 
Zuletzt bearbeitet von einem Moderator:
Zurück