Malcolm Beck`s: Internationlaisierung, UI Translation, wohin mit den Daten und in welcher Form?

Hallo, werte Gemeinde,

Ich hab ein Entscheidungsproblemchen. Ich hab mir ein kleines DB-Tool geschrieben, welches mir hilft, UI Content (also Strings und Meldungen, die ich innerhalb von Scripten brauche, oder auch marginaler Content im Template) zu übersetzen und zu verwalten. Das Tool läuft soweit auch ziemlich gut, ich kann Daten in Sekunden eingeben und mit wenigen Klicks auch relativ brauchbar mit Google übersetzen und bspw. mit Bing vergleichen, verschiedene Projekte verwalten, die unterschiedliche Sprachen anbieten usw.

Zum Entwickeln greife ich direkt auf die Daten in der DB zu. Für den produktiven Einsatz hatte ich mich, als ich anfing, das Tool zu programmieren, für PHP-Klassen mit Namespace entschieden, die nach der Verwaltung mit dem Tool generiert werden müssen (so das dieser Content im produktiven Einsatz ohne Datenbank zur Verfügung steht). Was auch wunderbar funzt, aber mittlerweile bin ich mir nicht mehr sicher, ob ich es bei Klassen belassen soll, oder ob ich die Daten einfach in PHP-Dateien als normale Arrays speichern soll. Die letzten Jahre über hatte ich es immer so gemacht, wie ich es in Felix Riesterers internationalem Gästebuch abgeguckt hatte, also mit separaten XML-Dateien für jede Sprache, aber das ist eine einzige Katastrophe (vor allem, wenn man viele Sprachen unterstützen und ständig neue Daten einpflegen muss, eine Katastrophe), davon will ich ganz weg. Ich will bei PHP bleiben, da ich auch mit PHP programmiere.

Beispieldaten sehen so aus (und für die bräuchte ich Hilfe, sowohl bei der Struktur als auch der Daten):

Orderstruktur der generierten Klassen:

/index.php
/src/ycore/strings/
                   /de.php
                   /ar.php
                   /en.php
                   /tr.php
                   // usw.

Wären die Daten in einem „/view/strings/“ Ordner besser aufgehoben?

Und in den Dateien steht dann.

<?php // de.php

  namespace Ycore;

  class de {

   public function __construct() {
     $this->ystr = $this->provideStrings ();
   }

   private function provideStrings () {
     return array ("formular" => array (
                                        "error" => array (
                                                          "categories" => "Kategorien",
                                                          "msg" => "Ein fehler ist aufgetreten",
                                                   ),
                                        "success" => "Bingo! Alle Eingaben sind richtig!",
                                 ),
                   "test" => "Erster Test mit blanker Tabelle"
                 );
      }
}

Ich weiss nicht mehr, wie ich auf Klassen kam. Ich erhoffte mir wohl einen Vorteil durch Autoload (Namespace bringt hier irgendwie überhaupt nichts). Aber Autoload kann man ja auch auf normale Scripte mit Arrays anwenden. Also, Vorschläge? Wo und wie speichert ihr solchen Content? Und neee, PHP get_text() kann ich leider nicht verwenden, absolut keine Alternative.

--
Hosen sind Blau
  1. Also, Vorschläge?

    Sprachen auf Subklassen abbilden.

    --
    Füße sind schwarz.
    1. Dann wäre es ja noch schlimmer als bereits jetzt mit den Klassen 😂

      Ne, ich werde die Daten einfach in Arrays im „/view/strings/“ Ordner speichern. Dadurch hab ich viel mehr Möglichkeiten und kann die Daten vielfältiger nutzen. Und während ich das schreibe, fühlt es sich auch richtiger an. BTW, so gesehen sind es aktuell schon Subklassen, weil ich noch eine „Caller“ Klasse dazwischen habe, mit der man die jeweils benötigte Klasse laden kann. Also man kann die bestehenden Klassen auf 2 Arten nutzen, aber Klassen scheinen mir generell ein ziemlich ungeeignetes Format für diese Art Content zu sein.

      --
      Hosen sind Blau
      1. Wenn Du Code generierst, ist es im Prinzip egal wieviele Klassen Du hast. Du instanziierst sie ja hoffentlich nicht zu Fuß, sondern über eine Factory - und wenn Du der sowas sagst wie

           interface MessageProvider {
              public function provideStrings($topic);
           }
        
           
           $mp = MessageFactory::GetMessages("de");
        
           $i18nStrings = $mp->provideStrings($topic);
        

        Dann kann es Dir im Code total wurscht sein, ob die MessageFactory einen DatabaseMessageProvider erzeugt (der eine Table liest), einen XmlFileMessageProvider (der XML importiert) oder einen DeMessageProvider (der die Texte konstant enthält). All das sind valide Implementierungen, und je nach Implementierung stellst Du deine Strings anders bereit.

        Wichtig finde ich nur, dass deine provideStrings-Methode einen Parameter hat, mit dem Du Ressourcen zu einem bestimmten Oberbegriff abrufen kannst. Es wäre sicherlich Zeitverschwendung, immer alle deutschen Texte für alle HTML Seiten deines Projekts zu laden. Und du kapselst damit die technische Abbildung deiner Ressourcen. Warum soll dein Code wissen, ob "formular.error.categories" und "test" in der gleichen Ressourcendatei liegen. Sag dem Provider, dass Du diese Ressource willst, und der soll sich drum kümmern, in welcher Datei er gucken muss. Ob er das zur Laufzeit entdeckt, oder dein Generator es ihm einspeichert - das ist ein Implementierungsdetail. Möglicherweise wirft das nun deinen bestehenden Code durcheinander, weil der eine Array-Hierarchie erwartet, da könntest Du Dir einmal das ArrayAccess Interface anschauen, um die Ressourcen-Arrays hinter einem Objekt zu verstecken.

        Eine DB-gestützte Ressourcenhaltung ist - finde ich - als Entwicklungsplattform gar nicht verkehrt, vor allem nicht, wenn Du sie schon hast und damit zurecht kommst. Wenn Du da umsteigen willst - nagut, da gibt es viele Möglichkeiten, wie man das designen kann, und ich würde an deiner Stelle dann nach einem Standardtool für das Management von internationalisierten Ressourcen suchen.

        Verkehrt wäre nur, wenn Live-System und Entwicklungsungebung auf die gleiche Datenbasis zugreifen, da muss auf jeden Fall ein Deployment-Schritt dazwischen sein. Dieser Deployment-Schritt konvertiert dann auch nach Bedarf das Datenformat der Entwicklungsumgebung für die Produktivumgebung.

        Das kann das Kopieren in eine andere DB sein, das kann das Generieren von PHP Klasssen sein, oder das Generieren von XML-Dateien. Muss auch nicht XML sein - es gibt andere Formate, die ggf. effizienter sind, z.B. kannst Du das Array für eine Sprache (oder ein Topic in einer Sprache) erzeugen, serialisieren und den serialisierten String als Datei speichern. Die heißt dann messages.de oder bestellform_messages.de oder messages/de/bestellform.res, da kannst Du Dich austoben.

        Du kannst es auch so machen, dass Du in der Entwicklungsumgebung mit einem DatabaseMessageProvider arbeitest, der direkt auf die Texttabellen deines Ressourcenpflegesystems zugreift, und im Produktivsystem nutzt Du einen anderen MessageProvider, der auf die Produktionsversion der Ressourcen zugreift - in welchem Format auch immer.

        Wenn es nicht zu viele Texte sind, spricht nichts gegen generierte Klassen. Bei einer größeren Webseite würde ich die Ressourcen schon in Topics aufteilen und nur die Texte für dieses Topic laden.

        Nun ja. Das war jetzt ein rolf.brain.dump(). Vielleicht ist was für Dich dabei :)

        Rolf

        1. Wenn Du Code generierst, ist es im Prinzip egal wieviele Klassen Du hast. Du instanziierst sie ja hoffentlich nicht zu Fuß, sondern über eine Factory - und wenn Du der sowas sagst wie

             interface MessageProvider {
                public function provideStrings($topic);
             }
          

          Ich habe soetwas ähnliches schon quasi „OnBoard“, wobei dein Vorschlag besser klingt. Da es mir schwer fällt, das Tool zu erklären, habe ich mal eine Demofunktion hinzugefügt. Im Grunde ist die Aufgabe das Tools, aus den Daten in der DB verschachtelte Arrays zu erzeugen. Was ich davor oder dahinter schreibe, ist variabel. Auf der Startseite ist ein Array zu sehen. Diese Daten kommen direkt aus der DB, es ist aber dieselbe Funktion, mit der ich auch die finalen Klassen generiere.

          Dann kann es Dir im Code total wurscht sein, ob die MessageFactory einen DatabaseMessageProvider erzeugt (der eine Table liest), einen XmlFileMessageProvider (der XML importiert) oder einen DeMessageProvider (der die Texte konstant enthält). All das sind valide Implementierungen, und je nach Implementierung stellst Du deine Strings anders bereit.

          An sich 'ne gute Idee, aber ich wollte von XML eigentlich ganz weg, da ich in XML keine Vorteile mehr sehe. Da ist JSON doch um ein vielfaches praktischer. Aber mal sehen, drüber schlafen werde ich auf jedenfall.

          Wichtig finde ich nur, dass deine provideStrings-Methode einen Parameter hat, mit dem Du Ressourcen zu einem bestimmten Oberbegriff abrufen kannst.

          Ich wusste die ganze Zeit, das da was fehlt. Ja, kommt ins nächste Update 😀

          Es wäre sicherlich Zeitverschwendung, immer alle deutschen Texte für alle HTML Seiten deines Projekts zu laden. Und du kapselst damit die technische Abbildung deiner Ressourcen.

          Eigentlich ist die Idee dahinter genau die gewesen. Also jeweils die Sprachdatei laden, die gerade benötigt wird und alles bereithalten. Aber den optionalen Parameter werde ich bei nächster Gelegenheit einbauen, der fehlt wirklich.

          da könntest Du Dir einmal das ArrayAccess Interface anschauen, um die Ressourcen-Arrays hinter einem Objekt zu verstecken.

          Das kenne ich noch nicht, sieht interessant aus.

          Eine DB-gestützte Ressourcenhaltung ist - finde ich - als Entwicklungsplattform gar nicht verkehrt

          Es ist seit Jahren schon eine Plage, weil es da irgendwie nichts brauchbares für gibt. Oder nichts, was dem nahe kommt, wie ich es gerne hätte.

          Verkehrt wäre nur, wenn Live-System und Entwicklungsungebung auf die gleiche Datenbasis zugreifen, da muss auf jeden Fall ein Deployment-Schritt dazwischen sein. Dieser Deployment-Schritt konvertiert dann auch nach Bedarf das Datenformat der Entwicklungsumgebung für die Produktivumgebung.

          Ich hatte da eigentlich genau andersherum gedacht. Also Lokal nutze ich die Daten aus der DB, damit ich nicht für jeden neu hinzugefügten String neue Dateien erzeugen muss. Nur Online werden die Klassen genutzt. Aber der Zugriff über die Schlüssel ist immer der gleiche. Aber ich verstehe auch deinen Ansatz hier nicht wirklich. Wie kann ich mir das vorstellen?

          Die heißt dann messages.de oder bestellform_messages.de oder messages/de/bestellform.res, da kannst Du Dich austoben.

          Ich hab's derzeit schon so, das man verschiedene Datenbanken verwalten kann und für jede Datenbank eigene Settings haben kann. Also das ist mit etwas Fantasie und Kreativität machbar 😀 Nur nicht mit Namespace, bzw. schon, aber sehr, sehr umständlich. Namespace fliegt auf jedenfall raus.

          Du kannst es auch so machen, dass Du in der Entwicklungsumgebung mit einem DatabaseMessageProvider arbeitest, der direkt auf die Texttabellen deines Ressourcenpflegesystems zugreift, und im Produktivsystem nutzt Du einen anderen MessageProvider, der auf die Produktionsversion der Ressourcen zugreift - in welchem Format auch immer.

          Leider verstehe ich diesen Punkt nicht, oder denke zu kompliziert. Auf der Demoseite kannst du sehen, was der aktuelle Stand ist. Auch mit ein paar Beispielen, leider in Englisch. Hoffe, das ist kein Problem, vor allem mein Englisch 😂 Eventuell könntest du mir anhand des Online-Beispiels erklären, was ich wie machen könnte, oder was dir da Spontan einfällt oder gar auffällt.

          Nun ja. Das war jetzt ein rolf.brain.dump(). Vielleicht ist was für Dich dabei :)

          Und ein wohlgeformter dazu. Ja, war auf jedenfall einiges dabei, Danke.

          --
          Hosen sind Blau