Load translations from the server & add language to settings applet
authorJack Allnutt <jack@allnutt.eu>
Fri, 5 Jul 2013 03:04:21 +0000 (04:04 +0100)
committerJack Allnutt <jack@allnutt.eu>
Fri, 5 Jul 2013 03:04:21 +0000 (04:04 +0100)
.gitignore
client/assets/locales/template.po [new file with mode: 0755]
client/assets/locales/translations.json [new file with mode: 0755]
client/assets/src/app.js
client/assets/src/applets/settings.js
client/assets/src/build.js
client/assets/src/index.html.tmpl
client/assets/src/models/application.js
package.json
server/httphandler.js

index 74651101b729245ad7d341ad5ffa92ca581fe267..0d105f30f7ff9cdb0cb4782e1b74cbba4e4caec1 100644 (file)
@@ -3,7 +3,9 @@ node/node_modules/
 node_modules/
 doc/
 client/assets/kiwi.js
-client/assets/kiwi.min.js\r
+client/assets/kiwi.min.js
+client/assets/locales/*.json
+!client/assets/locales/translations.json
 client/index.html
 kiwi.log
 kiwiirc.pid
diff --git a/client/assets/locales/template.po b/client/assets/locales/template.po
new file mode 100755 (executable)
index 0000000..4d3ef5a
--- /dev/null
@@ -0,0 +1,358 @@
+\r
+#: client/assets/src/applets/chanlist.js\r
+msgid "Channel Name"\r
+#msgstr ""\r
+\r
+msgid "Users"\r
+#msgstr ""\r
+\r
+msgid "Topic"\r
+#msgstr ""\r
+\r
+msgid "Channel List"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/applets/scripteditor.js\r
+msgid "Save"\r
+#msgstr ""\r
+\r
+msgid "Script error. %s"\r
+#msgstr ""\r
+\r
+msgid "Your script has been saved and is now active"\r
+#msgstr ""\r
+\r
+msgid "Script Editor"\r
+#msgstr\r
+\r
+\r
+\r
+#: client/assets/src/applets/settings.js\r
+msgid "Tabs"\r
+#msgstr ""\r
+\r
+msgid "List"\r
+#msgstr ""\r
+\r
+msgid "for large amouts of channels"\r
+#msgstr ""\r
+\r
+msgid "Join/part channel notifications"\r
+#msgstr ""\r
+\r
+msgid "Timestamps"\r
+#msgstr ""\r
+\r
+msgid "Mute sound notifications"\r
+#msgstr ""\r
+\r
+msgid "messages in scroll history"\r
+#msgstr ""\r
+\r
+msgid "Default IRC client"\r
+#msgstr ""\r
+\r
+msgid "Make Kiwi my default IRC client"\r
+#msgstr ""\r
+\r
+msgid "Note: Chrome or Chromium browser users may need to check their settings via %s if nothing happens"\r
+#msgstr ""\r
+\r
+msgid "Settings"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/models/applet.js\r
+msgid "Unknown Applet"\r
+#msgstr ""\r
+\r
+msgid "Loading.."\r
+#msgstr ""\r
+\r
+msgid "Not found"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/models/application.js\r
+msgid "You have been disconnected. Attempting to reconnect for you"\r
+#msgstr ""\r
+\r
+msgid "You have been disconnected. Attempting to reconnect again in %d seconds"\r
+#msgstr ""\r
+\r
+msgid "It's OK, you're connected again"\r
+#msgstr ""\r
+\r
+msgid "Settings have been saved"\r
+#msgstr ""\r
+\r
+msgid "Ignored nicks"\r
+#msgstr ""\r
+\r
+msgid "Not ignoring anybody"\r
+#msgstr ""\r
+\r
+msgid "Ignoring %s"\r
+#msgstr ""\r
+\r
+msgid "Specifiy which nick you wish to stop ignoring"\r
+#msgstr ""\r
+\r
+msgid "Stopped ignoring %s"\r
+#msgstr ""\r
+\r
+msgid "Applet "%s" does not exist"\r
+#msgstr ""\r
+\r
+msgid "Encoding modified to %s"\r
+#msgstr ""\r
+\r
+msgid "%s is not a valid encoding"\r
+#msgstr ""\r
+\r
+msgid "Encoding not specified"\r
+#msgstr ""\r
+\r
+msgid "Usage: /encoding [NEW-ENCODING]"\r
+#msgstr ""\r
+\r
+msgid "New Connection"\r
+#msgstr ""\r
+\r
+msgid "Connecting to %s:%s..."\r
+#msgstr ""\r
+\r
+msgid "Error connecting to %s:%s (%s)"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/models/channel.js\r
+msgid "%s has joined"\r
+#msgstr ""\r
+\r
+msgid "%s has quit %s"\r
+#msgstr ""\r
+\r
+msgid "%s was kicked by %s %s"\r
+#msgstr ""\r
+\r
+msgid "You have been kicked by %s %s"\r
+#msgstr ""\r
+\r
+msgid "%s has left %s"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/models/network.js\r
+msgid "%s is not a valid channel name"\r
+#msgstr ""\r
+\r
+msgid "Disconnected from the IRC network"\r
+#msgstr ""\r
+\r
+msgid "%s is now known as %s"\r
+#msgstr ""\r
+\r
+msgid "Topic set by %s at %s"\r
+#msgstr ""\r
+\r
+msgid "%s sets mode %s"\r
+#msgstr ""\r
+\r
+msgid "%s set mode %s"\r
+#msgstr ""\r
+\r
+msgid "Channels: %s"\r
+#msgstr ""\r
+\r
+msgid "Connected to server: %s %s"\r
+#msgstr ""\r
+\r
+msgid "Idle for %s, signed on %s"\r
+#msgstr ""\r
+\r
+msgid "Away: %s"\r
+#msgstr ""\r
+\r
+msgid "Idle for %s"\r
+#msgstr ""\r
+\r
+msgid "No such nick"\r
+#msgstr ""\r
+\r
+msgid "You are banned from %s. %s"\r
+#msgstr ""\r
+\r
+msgid "Bad channel key for %s"\r
+#msgstr ""\r
+\r
+msgid "%s is invite only."\r
+#msgstr ""\r
+\r
+msgid "%s is full."\r
+#msgstr ""\r
+\r
+msgid "The nickname "%s" is already in use. Please select a new nickname"\r
+#msgstr ""\r
+\r
+msgid "Incorrect password given"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/application.js\r
+msgid "This will close all KiwiIRC conversations. Are you sure you want to close this window?"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/channel.js\r
+msgid "Joining channel.."\r
+#msgstr ""\r
+\r
+msgid "Topic for %s is: %s"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/mediamessage.js\r
+msgid "Close media"\r
+#msgstr ""\r
+\r
+msgid "Not found"\r
+#msgstr ""\r
+\r
+msgid "Loading tweet"\r
+#msgstr ""\r
+\r
+msgid "Loading image"\r
+#msgstr ""\r
+\r
+msgid "Loading Reddit thread"\r
+#msgstr ""\r
+\r
+msgid "Loading gist"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/nickchangegbox.js\r
+msgid "New nick"\r
+#msgstr ""\r
+\r
+msgid "Change"\r
+#msgstr ""\r
+\r
+msgid "Cancel"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/panel.js\r
+msgid "People are talking!"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/serverselect.js\r
+msgid "Think of a nickname..."\r
+#msgstr ""\r
+\r
+msgid "Nickname"\r
+#msgstr ""\r
+\r
+msgid "I have a password"\r
+#msgstr ""\r
+\r
+msgid "Password"\r
+#msgstr ""\r
+\r
+msgid "Channel"\r
+#msgstr ""\r
+\r
+msgid "Channel Key"\r
+#msgstr ""\r
+\r
+msgid "Channel requires a key"\r
+#msgstr ""\r
+\r
+msgid "Key"\r
+#msgstr ""\r
+\r
+msgid "Start..."\r
+#msgstr ""\r
+\r
+msgid "Server and network"\r
+#msgstr ""\r
+\r
+msgid "Server"\r
+#msgstr ""\r
+\r
+msgid "Port"\r
+#msgstr ""\r
+\r
+msgid "Powered by Kiwi IRC"\r
+#msgstr ""\r
+\r
+msgid "Select a nickname first!"\r
+#msgstr ""\r
+\r
+msgid "Connected"\r
+#msgstr ""\r
+\r
+msgid "Connecting.."\r
+#msgstr ""\r
+\r
+msgid "Nickname already taken"\r
+#msgstr ""\r
+\r
+msgid "Erroneus nickname"\r
+#msgstr ""\r
+\r
+msgid "Incorrect Password"\r
+#msgstr ""\r
+\r
+msgid "Error Connecting"\r
+#msgstr ""\r
+\r
+msgid "Server not found"\r
+#msgstr ""\r
+\r
+msgid "Connection refused"\r
+#msgstr ""\r
+\r
+\r
+\r
+#: client/assets/src/views/userbox.js\r
+msgid "Op"\r
+#msgstr ""\r
+\r
+msgid "De-op"\r
+#msgstr ""\r
+\r
+msgid "Voice"\r
+#msgstr ""\r
+\r
+msgid "De-voice"\r
+#msgstr ""\r
+\r
+msgid "Kick"\r
+#msgstr ""\r
+\r
+msgid "Ban"\r
+#msgstr ""\r
+\r
+msgid "Message"\r
+#msgstr ""\r
+\r
+msgid "Info"\r
+#msgstr ""\r
+\r
+msgid "Slap!"\r
+#msgstr ""\r
diff --git a/client/assets/locales/translations.json b/client/assets/locales/translations.json
new file mode 100755 (executable)
index 0000000..1c96b79
--- /dev/null
@@ -0,0 +1,3 @@
+{\r
+       "en-gb": "English (British)"\r
+}
\ No newline at end of file
index 16f2b7ccf4db4f4c7267aa7733c6c20ebcca433c..1dc1a7f9d569d7abccfa540151552f2ffde34b0e 100644 (file)
@@ -104,6 +104,7 @@ _kiwi.global = {
 \r
        // Entry point to start the kiwi application\r
        start: function (opts) {\r
+               var continueStart, locale;\r
                opts = opts || {};\r
 \r
         // Load the plugin manager\r
@@ -113,18 +114,33 @@ _kiwi.global = {
         _kiwi.global.settings = _kiwi.model.DataStore.instance('kiwi.settings');\r
         _kiwi.global.settings.load();\r
 \r
-        _kiwi.global.i18n = new Jed();\r
-\r
-               _kiwi.app = new _kiwi.model.Application(opts);\r
-\r
-               if (opts.kiwi_server) {\r
-                       _kiwi.app.kiwi_server = opts.kiwi_server;\r
-               }\r
-\r
-               // Start the client up\r
-               _kiwi.app.start();\r
-\r
-               return true;\r
+        continueStart = function (locale, s, xhr) {\r
+               if (locale) {\r
+                       _kiwi.global.i18n = new Jed({locale_data: locale, domain: xhr.getResponseHeader('Content-Language')});\r
+               } else {\r
+                       _kiwi.global.i18n = new Jed();\r
+               }\r
+\r
+                       _kiwi.app = new _kiwi.model.Application(opts);\r
+\r
+                       if (opts.kiwi_server) {\r
+                               _kiwi.app.kiwi_server = opts.kiwi_server;\r
+                       }\r
+\r
+                       // Start the client up\r
+                       _kiwi.app.start();\r
+        };\r
+\r
+        locale = _kiwi.global.settings.get('locale')\r
+        if (!locale) {\r
+               $.getJSON(opts.base_path + '/assets/locales/magic.json', continueStart);\r
+        } else {\r
+               if (locale === 'en-gb') {\r
+                       continueStart();\r
+               } else {\r
+                       $.getJSON(opts.base_path + '/assets/locales/' + locale + '.json', continueStart);\r
+               }\r
+        }\r
        }\r
 };\r
 \r
index 50b2475165b899c3716abfac0257512d27c51083..3327be73d5a0480e57aab3df8926f623b354888e 100644 (file)
@@ -15,6 +15,7 @@
                 timestamps: _kiwi.global.i18n.translate('Timestamps').fetch(),\r
                 mute: _kiwi.global.i18n.translate('Mute sound notifications').fetch(),\r
                 scroll_history: _kiwi.global.i18n.translate('messages in scroll history').fetch(),\r
+                languages: _kiwi.app.translations,\r
                 default_client: _kiwi.global.i18n.translate('Default IRC client').fetch(),\r
                 make_default: _kiwi.global.i18n.translate('Make Kiwi my default IRC client').fetch(),\r
                 default_note: _kiwi.global.i18n.translate('Note: Chrome or Chromium browser users may need to check their settings via %s if nothing happens').fetch('<a href="chrome://settings/handlers">chrome://settings/handlers</a>')\r
@@ -55,6 +56,9 @@
                     case 'text':\r
                         $el.val(value);\r
                         break;\r
+                    case 'select-one':\r
+                        $('[value="' + value + '"]', that.$el).prop('selected', true);\r
+                        break;\r
                     default:\r
                         $('[data-setting="' + key + '"][data-value="' + value + '"]', that.$el).addClass('active');\r
                         break;\r
@@ -65,7 +69,7 @@
         saveSettings: function (event) {\r
             var value,\r
                 settings = _kiwi.global.settings,\r
-                $setting = $(event.currentTarget, this.$el)\r
+                $setting = $(event.currentTarget, this.$el);\r
 \r
             switch (event.currentTarget.type) {\r
                 case 'checkbox':\r
@@ -75,6 +79,9 @@
                 case 'text':\r
                     value = $setting.val();\r
                     break;\r
+                case 'select-one':\r
+                    value = $(event.currentTarget[$setting.prop('selectedIndex')]).val();\r
+                    break;\r
                 default:\r
                     value = $setting.data('value');\r
                     break;\r
index 28886d57419ae6b475777ee6531ffcf662fcc536..1e486ed04a36707adba5164720398ce96cbc6a41 100644 (file)
@@ -1,6 +1,7 @@
 var fs        = require('fs'),\r
     uglifyJS  = require('uglify-js'),\r
     _         = require('lodash'),\r
+    po2json   = require('po2json'),\r
     config    = require('./../../../server/configuration.js');\r
 \r
 var FILE_ENCODING = 'utf-8',\r
@@ -100,6 +101,24 @@ console.log('kiwi.js and kiwi.min.js built');
 \r
 \r
 \r
+/**\r
+*   Convert translations from .po to .json\r
+*/\r
+var translations = [];\r
+var translation_files = fs.readdirSync(__dirname + '/../locales');\r
+translation_files.forEach(function (file) {\r
+    var locale = file.slice(0, -3),\r
+        json = '',\r
+        languages = JSON.parse(fs.readFileSync(__dirname + '/../locales/translations.json'));\r
+    if ((file.slice(-3) === '.po') && (locale !== 'template')) {\r
+        json = po2json.parseSync(__dirname + '/../locales/' + file);\r
+        fs.writeFileSync(__dirname + '/../locales/' + locale + '.json', JSON.stringify(json));\r
+        translations.push({tag: locale, language: languages[locale]});\r
+        console.log('Built translation file %s', locale + '.json');\r
+    }\r
+});\r
+\r
+\r
 \r
 \r
 \r
@@ -140,6 +159,9 @@ if (config.get().client_plugins && config.get().client_plugins.length > 0) {
     vars.client_plugins = config.get().client_plugins;\r
 }\r
 \r
+// Translations\r
+vars.translations = translations;\r
+\r
 _.each(vars, function(value, key) {\r
     if (typeof value === 'object') value = JSON.stringify(value);\r
     index_src = index_src.replace(new RegExp('<%' + key + '%>', 'g'), value);\r
@@ -148,4 +170,4 @@ _.each(vars, function(value, key) {
 fs.writeFileSync(__dirname + '/../../index.html', index_src, FILE_ENCODING);\r
 \r
 \r
-console.log('index.html built');
\ No newline at end of file
+console.log('index.html built');\r
index c94f221c1983eacf207ee49299ce1a33bcb59de5..7324b0fc84f669cbb4bb43afe72aa661b93e67c6 100644 (file)
                 </div>\r
             </section>\r
 \r
+            <section>\r
+                <h6>Language</h6>\r
+                <div class="control-group">\r
+                    <select data-setting="locale">\r
+                        <option value="en-gb">English (British)</option>\r
+                        <% _.forEach(languages, function(lang) { %>\r
+                            <option value="<%= lang.tag %>"><%= lang.language %></li>\r
+                        <% }); %>\r
+                    </select>\r
+                </div>\r
+            </section>\r
+\r
             <section class="protocol_handler">\r
                 <h6><%= default_client %></h6>\r
                 <div class="control-group">\r
                 //kiwi_server: 'http://kiwiirc.com:80',\r
 \r
                 server_settings: <%server_settings%>,\r
-                client_plugins: <%client_plugins%>\r
+                client_plugins: <%client_plugins%>,\r
+                translations: <%translations%>\r
             };\r
 \r
             // Start the app\r
index 1ce101e4a8e79661f2d9184fc54d1222cb3dc78b..3fd84f0aaf0b168bd01901ab7778731ed117814b 100644 (file)
@@ -25,6 +25,7 @@ _kiwi.model.Application = function () {
 \r
             // Any options sent down from the server\r
             this.server_settings = options[0].server_settings || {};\r
+            this.translations = options[0].translations || {};\r
 \r
             // Best guess at where the kiwi server is\r
             this.detectKiwiServer();\r
index b20a822f8354e1ea2a6429e34b084f8a12d9592f..e76397d598cf78b2c5e90b73899ac2edc59737f4 100644 (file)
@@ -22,6 +22,7 @@
     "eventemitter2": "0.4.11",\r
     "ipaddr.js": "0.1.1",\r
     "socksjs": "0.3.3",\r
-    "iconv-lite" : "0.2.10"\r
+    "iconv-lite" : "0.2.10",\r
+    "po2json": "0.0.6"\r
   }\r
 }\r
index 816cb37333d306f935c5e092ddf62733a31506d4..b975892f8af3e461beaf204f938c83ac7e484d67 100644 (file)
@@ -1,5 +1,7 @@
 var url         = require('url'),
-    node_static = require ('node-static');
+    fs          = require('fs'),
+    node_static = require('node-static'),
+    _           = require('lodash');
 
 
 
@@ -21,7 +23,7 @@ HttpHandler.prototype.serve = function (request, response) {
     if (base_path.substr(base_path.length - 1) === '/') {
         base_path = base_path.substr(0, base_path.length - 1);
     }
-    
+
     // Build the regex to match the base_path
     base_path_regex = base_path.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
 
@@ -33,6 +35,12 @@ HttpHandler.prototype.serve = function (request, response) {
         request.url = '/';
     }
 
+    // If the 'magic' translation is requested, figure out the best language to use from
+    // the Accept-Language HTTP header. If nothing is suitible, serve an empty response,
+    // Kiwi will just use the default en-gb strings baked in to it.
+    if (request.url === '/assets/locales/magic.json') {
+        return serveMagicLocale.call(this, request, response);
+    }
 
     this.file_server.serve(request, response, function (err) {
         if (err) {
@@ -40,4 +48,47 @@ HttpHandler.prototype.serve = function (request, response) {
             response.end();
         }
     });
-};
\ No newline at end of file
+};
+
+var serveMagicLocale = function (request, response) {
+    var langs = [],
+        available = [],
+        i = 0;
+    if (request.headers['accept-language']) {
+        // Example: en-gb,en;q=0.5
+        langs = request.headers['accept-language'].split(',');
+        available = (function () {
+            var files = [],
+                l = [];
+            files = fs.readdirSync('client/assets/locales');
+            files.forEach(function (file) {
+                if (file.slice(-5) === '.json') {
+                    l.push(file.slice(0, -5));
+                }
+            });
+            return l;
+        })();
+        for (i = 0; i < langs.length; i++) {
+            langs[i] = langs[i].split(';q=');
+            langs[i][1] = (typeof langs[i][1] === 'string') ? parseFloat(langs[i][1]) : 1.0;
+        }
+        langs.sort(function (a, b) {
+            return b[1] - a[1];
+        });
+
+        for (i = 0; i < langs.length; i++) {
+            if (langs[i][0] === '*') {
+                break;
+            } else if (_.contains(available, langs[i][0])) {
+                return this.file_server.serveFile('/assets/locales/' + langs[i][0] + '.json', 200, {Vary: 'Accept-Language', 'Content-Language': langs[i][0]}, request, response);
+            }
+        }
+    }
+
+    response.writeHead(200, {
+        'Vary': 'Accept-Language',
+        'Content-Type': 'application/json',
+        'Content-Language': 'en-gb'
+    });
+    response.end('{"en-gb": {}}');
+};