Gunnar Bittersmann: mehrsprachige Texte in Scripten

Hello out there!

Für Programme in verschiedenen Sprachversionen ist es ja sinnvoll, Ausgabetexte nicht im Quelltext zu verstreuen, sondern gesammelt zu notieren, also bspw. nicht

var heading = document.createElement("h1");  
heading.appendChild(document.createTextNode("Die Allgemeine Erklärung der Menschenrechte"));  
document.body.appendChild(heading);  
  
var article1 = document.createElement("p");  
article1.appendChild(document.createTextNode("Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen."));  
document.body.appendChild(article1);

und dann für die englische Version eine Extra-Version des Programms, sondern ein Programm mit allen Sprachen eher so:

var textHeading = {  
  de: "Die Allgemeine Erklärung der Menschenrechte",  
  en: "Universal Declaration of Human Rights"  
};  
  
var textArticle1 = {  
  de: "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen.",  
  en: "All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."  
};  
  
var lang = document.body.parentNode.lang; // sei im <html>-Tag das 'lang'-Attribut gesetzt  
  
var heading = document.createElement("h1");  
heading.appendChild(document.createTextNode(textHeading[lang]));  
document.body.appendChild(heading);  
var article1 = document.createElement("p");  
article1.appendChild(document.createTextNode(textArticle1[lang]));  
document.body.appendChild(article1);

1. Frage: Oder sollte man doch eher alle Texte in einem Objekt sammeln?

var text = {};  
  
text.heading = {  
  de: "Die Allgemeine Erklärung der Menschenrechte",  
  en: "Universal Declaration of Human Rights"  
};  
  
text.article1 = {  
  de: "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen.",  
  en: "All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."  
};  
  
var lang = document.body.parentNode.lang; // sei im <html>-Tag das 'lang'-Attribut gesetzt  
  
var heading = document.createElement("h1");  
heading.appendChild(document.createTextNode(text.heading[lang]));  
document.body.appendChild(heading);  
var article1 = document.createElement("p");  
article1.appendChild(document.createTextNode(text.article1[lang]));  
document.body.appendChild(article1);

2. Frage: Oder doch lieber so

var text = {};  
  
text.de = {  
  heading: "Die Allgemeine Erklärung der Menschenrechte",  
  article1: "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen."  
};  
  
text.en = {  
  heading: "Universal Declaration of Human Rights",  
  article1: "All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."  
}  
  
var lang = document.body.parentNode.lang; // sei im <html>-Tag das 'lang'-Attribut gesetzt  
  
var heading = document.createElement("h1");  
heading.appendChild(document.createTextNode(text[lang].heading));  
document.body.appendChild(heading);  
var article1 = document.createElement("p");  
article1.appendChild(document.createTextNode(text[lang].article1));  
document.body.appendChild(article1);

Die letzte Variante dürfte das Hinzufügen einer weiteren Sprache vereinfachen: copy and paste und übersetzen aller Texte.

Das Halten aller Sprachversionen desselben Texts in einem Objekt erscheint mir allerdings intuitiver und logischer, es macht auch durch die Nähe der entsprechenden Texte das Übersetzen einfacher. Allerdings muss man mehr aufpassen, dass man nicht bei einem Text das Hinzufügen der neuen Sprache vergisst.

Soll nicht eine neue Sprache, sondern ein neuer Text hinzugefügt werden, gilt alles andersrum (bis auf die Intuitivität). Wie würdet Ihr das implementieren?

See ya up the road,
Gunnar

--
„Wer Gründe anhört, kommt in Gefahr nachzugeben.“ (Goethe)
  1. Hallo Gunnar.

    Soll nicht eine neue Sprache, sondern ein neuer Text hinzugefügt werden, gilt alles andersrum (bis auf die Intuitivität). Wie würdet Ihr das implementieren?

    Ich persönlich würde den gettext-Ansatz bevorzugen. Hierbei werden Meldungen überall einfach direkt notiert, und ein gettext-Funktionsaufruf (üblicherweise „_()“) drumherum gepackt.

    Diese Funktion nimmt den String entgegen und nutzt ihn als ID um in Übersetzungen (hier: Objekten) das passende Pendant oder, falls nicht vorhanden, den Originalstring zurückzugeben.

    Also in etwa so:

    var de = {  
      "A translation " : "Eine Übersetzung ",  
      "ain't not " : "ist nicht ",  
      "difficult." : "schwierig."  
    };  
      
    function _(msg) {  
      
      var locale = getlocale(); // Müsstest du selbst implementieren, entspräche, „de“, „en“, …  
      
      return (window[locale][msg] ? window[locale][msg] : msg); // Übersetzung oder Originalstring  
    }  
      
    alert(_("A translation ") +  
          _("ain't not ") +  
          _("difficult."));
    

    Die Übersetzungen selbst könnten natürlich ebenso an ein beliebiges anderes Objekt neben window gekoppelt werden.

    Einen schönen Freitag noch.

    Gruß, Mathias

    --
    ie:% fl:| br:< va:) ls:& fo:) rl:( n4:~ ss:) de:] js:| mo:| zu:)
    debian/rules
    1. Hello out there!

      "A translation ain't not difficult."

      Hm, manchmal doch. ;-) "ain't not"??

      Aber auch nicht dumm, der Ansatz. Da immer der Text in einer Sprache vollständig im Quelltext steht, ist der Quelltext besser lesbar.

      Wenn ich das richtig verstanden habe, gibt es für verschiedene Sprachen jeweils separate .mo-/.po-Dateien, nicht alle Sprachen in einer Sammlung?

      See ya up the road,
      Gunnar

      --
      „Wer Gründe anhört, kommt in Gefahr nachzugeben.“ (Goethe)
      1. Hallo Gunnar.

        "A translation ain't not difficult."

        Hm, manchmal doch. ;-) "ain't not"??

        Keine Ahnung. Sprang mir spontan in den Kopf.

        Aber auch nicht dumm, der Ansatz. Da immer der Text in einer Sprache vollständig im Quelltext steht, ist der Quelltext besser lesbar.

        Man müsste sich nur logischerweise auf eine Sprache festlegen und diese konsistent überall einsetzen. Auch der gettext-Aufruf darf nicht vergessen werden. (Ich spreche aus Erfahrung, deshalb die extra Erwähnung.)

        Wenn ich das richtig verstanden habe, gibt es für verschiedene Sprachen jeweils separate .mo-/.po-Dateien, nicht alle Sprachen in einer Sammlung?

        Normalerweise, ja. Da dies aber in JS etwas umständlich umsetzbar wäre, mein Vorschlag die Dateien durch Objekte zu repräsentieren.

        Einen schönen Freitag noch.

        Gruß, Mathias

        --
        ie:% fl:| br:< va:) ls:& fo:) rl:( n4:~ ss:) de:] js:| mo:| zu:)
        debian/rules
        1. Hello out there!

          "A translation ain't not difficult."

          Hm, manchmal doch. ;-) "ain't not"??

          Keine Ahnung. Sprang mir spontan in den Kopf.

          Die doppelte Verneinung?

          Wenn ich das richtig verstanden habe, gibt es für verschiedene Sprachen jeweils separate .mo-/.po-Dateien, nicht alle Sprachen in einer Sammlung?

          Normalerweise, ja. Da dies aber in JS etwas umständlich umsetzbar wäre,

          In program.de.html stünde

          <script type="text/javascript" src="text.de.js"/>  
          <script type="text/javascript" src="program.js"/>
          

          in program.en.html stünde

          <script type="text/javascript" src="text.en.js"/>  
          <script type="text/javascript" src="program.js"/>
          

          mein Vorschlag die Dateien durch Objekte zu repräsentieren.

          Was immer noch meine 2. Frage offen ließe: erst nach Sprachen oder erst nach Texten geordnet?

          See ya up the road,
          Gunnar

          --
          „Wer Gründe anhört, kommt in Gefahr nachzugeben.“ (Goethe)
          1. Hallo Gunnar.

            "A translation ain't not difficult."

            Hm, manchmal doch. ;-) "ain't not"??

            Keine Ahnung. Sprang mir spontan in den Kopf.

            Die doppelte Verneinung?

            Ja. Frag’ mich nicht.

            Wenn ich das richtig verstanden habe, gibt es für verschiedene Sprachen jeweils separate .mo-/.po-Dateien, nicht alle Sprachen in einer Sammlung?

            Normalerweise, ja. Da dies aber in JS etwas umständlich umsetzbar wäre,

            In program.de.html stünde

            <script type="text/javascript" src="text.de.js"/>

            <script type="text/javascript" src="program.js"/>

            
            >   
            > in program.en.html stünde  
            > ~~~html
            
            <script type="text/javascript" src="text.en.js"/>  
            
            > <script type="text/javascript" src="program.js"/>
            
            

            Gut, wenn eine Trennung auf diese Weise möglich wäre, würde ich diese bevorzugen.

            mein Vorschlag die Dateien durch Objekte zu repräsentieren.

            Was immer noch meine 2. Frage offen ließe: erst nach Sprachen oder erst nach Texten geordnet?

            Diese Frage stellt sich doch gar nicht, da die Übersetzungen sowieso voneinander getrennt gespeichert werden. (Egal ob als Datei oder Objekt.) Und innerhalb des Objektes ist die Sortierung der Texte sowieso egal, da ja wie gesagt über deren Inhalt auf die Übersetzung zugegriffen wird.

            Einen schönen Freitag noch.

            Gruß, Mathias

            --
            ie:% fl:| br:< va:) ls:& fo:) rl:( n4:~ ss:) de:] js:| mo:| zu:)
            debian/rules
            1. Hello out there!

              mein Vorschlag die Dateien durch Objekte zu repräsentieren.

              Was immer noch meine 2. Frage offen ließe: erst nach Sprachen oder erst nach Texten geordnet?

              Diese Frage stellt sich doch gar nicht, da die Übersetzungen sowieso voneinander getrennt gespeichert werden. (Egal ob als Datei oder Objekt.)

              Naja, könnte ja wie in meinem Beispiel auch alles in einem Objekt stehen, dann wäre beides möglich. Nur die Implementierung der Funktion _() wäre geringfügig anders.

              Allerdings ist die Frage aus einem anderen Grund leicht beantwortet: Die Übersetzungen werden ja nicht zwangläufig alle vom Programmierer selbst gemacht, evtl. auch für jede Sprache von einem anderen Übersetzer. Da wäre es idiotisch, nicht zuerst nach Sprachen zu ordnen.

              See ya up the road,
              Gunnar

              --
              „Wer Gründe anhört, kommt in Gefahr nachzugeben.“ (Goethe)
    2. Hallo Mathias,

      Ich persönlich würde den gettext-Ansatz bevorzugen.

      Hey! Als ich anfing zu schreiben, warst Du noch nicht da! ;)

      Tim

      1. Hallo Tim.

        Ich persönlich würde den gettext-Ansatz bevorzugen.

        Hey! Als ich anfing zu schreiben, warst Du noch nicht da! ;)

        _("First come, first served."); // ;-)

        Einen schönen Freitag noch.

        Gruß, Mathias

        --
        ie:% fl:| br:< va:) ls:& fo:) rl:( n4:~ ss:) de:] js:| mo:| zu:)
        debian/rules
  2. Hallo Gunnar,

    Wie würdet Ihr das implementieren?

    Ich würde mich einfach am System von GNU gettext orientieren. In diesem sind Programmierung und Übersetzung stark getrennt, weil man im Prinzip erst nach der Programmierung weiss, welche Strings zu übersetzen sind. Also erstmal in Phase 1 fröhlich drauf los programmieren:

    ~~~javascript var heading = document.createElement("h1");
      heading.appendChild(document.createTextNode(_("Die Allgemeine Erklärung der
                                                     Menschenrechte")));
      document.body.appendChild(heading);

    var article1 = document.createElement("p");
      article1.appendChild(document.createTextNode(_("Alle Menschen sind frei und
               gleich an Würde und Rechten geboren. Sie sind mit Vernunft und
               Gewissen begabt und sollen einander im Geist der Brüderlichkeit
               begegnen.")));
      document.body.appendChild(article1);

      
    Beachte, dass ich die Texte in einen unauffälligen Funktionsaufruf eingebettet habe, die Funktion heisst aus Konvention "\_". In Phase 1 tut die erstmal noch nix:  
      
      `var _ = function (text) { return text; }`{:.language-javascript}  
      
    Ist die Programmierung der Logik fröhlich abgeschlossen, kommt nun Phase 2, das Extrahieren der Strings. Die billigste – aber nicht serh gute – Version wäre dies hier:  
      
      ~~~javascript
    var strings = [];  
      var _ = function (text) { strings.push(text); return text; }
    

    D.h. im Prinzip wird bei jedem Aufruf von _() der jeweilige String in einer Datenstruktur gesammelt. Der Nachteil ist, dass dies aus dem Programm selber geschieht, man weiss also nicht, ob alle Aufrufe von _() überhaupt durch die Programmlogik vorgenommen wird.

    Geschickter wäre es, seine Javascript-Datei durch ein anderes Programm analysieren zu lassen. xgettext - das bei GNU gettext benutzt wird - hat offenbar keine Unterstützung für Javascript. Und es hat auch einen Nachteil, dass es seine Übersetzungs-Datenstrukturen in einem Format auswirft, das für Javascript nicht besonders geeignet ist. Ich würde mir wohl ein Skript basteln, dass programmlogik.js mit einen Regulären Ausdruck durchgeht, nach dem Muster „_(" irgendwas ") sucht, das irgendwas extrahiert und als JSON ausspuckt.

    Man kann das natürlich auch per Hand machen, hauptsache man hat hinterher ein JSON-Objekt, in dem die verschiedenen Strings der Programmierphase die Schlüsseln sind. Dieses JSON-Objekt liefert nämlich dann die Datenstruktur zum übersetzen:

    ~~~javascript var translations = {
          "Die Allgemeine Erklärung der Menschenrechte" : {
              de : "Die Allgemeine Erklärung der Menschenrechte",
              en : "Universal Declaration of Human Rights"
          },
          "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen." : {
              de : "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen.",
              en : "All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."
          }
      };

      
    Und nun kann man die ominöse Unterstrich-Funktion einfach in die Übersetzungsfunktion umwandeln:  
      
      ~~~javascript
    var locale = "en";  
      var _ = function (text) { return translations[text][locale] || text; };
    

    Die zu übersetzenden Texte als Schlüssel zu benutzen hat den Vorteil, dass man sich anders als in Deinem Beispiel sich nicht mehr um die jeweilige Struktur kümmern muss. Unzählige Variablen wie "text.en.heading1" sind also überflüssig.

    Dies ist natürlich nur so hinskizziert. Idealerweise macht man die Übersetzungsfunktion noch intelligenter, z.B. indem man ihr Unterstützung für Language Tags einbaut, wie ich das hier mal skizziert habe. Die Königsklasse wäre es wohl noch, die Sprache automatisch aus dem Dokument bzw. den Elternelementen zu ermitteln – aber ich glaube nicht, dass das möglich wäre, wenn man nicht das DOM komplett kapselt und in jeder DOM-Einfüge-Funktion die derzeit aktuelle Sprache des Dokumentes zur Verfügung hat. Dieses könnte man dann _() übergeben. Komisch, dass es das noch nicht in den jeweiligen Bibliotheken gibt. ;)

    Tim

    1. Hello out there!

      Vielen Dank an dich und Mathias!

      ~~~javascript

      var translations = {

      "Die Allgemeine Erklärung der Menschenrechte" : {
                de : "Die Allgemeine Erklärung der Menschenrechte",
                en : "Universal Declaration of Human Rights"
            },
            "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen." : {
                de : "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen.",
                en : "All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."
            }
        };

        
      In Anbetracht [meiner letzten Überlegung](https://forum.selfhtml.org/?t=158383&m=1029847) wäre es wohl vorteilhafter, die Datenstruktur sähe so aus:  
        
        ~~~javascript
      var translations = {  
            de : {  
                "Die Allgemeine Erklärung der Menschenrechte" : "Die Allgemeine Erklärung der Menschenrechte",  
                "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen." : "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen."  
            },  
            en : {  
                "Die Allgemeine Erklärung der Menschenrechte" : "Universal Declaration of Human Rights",  
                "Alle Menschen sind frei und gleich an Würde und Rechten geboren. Sie sind mit Vernunft und Gewissen begabt und sollen einander im Geist der Brüderlichkeit begegnen." : "All human beings are born free and equal in dignity and rights. They are endowed with reason and conscience and should act towards one another in a spirit of brotherhood."  
            }  
        };
      

      Und die ominöse Unterstrich-Funktion:
        ~~~javascript var locale = "en";
        var _ = function (text) { return translations[locale][text] || text; };

        
        
      Q: Macht es Sinn, auch die Originalsprache in diesem Objekt nochmals abzulegen? Es wäre ja auch möglich:  
        `var _ = function (text) { return (locale != "de" && translations[locale][text] || text; };`{:.language-javascript}  
        
      A: Ja, macht Sinn, denn dann müssen spätere Änderungen in der Formulierung nur an dieser Stelle vorgenommen werden, nicht im Programmcode.  
        
      See ya up the road,  
      Gunnar
      
      -- 
      „Wer Gründe anhört, kommt in Gefahr nachzugeben.“ (Goethe)