From 0fa2ca42928106d41261f34172fb4404c231b787 Mon Sep 17 00:00:00 2001 From: Jack Allnutt Date: Fri, 5 Jul 2013 04:04:21 +0100 Subject: [PATCH] Load translations from the server & add language to settings applet --- .gitignore | 4 +- client/assets/locales/template.po | 358 ++++++++++++++++++++++++ client/assets/locales/translations.json | 3 + client/assets/src/app.js | 40 ++- client/assets/src/applets/settings.js | 9 +- client/assets/src/build.js | 24 +- client/assets/src/index.html.tmpl | 15 +- client/assets/src/models/application.js | 1 + package.json | 3 +- server/httphandler.js | 57 +++- 10 files changed, 494 insertions(+), 20 deletions(-) create mode 100755 client/assets/locales/template.po create mode 100755 client/assets/locales/translations.json diff --git a/.gitignore b/.gitignore index 7465110..0d105f3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ node/node_modules/ node_modules/ doc/ client/assets/kiwi.js -client/assets/kiwi.min.js +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 index 0000000..4d3ef5a --- /dev/null +++ b/client/assets/locales/template.po @@ -0,0 +1,358 @@ + +#: client/assets/src/applets/chanlist.js +msgid "Channel Name" +#msgstr "" + +msgid "Users" +#msgstr "" + +msgid "Topic" +#msgstr "" + +msgid "Channel List" +#msgstr "" + + + +#: client/assets/src/applets/scripteditor.js +msgid "Save" +#msgstr "" + +msgid "Script error. %s" +#msgstr "" + +msgid "Your script has been saved and is now active" +#msgstr "" + +msgid "Script Editor" +#msgstr + + + +#: client/assets/src/applets/settings.js +msgid "Tabs" +#msgstr "" + +msgid "List" +#msgstr "" + +msgid "for large amouts of channels" +#msgstr "" + +msgid "Join/part channel notifications" +#msgstr "" + +msgid "Timestamps" +#msgstr "" + +msgid "Mute sound notifications" +#msgstr "" + +msgid "messages in scroll history" +#msgstr "" + +msgid "Default IRC client" +#msgstr "" + +msgid "Make Kiwi my default IRC client" +#msgstr "" + +msgid "Note: Chrome or Chromium browser users may need to check their settings via %s if nothing happens" +#msgstr "" + +msgid "Settings" +#msgstr "" + + + +#: client/assets/src/models/applet.js +msgid "Unknown Applet" +#msgstr "" + +msgid "Loading.." +#msgstr "" + +msgid "Not found" +#msgstr "" + + + +#: client/assets/src/models/application.js +msgid "You have been disconnected. Attempting to reconnect for you" +#msgstr "" + +msgid "You have been disconnected. Attempting to reconnect again in %d seconds" +#msgstr "" + +msgid "It's OK, you're connected again" +#msgstr "" + +msgid "Settings have been saved" +#msgstr "" + +msgid "Ignored nicks" +#msgstr "" + +msgid "Not ignoring anybody" +#msgstr "" + +msgid "Ignoring %s" +#msgstr "" + +msgid "Specifiy which nick you wish to stop ignoring" +#msgstr "" + +msgid "Stopped ignoring %s" +#msgstr "" + +msgid "Applet "%s" does not exist" +#msgstr "" + +msgid "Encoding modified to %s" +#msgstr "" + +msgid "%s is not a valid encoding" +#msgstr "" + +msgid "Encoding not specified" +#msgstr "" + +msgid "Usage: /encoding [NEW-ENCODING]" +#msgstr "" + +msgid "New Connection" +#msgstr "" + +msgid "Connecting to %s:%s..." +#msgstr "" + +msgid "Error connecting to %s:%s (%s)" +#msgstr "" + + + +#: client/assets/src/models/channel.js +msgid "%s has joined" +#msgstr "" + +msgid "%s has quit %s" +#msgstr "" + +msgid "%s was kicked by %s %s" +#msgstr "" + +msgid "You have been kicked by %s %s" +#msgstr "" + +msgid "%s has left %s" +#msgstr "" + + + +#: client/assets/src/models/network.js +msgid "%s is not a valid channel name" +#msgstr "" + +msgid "Disconnected from the IRC network" +#msgstr "" + +msgid "%s is now known as %s" +#msgstr "" + +msgid "Topic set by %s at %s" +#msgstr "" + +msgid "%s sets mode %s" +#msgstr "" + +msgid "%s set mode %s" +#msgstr "" + +msgid "Channels: %s" +#msgstr "" + +msgid "Connected to server: %s %s" +#msgstr "" + +msgid "Idle for %s, signed on %s" +#msgstr "" + +msgid "Away: %s" +#msgstr "" + +msgid "Idle for %s" +#msgstr "" + +msgid "No such nick" +#msgstr "" + +msgid "You are banned from %s. %s" +#msgstr "" + +msgid "Bad channel key for %s" +#msgstr "" + +msgid "%s is invite only." +#msgstr "" + +msgid "%s is full." +#msgstr "" + +msgid "The nickname "%s" is already in use. Please select a new nickname" +#msgstr "" + +msgid "Incorrect password given" +#msgstr "" + + + +#: client/assets/src/views/application.js +msgid "This will close all KiwiIRC conversations. Are you sure you want to close this window?" +#msgstr "" + + + +#: client/assets/src/views/channel.js +msgid "Joining channel.." +#msgstr "" + +msgid "Topic for %s is: %s" +#msgstr "" + + + +#: client/assets/src/views/mediamessage.js +msgid "Close media" +#msgstr "" + +msgid "Not found" +#msgstr "" + +msgid "Loading tweet" +#msgstr "" + +msgid "Loading image" +#msgstr "" + +msgid "Loading Reddit thread" +#msgstr "" + +msgid "Loading gist" +#msgstr "" + + + +#: client/assets/src/views/nickchangegbox.js +msgid "New nick" +#msgstr "" + +msgid "Change" +#msgstr "" + +msgid "Cancel" +#msgstr "" + + + +#: client/assets/src/views/panel.js +msgid "People are talking!" +#msgstr "" + + + +#: client/assets/src/views/serverselect.js +msgid "Think of a nickname..." +#msgstr "" + +msgid "Nickname" +#msgstr "" + +msgid "I have a password" +#msgstr "" + +msgid "Password" +#msgstr "" + +msgid "Channel" +#msgstr "" + +msgid "Channel Key" +#msgstr "" + +msgid "Channel requires a key" +#msgstr "" + +msgid "Key" +#msgstr "" + +msgid "Start..." +#msgstr "" + +msgid "Server and network" +#msgstr "" + +msgid "Server" +#msgstr "" + +msgid "Port" +#msgstr "" + +msgid "Powered by Kiwi IRC" +#msgstr "" + +msgid "Select a nickname first!" +#msgstr "" + +msgid "Connected" +#msgstr "" + +msgid "Connecting.." +#msgstr "" + +msgid "Nickname already taken" +#msgstr "" + +msgid "Erroneus nickname" +#msgstr "" + +msgid "Incorrect Password" +#msgstr "" + +msgid "Error Connecting" +#msgstr "" + +msgid "Server not found" +#msgstr "" + +msgid "Connection refused" +#msgstr "" + + + +#: client/assets/src/views/userbox.js +msgid "Op" +#msgstr "" + +msgid "De-op" +#msgstr "" + +msgid "Voice" +#msgstr "" + +msgid "De-voice" +#msgstr "" + +msgid "Kick" +#msgstr "" + +msgid "Ban" +#msgstr "" + +msgid "Message" +#msgstr "" + +msgid "Info" +#msgstr "" + +msgid "Slap!" +#msgstr "" diff --git a/client/assets/locales/translations.json b/client/assets/locales/translations.json new file mode 100755 index 0000000..1c96b79 --- /dev/null +++ b/client/assets/locales/translations.json @@ -0,0 +1,3 @@ +{ + "en-gb": "English (British)" +} \ No newline at end of file diff --git a/client/assets/src/app.js b/client/assets/src/app.js index 16f2b7c..1dc1a7f 100644 --- a/client/assets/src/app.js +++ b/client/assets/src/app.js @@ -104,6 +104,7 @@ _kiwi.global = { // Entry point to start the kiwi application start: function (opts) { + var continueStart, locale; opts = opts || {}; // Load the plugin manager @@ -113,18 +114,33 @@ _kiwi.global = { _kiwi.global.settings = _kiwi.model.DataStore.instance('kiwi.settings'); _kiwi.global.settings.load(); - _kiwi.global.i18n = new Jed(); - - _kiwi.app = new _kiwi.model.Application(opts); - - if (opts.kiwi_server) { - _kiwi.app.kiwi_server = opts.kiwi_server; - } - - // Start the client up - _kiwi.app.start(); - - return true; + continueStart = function (locale, s, xhr) { + if (locale) { + _kiwi.global.i18n = new Jed({locale_data: locale, domain: xhr.getResponseHeader('Content-Language')}); + } else { + _kiwi.global.i18n = new Jed(); + } + + _kiwi.app = new _kiwi.model.Application(opts); + + if (opts.kiwi_server) { + _kiwi.app.kiwi_server = opts.kiwi_server; + } + + // Start the client up + _kiwi.app.start(); + }; + + locale = _kiwi.global.settings.get('locale') + if (!locale) { + $.getJSON(opts.base_path + '/assets/locales/magic.json', continueStart); + } else { + if (locale === 'en-gb') { + continueStart(); + } else { + $.getJSON(opts.base_path + '/assets/locales/' + locale + '.json', continueStart); + } + } } }; diff --git a/client/assets/src/applets/settings.js b/client/assets/src/applets/settings.js index 50b2475..3327be7 100644 --- a/client/assets/src/applets/settings.js +++ b/client/assets/src/applets/settings.js @@ -15,6 +15,7 @@ timestamps: _kiwi.global.i18n.translate('Timestamps').fetch(), mute: _kiwi.global.i18n.translate('Mute sound notifications').fetch(), scroll_history: _kiwi.global.i18n.translate('messages in scroll history').fetch(), + languages: _kiwi.app.translations, default_client: _kiwi.global.i18n.translate('Default IRC client').fetch(), make_default: _kiwi.global.i18n.translate('Make Kiwi my default IRC client').fetch(), default_note: _kiwi.global.i18n.translate('Note: Chrome or Chromium browser users may need to check their settings via %s if nothing happens').fetch('chrome://settings/handlers') @@ -55,6 +56,9 @@ case 'text': $el.val(value); break; + case 'select-one': + $('[value="' + value + '"]', that.$el).prop('selected', true); + break; default: $('[data-setting="' + key + '"][data-value="' + value + '"]', that.$el).addClass('active'); break; @@ -65,7 +69,7 @@ saveSettings: function (event) { var value, settings = _kiwi.global.settings, - $setting = $(event.currentTarget, this.$el) + $setting = $(event.currentTarget, this.$el); switch (event.currentTarget.type) { case 'checkbox': @@ -75,6 +79,9 @@ case 'text': value = $setting.val(); break; + case 'select-one': + value = $(event.currentTarget[$setting.prop('selectedIndex')]).val(); + break; default: value = $setting.data('value'); break; diff --git a/client/assets/src/build.js b/client/assets/src/build.js index 28886d5..1e486ed 100644 --- a/client/assets/src/build.js +++ b/client/assets/src/build.js @@ -1,6 +1,7 @@ var fs = require('fs'), uglifyJS = require('uglify-js'), _ = require('lodash'), + po2json = require('po2json'), config = require('./../../../server/configuration.js'); var FILE_ENCODING = 'utf-8', @@ -100,6 +101,24 @@ console.log('kiwi.js and kiwi.min.js built'); +/** +* Convert translations from .po to .json +*/ +var translations = []; +var translation_files = fs.readdirSync(__dirname + '/../locales'); +translation_files.forEach(function (file) { + var locale = file.slice(0, -3), + json = '', + languages = JSON.parse(fs.readFileSync(__dirname + '/../locales/translations.json')); + if ((file.slice(-3) === '.po') && (locale !== 'template')) { + json = po2json.parseSync(__dirname + '/../locales/' + file); + fs.writeFileSync(__dirname + '/../locales/' + locale + '.json', JSON.stringify(json)); + translations.push({tag: locale, language: languages[locale]}); + console.log('Built translation file %s', locale + '.json'); + } +}); + + @@ -140,6 +159,9 @@ if (config.get().client_plugins && config.get().client_plugins.length > 0) { vars.client_plugins = config.get().client_plugins; } +// Translations +vars.translations = translations; + _.each(vars, function(value, key) { if (typeof value === 'object') value = JSON.stringify(value); index_src = index_src.replace(new RegExp('<%' + key + '%>', 'g'), value); @@ -148,4 +170,4 @@ _.each(vars, function(value, key) { fs.writeFileSync(__dirname + '/../../index.html', index_src, FILE_ENCODING); -console.log('index.html built'); \ No newline at end of file +console.log('index.html built'); diff --git a/client/assets/src/index.html.tmpl b/client/assets/src/index.html.tmpl index c94f221..7324b0f 100644 --- a/client/assets/src/index.html.tmpl +++ b/client/assets/src/index.html.tmpl @@ -245,6 +245,18 @@ +
+
Language
+
+ +
+
+
<%= default_client %>
@@ -395,7 +407,8 @@ //kiwi_server: 'http://kiwiirc.com:80', server_settings: <%server_settings%>, - client_plugins: <%client_plugins%> + client_plugins: <%client_plugins%>, + translations: <%translations%> }; // Start the app diff --git a/client/assets/src/models/application.js b/client/assets/src/models/application.js index 1ce101e..3fd84f0 100644 --- a/client/assets/src/models/application.js +++ b/client/assets/src/models/application.js @@ -25,6 +25,7 @@ _kiwi.model.Application = function () { // Any options sent down from the server this.server_settings = options[0].server_settings || {}; + this.translations = options[0].translations || {}; // Best guess at where the kiwi server is this.detectKiwiServer(); diff --git a/package.json b/package.json index b20a822..e76397d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "eventemitter2": "0.4.11", "ipaddr.js": "0.1.1", "socksjs": "0.3.3", - "iconv-lite" : "0.2.10" + "iconv-lite" : "0.2.10", + "po2json": "0.0.6" } } diff --git a/server/httphandler.js b/server/httphandler.js index 816cb37..b975892 100644 --- a/server/httphandler.js +++ b/server/httphandler.js @@ -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": {}}'); +}; -- 2.25.1