Multikulti mit Sencha Touch – Übersetzung einer App

Eine Frage, die sich mir schnell stellte, als ich mit der Entwicklung der ersten App auf Basis von Sencha Touch begann, war die Frage nach der Internationalisierung. Wie konnte man am besten eine mehrsprachige Anwendung umsetzen? Ich habe ein wenig im Netz gesucht und mich dabei inspirieren lassen. Herausgekommen ist eine Erweiterung für Sencha Touch, die es ermöglicht JSON Dateien zu laden, in denen Texte gespeichert sind.

Dieses kleine Helferlein war für mich einfach eine erste Fingerübung im Umgang mit Sencha Touch.

Mehr gibt es dazu eigentlich nicht zu sagen. Per XMLHttpRequest werden die gewünschten Dateien für die angegebenen Sprachen einmalig geladen und später greift man auf diese Daten über den Pfad in dem JSON Objekt zu.

Das funktioniert auch in einem PhoneGap-Projekt.
Für Verbesserungsvorschläge bin ich jederzeit dankbar 😉

 

  1. /**
  2.  * aflx - always flexible
  3.  * http://blog.aflx.de
  4.  * ak@aflx.de
  5.  *
  6.  * Copyright 2011 Alexander Keller
  7.  * All Rights Reserved.
  8.  */
  9.  
  10. Ext.ns('Ext.util');
  11.  
  12. /**
  13.  * This class provides the possibility to load files for different languages containing
  14.  * a JSON object, where the leaves are strings/translations. Later you can access theses
  15.  * strings by calling the __() function passing the path to the leave.
  16.  */
  17. Ext.util.Translation = Ext.extend(Object, {
  18.   language: "de",
  19.   files: [ "application" ],
  20.   languages: [ "de" ],
  21.   pathPrefix: "app/resources",
  22.   loadingStack: {},
  23.   data: {},
  24.  
  25.   // Recursive looking for the given path
  26.   getTranslation: function(value, data) {
  27.     var prop = value.shift();
  28.  
  29.     if (data == null) {
  30.       console.log("Could not load language resources!");
  31.       return false;
  32.     }
  33.  
  34.     if (typeof(data[prop]) == "object") {
  35.       return this.getTranslation(value, data[prop]);
  36.     }
  37.  
  38.     return data[prop];
  39.   },
  40.  
  41.   // extract the language from a given string (example: de_DE => de)
  42.   parseLang: function(lang) {
  43.     lang = lang.replace(/_/, '-').toLowerCase();
  44.     if (lang.length > 3) {
  45.       lang = lang.substring(0, 3) + lang.substring(3).toUpperCase();
  46.     }
  47.     return lang;
  48.   },
  49.  
  50.   // downloads a file and saves it
  51.   loadResources: function(resource, options) {
  52.     var me = this;
  53.  
  54.     if (options == null) {
  55.       options = {};
  56.     }
  57.  
  58.     if (options.language == null) {
  59.       options.language = this.language;
  60.     }
  61.  
  62.     var file = resource + "-" + options.language + ".json";
  63.  
  64.     if (options.pathPrefix != null) {
  65.       file = options.pathPrefix + "/" + resource + "-" + options.language + ".json";
  66.     }
  67.  
  68.     if (this.loadingStack[file] == "loading" ||
  69.       this.loadingStack[file] == "ready") {
  70.       return;
  71.     } else if (this.loadingStack[file] == "error") {
  72.       return;
  73.     } else {
  74.       this.loadingStack[file] = "loading";
  75.     }
  76.  
  77.     var xhr;
  78.     try {
  79.       xhr = new ActiveXObject('Msxml2.XMLHTTP');
  80.     } catch (e) {
  81.       try {
  82.         xhr = new ActiveXObject('Microsoft.XMLHTTP');
  83.       } catch (e2) {
  84.         try {
  85.           xhr = new XMLHttpRequest();
  86.       } catch (e3) {
  87.           xhr = false;
  88.         }
  89.       }
  90.     }
  91.  
  92.     xhr.onreadystatechange = function() {
  93.       if(xhr.readyState == 4) {
  94.       if (xhr.responseText) {
  95.           var data = Ext.util.JSON.decode(xhr.responseText);
  96.     me.data[options.language] = data;
  97.     me.loadingStack[file] = "ready";
  98.     return true;
  99.   } else {
  100.     console.log("Could not load language resources!");
  101.     me.loadingStack[file] = "error";
  102.     return false;
  103.   }
  104.       }
  105.     };
  106.  
  107.     xhr.open("GET", file, XMLHttpRequest.SYNC);
  108.     xhr.send(null);
  109.   },
  110.  
  111.   // searching for a path in the JSON object
  112.   translate: function(value, options) {
  113.     if (options == null) {
  114.       options = {};
  115.     }
  116.  
  117.     // if not requesting the translation for a specific language,
  118.     // use the active language
  119.     if (options.language == null) {
  120.       options.language = this.language;
  121.     }
  122.  
  123.     // load the files if not already done
  124.     if (options.resources != null) {
  125.       if (options.pathPrefix == null) {
  126.   options.pathPrefix = "";
  127.       }
  128.  
  129.       for(var i = 0; i < options.resources.length; i++) {
  130.         this.loadResources(options.resources[i], options);
  131.       }
  132.     }
  133.  
  134.     return this.getTranslation(value.split("."), this.data[options.language]);
  135.   }
  136. });
  137.  
  138. // A reference to an instance of the translator (singleton).
  139. Ext.util.Translation.self = null;
  140.  
  141. /**
  142.  * The setup for the translations.
  143.  * Call this function before using this class...
  144.  *
  145.  * Usage example
  146.  * =============
  147.  *
  148.  * Ext.util.Translation.init({
  149.  *    files: [ "application" ],
  150.  *    language: "de",
  151.  *    languages: [ "de" ],
  152.  *    pathPrefix: "app/resources"
  153.  * });
  154.  *
  155.  */
  156. Ext.util.Translation.init = function(options) {
  157.   Ext.util.Translation.self = new Ext.util.Translation();
  158.  
  159.   Ext.util.Translation.load(options);
  160. };
  161.  
  162. /**
  163.  * The load() function can be used to load an additional language pack.
  164.  *
  165.  * @param options - an object
  166.  *  pathPrefix:     the path relative to the html file where this js is include
  167.  *  languages:  languages to load (use de, en, it etc...)
  168.  *  language:   the active language, one of the languages array
  169.  *  files:    an array with the files to load (It's not the real filename,
  170.  *      it's just the first part of the filename. If you want to load
  171.  *      application-de.json you have to add "application" to this array and
  172.  *      "de" to the languages array.)
  173.  */
  174. Ext.util.Translation.load = function(options) {
  175.   for (var i = 0; i < options.files.length; i++) {
  176.     for (var j = 0; j < options.languages.length; j++) {
  177.       Ext.util.Translation.self.loadResources(
  178.         options.files[i],{ pathPrefix: options.pathPrefix, language: options.languages[j]}
  179.       );
  180.     }
  181.   }
  182. };
  183.  
  184. /**
  185.  * The translation texts are saved as a JSON object. So this function does not translate
  186.  * a string, it returns just a value for a given path in this JSON object.
  187.  *
  188.  * Usage example:
  189.  *
  190.  * example-de.json
  191.  * ===============
  192.  *
  193.  * {
  194.  *     "title": {
  195.  *         "hello_world": "Hallo Welt!"
  196.  *     }
  197.  * }
  198.  *
  199.  * Getting the translation
  200.  * =======================
  201.  *
  202.  * var trans = Ext.util.Translation.__('title.hello_world');
  203.  */
  204. Ext.util.Translation.__ = function(value) {
  205.   if (Ext.util.Translation.self == null) {
  206.     Ext.util.Translation.init({
  207.       files: Ext.util.Translation.self.files,
  208.       languages: Ext.util.Translation.self.languages,
  209.       pathPrefix: Ext.util.Translation.self.pathPrefix
  210.     });
  211.   }
  212.  
  213.   return Ext.util.Translation.self.translate(value);
  214. };

3 Gedanken zu „Multikulti mit Sencha Touch – Übersetzung einer App

  1. Hey Alex,
    mir geistert seit einiger Zeit schon folgendes im Kopf herum:
    Wenn PhoneGap AJAX will, ist es dann möglich mit GWT Mobile-Apps zu schreiben?
    Laut Google sieht es tatsächlich so aus.

    Die Schritte wären dann:
    Anwendung schreiben in GWT (Java mit allen Sprachfeatures)
    -> compile to AJAX mit GWT
    -> compile to App mit PhoneGap

    Ich hab das ganze natürlich noch nicht selber ausprobiert, aber gerade im Zusammenhang mit i18n würde mich interessieren ob die GWT-eigenen Mechnaismen, bei denen zur Compilezeit die Sprache in die Anwendung geschraubt werden kann greifen können (der Installer müsste dann wohl die richtige Endanwendung wählen).
    Selbst wenn das ungünstig ist, gibt es von GWT-Seite schon vordefinerte Mechanismen um Resourcen zur Laufzeit zu lokalisieren (also auch Bilder und Files etc.).

    Das ganze find ich recht spannend und ich werde dich bei Gelegenheit mal dazu anrufen falls ich Zeit finde mal selber in Richtung mobile Entwicklung zu linsen :).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.