From 0d29c21fb17ca2a828fd0360ae1333a4fa438736 Mon Sep 17 00:00:00 2001 From: Jack Allnutt Date: Thu, 4 Jul 2013 04:28:20 +0100 Subject: [PATCH] Use jed to handle translatable strings --- client/assets/libs/jed.js | 1005 +++++++++++++++++++++ client/assets/src/app.js | 2 + client/assets/src/applets/chanlist.js | 9 +- client/assets/src/applets/scripteditor.js | 15 +- client/assets/src/applets/settings.js | 16 +- client/assets/src/index.html.tmpl | 76 +- client/assets/src/models/applet.js | 6 +- client/assets/src/models/application.js | 34 +- client/assets/src/models/channel.js | 10 +- client/assets/src/models/network.js | 48 +- client/assets/src/views/application.js | 2 +- client/assets/src/views/channel.js | 4 +- client/assets/src/views/mediamessage.js | 12 +- client/assets/src/views/nickchangebox.js | 11 +- client/assets/src/views/panel.js | 6 +- client/assets/src/views/serverselect.js | 39 +- client/assets/src/views/userbox.js | 13 +- 17 files changed, 1183 insertions(+), 125 deletions(-) create mode 100755 client/assets/libs/jed.js diff --git a/client/assets/libs/jed.js b/client/assets/libs/jed.js new file mode 100755 index 0000000..44242bb --- /dev/null +++ b/client/assets/libs/jed.js @@ -0,0 +1,1005 @@ +/* +jed.js +v0.5.0beta + +https://github.com/SlexAxton/Jed +----------- +A gettext compatible i18n library for modern JavaScript Applications + +by Alex Sexton - AlexSexton [at] gmail - @SlexAxton +WTFPL license for use +Dojo CLA for contributions + +Jed offers the entire applicable GNU gettext spec'd set of +functions, but also offers some nicer wrappers around them. +The api for gettext was written for a language with no function +overloading, so Jed allows a little more of that. + +Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote +gettext.js back in 2008. I was able to vet a lot of my ideas +against his. I also made sure Jed passed against his tests +in order to offer easy upgrades -- jsgettext.berlios.de +*/ +(function (root, undef) { + + // Set up some underscore-style functions, if you already have + // underscore, feel free to delete this section, and use it + // directly, however, the amount of functions used doesn't + // warrant having underscore as a full dependency. + // Underscore 1.3.0 was used to port and is licensed + // under the MIT License by Jeremy Ashkenas. + var ArrayProto = Array.prototype, + ObjProto = Object.prototype, + slice = ArrayProto.slice, + hasOwnProp = ObjProto.hasOwnProperty, + nativeForEach = ArrayProto.forEach, + breaker = {}; + + // We're not using the OOP style _ so we don't need the + // extra level of indirection. This still means that you + // sub out for real `_` though. + var _ = { + forEach : function( obj, iterator, context ) { + var i, l, key; + if ( obj === null ) { + return; + } + + if ( nativeForEach && obj.forEach === nativeForEach ) { + obj.forEach( iterator, context ); + } + else if ( obj.length === +obj.length ) { + for ( i = 0, l = obj.length; i < l; i++ ) { + if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) { + return; + } + } + } + else { + for ( key in obj) { + if ( hasOwnProp.call( obj, key ) ) { + if ( iterator.call (context, obj[key], key, obj ) === breaker ) { + return; + } + } + } + } + }, + extend : function( obj ) { + this.forEach( slice.call( arguments, 1 ), function ( source ) { + for ( var prop in source ) { + obj[prop] = source[prop]; + } + }); + return obj; + } + }; + // END Miniature underscore impl + + // Jed is a constructor function + var Jed = function ( options ) { + // Some minimal defaults + this.defaults = { + "locale_data" : { + "messages" : { + "" : { + "domain" : "messages", + "lang" : "en", + "plural_forms" : "nplurals=2; plural=(n != 1);" + } + // There are no default keys, though + } + }, + // The default domain if one is missing + "domain" : "messages" + }; + + // Mix in the sent options with the default options + this.options = _.extend( {}, this.defaults, options ); + this.textdomain( this.options.domain ); + + if ( this.options.domain && ! this.options.locale_data[ this.options.domain ] ) { + throw new Error('Text domain set to non-existent domain: `' + this.options.domain + '`'); + } + }; + + // The gettext spec sets this character as the default + // delimiter for context lookups. + // e.g.: context\u0004key + // If your translation company uses something different, + // just change this at any time and it will use that instead. + Jed.context_delimiter = String.fromCharCode( 4 ); + + function getPluralFormFunc ( plural_form_string ) { + return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);"); + } + + function Chain( key, i18n ){ + this._key = key; + this._i18n = i18n; + } + + // Create a chainable api for adding args prettily + _.extend( Chain.prototype, { + onDomain : function ( domain ) { + this._domain = domain; + return this; + }, + withContext : function ( context ) { + this._context = context; + return this; + }, + ifPlural : function ( num, pkey ) { + this._val = num; + this._pkey = pkey; + return this; + }, + fetch : function ( sArr ) { + if ( {}.toString.call( sArr ) != '[object Array]' ) { + sArr = [].slice.call(arguments); + } + return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )( + this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val), + sArr + ); + } + }); + + // Add functions to the Jed prototype. + // These will be the functions on the object that's returned + // from creating a `new Jed()` + // These seem redundant, but they gzip pretty well. + _.extend( Jed.prototype, { + // The sexier api start point + translate : function ( key ) { + return new Chain( key, this ); + }, + + textdomain : function ( domain ) { + if ( ! domain ) { + return this._textdomain; + } + this._textdomain = domain; + }, + + gettext : function ( key ) { + return this.dcnpgettext.call( this, undef, undef, key ); + }, + + dgettext : function ( domain, key ) { + return this.dcnpgettext.call( this, domain, undef, key ); + }, + + dcgettext : function ( domain , key /*, category */ ) { + // Ignores the category anyways + return this.dcnpgettext.call( this, domain, undef, key ); + }, + + ngettext : function ( skey, pkey, val ) { + return this.dcnpgettext.call( this, undef, undef, skey, pkey, val ); + }, + + dngettext : function ( domain, skey, pkey, val ) { + return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); + }, + + dcngettext : function ( domain, skey, pkey, val/*, category */) { + return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); + }, + + pgettext : function ( context, key ) { + return this.dcnpgettext.call( this, undef, context, key ); + }, + + dpgettext : function ( domain, context, key ) { + return this.dcnpgettext.call( this, domain, context, key ); + }, + + dcpgettext : function ( domain, context, key/*, category */) { + return this.dcnpgettext.call( this, domain, context, key ); + }, + + npgettext : function ( context, skey, pkey, val ) { + return this.dcnpgettext.call( this, undef, context, skey, pkey, val ); + }, + + dnpgettext : function ( domain, context, skey, pkey, val ) { + return this.dcnpgettext.call( this, domain, context, skey, pkey, val ); + }, + + // The most fully qualified gettext function. It has every option. + // Since it has every option, we can use it from every other method. + // This is the bread and butter. + // Technically there should be one more argument in this function for 'Category', + // but since we never use it, we might as well not waste the bytes to define it. + dcnpgettext : function ( domain, context, singular_key, plural_key, val ) { + // Set some defaults + + plural_key = plural_key || singular_key; + + // Use the global domain default if one + // isn't explicitly passed in + domain = domain || this._textdomain; + + // Default the value to the singular case + val = typeof val == 'undefined' ? 1 : val; + + var fallback; + + // Handle special cases + + // No options found + if ( ! this.options ) { + // There's likely something wrong, but we'll return the correct key for english + // We do this by instantiating a brand new Jed instance with the default set + // for everything that could be broken. + fallback = new Jed(); + return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val ); + } + + // No translation data provided + if ( ! this.options.locale_data ) { + throw new Error('No locale data provided.'); + } + + if ( ! this.options.locale_data[ domain ] ) { + throw new Error('Domain `' + domain + '` was not found.'); + } + + if ( ! this.options.locale_data[ domain ][ "" ] ) { + throw new Error('No locale meta information provided.'); + } + + // Make sure we have a truthy key. Otherwise we might start looking + // into the empty string key, which is the options for the locale + // data. + if ( ! singular_key ) { + throw new Error('No translation key found.'); + } + + // Handle invalid numbers, but try casting strings for good measure + if ( typeof val != 'number' ) { + val = parseInt( val, 10 ); + + if ( isNaN( val ) ) { + throw new Error('The number that was passed in is not a number.'); + } + } + + var key = context ? context + Jed.context_delimiter + singular_key : singular_key, + locale_data = this.options.locale_data, + dict = locale_data[ domain ], + pluralForms = dict[""].plural_forms || (locale_data.messages || this.defaults.locale_data.messages)[""].plural_forms, + val_idx = getPluralFormFunc(pluralForms)(val) + 1, + val_list, + res; + + // Throw an error if a domain isn't found + if ( ! dict ) { + throw new Error('No domain named `' + domain + '` could be found.'); + } + + val_list = dict[ key ]; + + // If there is no match, then revert back to + // english style singular/plural with the keys passed in. + if ( ! val_list || val_idx >= val_list.length ) { + res = [ null, singular_key, plural_key ]; + return res[ getPluralFormFunc(pluralForms)( val ) + 1 ]; + } + + res = val_list[ val_idx ]; + + // This includes empty strings on purpose + if ( ! res ) { + res = [ null, singular_key, plural_key ]; + return res[ getPluralFormFunc(pluralForms)( val ) + 1 ]; + } + return res; + } + }); + + + // We add in sprintf capabilities for post translation value interolation + // This is not internally used, so you can remove it if you have this + // available somewhere else, or want to use a different system. + + // We _slightly_ modify the normal sprintf behavior to more gracefully handle + // undefined values. + + /** + sprintf() for JavaScript 0.7-beta1 + http://www.diveintojavascript.com/projects/javascript-sprintf + + Copyright (c) Alexandru Marasteanu + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of sprintf() for JavaScript nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + var sprintf = (function() { + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + } + function str_repeat(input, multiplier) { + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } + + var str_format = function() { + if (!str_format.cache.hasOwnProperty(arguments[0])) { + str_format.cache[arguments[0]] = str_format.parse(arguments[0]); + } + return str_format.format.call(null, str_format.cache[arguments[0]], arguments); + }; + + str_format.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); + } + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } + + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); + } + + // Jed EDIT + if ( typeof arg == 'undefined' || arg === null ) { + arg = ''; + } + // Jed EDIT + + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = Math.abs(arg); break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; + + str_format.cache = {}; + + str_format.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else { + throw('[sprintf] huh?'); + } + } + } + else { + throw('[sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); + } + else { + throw('[sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; + + return str_format; + })(); + + var vsprintf = function(fmt, argv) { + argv.unshift(fmt); + return sprintf.apply(null, argv); + }; + + Jed.parse_plural = function ( plural_forms, n ) { + plural_forms = plural_forms.replace(/n/g, n); + return Jed.parse_expression(plural_forms); + }; + + Jed.sprintf = function ( fmt, args ) { + if ( {}.toString.call( args ) == '[object Array]' ) { + return vsprintf( fmt, [].slice.call(args) ); + } + return sprintf.apply(this, [].slice.call(arguments) ); + }; + + Jed.prototype.sprintf = function () { + return Jed.sprintf.apply(this, arguments); + }; + // END sprintf Implementation + + // Start the Plural forms section + // This is a full plural form expression parser. It is used to avoid + // running 'eval' or 'new Function' directly against the plural + // forms. + // + // This can be important if you get translations done through a 3rd + // party vendor. I encourage you to use this instead, however, I + // also will provide a 'precompiler' that you can use at build time + // to output valid/safe function representations of the plural form + // expressions. This means you can build this code out for the most + // part. + Jed.PF = {}; + + Jed.PF.parse = function ( p ) { + var plural_str = Jed.PF.extractPluralExpr( p ); + return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str); + }; + + Jed.PF.compile = function ( p ) { + // Handle trues and falses as 0 and 1 + function imply( val ) { + return (val === true ? 1 : val ? val : 0); + } + + var ast = Jed.PF.parse( p ); + return function ( n ) { + return imply( Jed.PF.interpreter( ast )( n ) ); + }; + }; + + Jed.PF.interpreter = function ( ast ) { + return function ( n ) { + var res; + switch ( ast.type ) { + case 'GROUP': + return Jed.PF.interpreter( ast.expr )( n ); + case 'TERNARY': + if ( Jed.PF.interpreter( ast.expr )( n ) ) { + return Jed.PF.interpreter( ast.truthy )( n ); + } + return Jed.PF.interpreter( ast.falsey )( n ); + case 'OR': + return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n ); + case 'AND': + return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n ); + case 'LT': + return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n ); + case 'GT': + return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n ); + case 'LTE': + return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n ); + case 'GTE': + return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n ); + case 'EQ': + return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n ); + case 'NEQ': + return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n ); + case 'MOD': + return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n ); + case 'VAR': + return n; + case 'NUM': + return ast.val; + default: + throw new Error("Invalid Token found."); + } + }; + }; + + Jed.PF.extractPluralExpr = function ( p ) { + // trim first + p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + + if (! /;\s*$/.test(p)) { + p = p.concat(';'); + } + + var nplurals_re = /nplurals\=(\d+);/, + plural_re = /plural\=(.*);/, + nplurals_matches = p.match( nplurals_re ), + res = {}, + plural_matches; + + // Find the nplurals number + if ( nplurals_matches.length > 1 ) { + res.nplurals = nplurals_matches[1]; + } + else { + throw new Error('nplurals not found in plural_forms string: ' + p ); + } + + // remove that data to get to the formula + p = p.replace( nplurals_re, "" ); + plural_matches = p.match( plural_re ); + + if (!( plural_matches && plural_matches.length > 1 ) ) { + throw new Error('`plural` expression not found: ' + p); + } + return plural_matches[ 1 ]; + }; + + /* Jison generated parser */ + Jed.PF.parser = (function(){ + +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"}, +productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]], +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + +var $0 = $$.length - 1; +switch (yystate) { +case 1: return { type : 'GROUP', expr: $$[$0-1] }; +break; +case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] }; +break; +case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] }; +break; +case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] }; +break; +case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] }; +break; +case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] }; +break; +case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] }; +break; +case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] }; +break; +case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] }; +break; +case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] }; +break; +case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] }; +break; +case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] }; +break; +case 13:this.$ = { type: 'VAR' }; +break; +case 14:this.$ = { type: 'NUM', val: Number(yytext) }; +break; +} +}, +table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}], +defaultActions: {6:[2,1]}, +parseError: function parseError(str, hash) { + throw new Error(str); +}, +parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], // semantic value stack + lstack = [], // location stack + table = this.table, + yytext = '', + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + + //this.reductionCount = this.shiftCount = 0; + + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + if (typeof this.lexer.yylloc == 'undefined') + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + + if (typeof this.yy.parseError === 'function') + this.parseError = this.yy.parseError; + + function popStack (n) { + stack.length = stack.length - 2*n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + + function lex() { + var token; + token = self.lexer.lex() || 1; // $end = 1 + // if token isn't its numeric value, convert + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + } + + var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; + while (true) { + // retreive state number from top of stack + state = stack[stack.length-1]; + + // use default actions if available + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol == null) + symbol = lex(); + // read action for current state and first input + action = table[state] && table[state][symbol]; + } + + // handle parse error + _handle_error: + if (typeof action === 'undefined' || !action.length || !action[0]) { + + if (!recovering) { + // Report error + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'"+this.terminals_[p]+"'"); + } + var errStr = ''; + if (this.lexer.showPosition) { + errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; + } else { + errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + + (symbol == 1 /*EOF*/ ? "end of input" : + ("'"+(this.terminals_[symbol] || symbol)+"'")); + } + this.parseError(errStr, + {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + + // just recovered from another error + if (recovering == 3) { + if (symbol == EOF) { + throw new Error(errStr || 'Parsing halted.'); + } + + // discard current lookahead and grab another + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + symbol = lex(); + } + + // try to recover from error + while (1) { + // check for error recovery rule in this state + if ((TERROR.toString()) in table[state]) { + break; + } + if (state == 0) { + throw new Error(errStr || 'Parsing halted.'); + } + popStack(1); + state = stack[stack.length-1]; + } + + preErrorSymbol = symbol; // save the lookahead token + symbol = TERROR; // insert generic error symbol as new lookahead + state = stack[stack.length-1]; + action = table[state] && table[state][TERROR]; + recovering = 3; // allow 3 real symbols to be shifted before reporting a new error + } + + // this shouldn't happen, unless resolve defaults are off + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); + } + + switch (action[0]) { + + case 1: // shift + //this.shiftCount++; + + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); // push state + symbol = null; + if (!preErrorSymbol) { // normal execution/no error + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { // error just occurred, resume old lookahead f/ before error + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + + case 2: // reduce + //this.reductionCount++; + + len = this.productions_[action[1]][1]; + + // perform semantic action + yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 + // default location, uses first token for firsts, last for lasts + yyval._$ = { + first_line: lstack[lstack.length-(len||1)].first_line, + last_line: lstack[lstack.length-1].last_line, + first_column: lstack[lstack.length-(len||1)].first_column, + last_column: lstack[lstack.length-1].last_column + }; + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + + if (typeof r !== 'undefined') { + return r; + } + + // pop off stack + if (len) { + stack = stack.slice(0,-1*len*2); + vstack = vstack.slice(0, -1*len); + lstack = lstack.slice(0, -1*len); + } + + stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) + vstack.push(yyval.$); + lstack.push(yyval._$); + // goto new state = table[STATE][NONTERMINAL] + newState = table[stack[stack.length-2]][stack[stack.length-1]]; + stack.push(newState); + break; + + case 3: // accept + return true; + } + + } + + return true; +}};/* Jison generated lexer */ +var lexer = (function(){ + +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parseError) { + this.yy.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext+=ch; + this.yyleng++; + this.match+=ch; + this.matched+=ch; + var lines = ch.match(/\n/); + if (lines) this.yylineno++; + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + this._input = ch + this._input; + return this; + }, +more:function () { + this._more = true; + return this; + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + match = this._input.match(this.rules[rules[i]]); + if (match) { + lines = match[0].match(/\n.*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); + if (token) return token; + else return; + } + } + if (this._input === "") { + return this.EOF; + } else { + this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return 20 +break; +case 2:return 19 +break; +case 3:return 8 +break; +case 4:return 9 +break; +case 5:return 6 +break; +case 6:return 7 +break; +case 7:return 11 +break; +case 8:return 13 +break; +case 9:return 10 +break; +case 10:return 12 +break; +case 11:return 14 +break; +case 12:return 15 +break; +case 13:return 16 +break; +case 14:return 17 +break; +case 15:return 18 +break; +case 16:return 5 +break; +case 17:return 'INVALID' +break; +} +}; +lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./]; +lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})() +parser.lexer = lexer; +return parser; +})(); +// End parser + + // Handle node, amd, and global systems + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = Jed; + } + exports.Jed = Jed; + } + else { + if (typeof define === 'function' && define.amd) { + define('jed', function() { + return Jed; + }); + } + // Leak a global regardless of module system + root['Jed'] = Jed; + } + +})(this); diff --git a/client/assets/src/app.js b/client/assets/src/app.js index 7eeeae0..16f2b7c 100644 --- a/client/assets/src/app.js +++ b/client/assets/src/app.js @@ -113,6 +113,8 @@ _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) { diff --git a/client/assets/src/applets/chanlist.js b/client/assets/src/applets/chanlist.js index e64727f..efbf019 100644 --- a/client/assets/src/applets/chanlist.js +++ b/client/assets/src/applets/chanlist.js @@ -7,7 +7,12 @@ initialize: function (options) { - this.$el = $($('#tmpl_channel_list').html().trim()); + var text = { + channel_name: _kiwi.global.i18n.translate('Channel Name').fetch(), + users: _kiwi.global.i18n.translate('Users').fetch(), + topic: _kiwi.global.i18n.translate('Topic').fetch() + }; + this.$el = $(_.template($('#tmpl_channel_list').html().trim(), text)); this.channels = []; @@ -50,7 +55,7 @@ var Applet = Backbone.Model.extend({ initialize: function () { - this.set('title', 'Channel List'); + this.set('title', _kiwi.global.i18n.translate('Channel List').fetch()); this.view = new View(); this.network = _kiwi.global.components.Network(); diff --git a/client/assets/src/applets/scripteditor.js b/client/assets/src/applets/scripteditor.js index ef804f2..a196594 100644 --- a/client/assets/src/applets/scripteditor.js +++ b/client/assets/src/applets/scripteditor.js @@ -5,8 +5,11 @@ }, initialize: function (options) { - var that = this; - this.$el = $($('#tmpl_script_editor').html().trim()); + var that = this, + text = { + save: _kiwi.global.i18n.translate('Save').fetch() + }; + this.$el = $(_.template($('#tmpl_script_editor').html().trim(), text)); this.model.on('applet_loaded', function () { that.$el.parent().css('height', '100%'); @@ -53,7 +56,7 @@ _kiwi.user_script = new user_fn(); } catch (err) { - this.setStatus('Script error. ' + err.toString()); + this.setStatus(_kiwi.global.i18n.translate('Script error. %s').fetch(err.toString())); return; } @@ -61,7 +64,7 @@ _kiwi.global.settings.set('user_script', this.editor.getValue()); _kiwi.global.settings.save(); - this.setStatus('Your script has been saved and is now active :)'); + this.setStatus(_kiwi.global.i18n.translate('Your script has been saved and is now active').fetch() + ' :)'); }, @@ -82,8 +85,8 @@ initialize: function () { var that = this; - this.set('title', 'Script Editor'); - this.view = new view({model: this}) + this.set('title', _kiwi.global.i18n.translate('Script Editor').fetch()); + this.view = new view({model: this}); } }); diff --git a/client/assets/src/applets/settings.js b/client/assets/src/applets/settings.js index 0679527..50b2475 100644 --- a/client/assets/src/applets/settings.js +++ b/client/assets/src/applets/settings.js @@ -7,7 +7,19 @@ }, initialize: function (options) { - this.$el = $($('#tmpl_applet_settings').html().trim()); + var text = { + tabs: _kiwi.global.i18n.translate('Tabs').fetch(), + list: _kiwi.global.i18n.translate('List').fetch(), + large_amounts_of_chans: _kiwi.global.i18n.translate('for large amouts of channels').fetch(), + join_part: _kiwi.global.i18n.translate('Join/part channel notifications').fetch(), + 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(), + 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') + }; + this.$el = $(_.template($('#tmpl_applet_settings').html().trim(), text)); if (!navigator.registerProtocolHandler) { this.$el.find('.protocol_handler').remove(); @@ -92,7 +104,7 @@ var Applet = Backbone.Model.extend({ initialize: function () { - this.set('title', 'Settings'); + this.set('title', _kiwi.global.i18n.translate('Settings').fetch()); this.view = new View(); } }); diff --git a/client/assets/src/index.html.tmpl b/client/assets/src/index.html.tmpl index 63038a4..c94f221 100644 --- a/client/assets/src/index.html.tmpl +++ b/client/assets/src/index.html.tmpl @@ -58,22 +58,22 @@ @@ -95,66 +95,66 @@
-
Think of a nickname..
+
<%= think_nick %>
- + - + - + - + - +
- +
- +
- +
- Server and network + <%= server_network %>
- + - + @@ -167,7 +167,7 @@ @@ -205,13 +205,13 @@
@@ -223,34 +223,34 @@
-
Default IRC client
+
<%= default_client %>
- +
- Note: Chrome or Chromium browser users may need to check their settings via chrome://settings/handlers if nothing happens + <%= default_note %>
@@ -262,9 +262,9 @@
- - - + + + @@ -283,7 +283,7 @@ #kiwi .script_editor .se_toolbar button i { font-size:1.2em; margin-left:3px; }
-
+
@@ -313,7 +313,7 @@ // Common dependancies that are required at all times var scripts = [ ['libs/jquery-1.9.1.min.js', 'libs/lodash.min.js'], - 'libs/backbone.min.js' + 'libs/backbone.min.js', 'libs/jed.js' ]; // If in debug mode, load each development script diff --git a/client/assets/src/models/applet.js b/client/assets/src/models/applet.js index 0dca637..495e7d3 100644 --- a/client/assets/src/models/applet.js +++ b/client/assets/src/models/applet.js @@ -25,7 +25,7 @@ _kiwi.model.Applet = _kiwi.model.Panel.extend({ if (applet_object.get || applet_object.extend) { // Try find a title for the applet - this.set('title', applet_object.get('title') || 'Unknown Applet'); + this.set('title', applet_object.get('title') || _kiwi.global.i18n.translate('Unknown Applet').fetch()); // Update the tabs title if the applet changes it applet_object.bind('change:title', function (obj, new_value) { @@ -56,11 +56,11 @@ _kiwi.model.Applet = _kiwi.model.Panel.extend({ loadFromUrl: function(applet_url, applet_name) { var that = this; - this.view.$el.html('Loading..'); + this.view.$el.html(_kiwi.global.i18n.translate('Loading..').fetch()); $script(applet_url, function () { // Check if the applet loaded OK if (!_kiwi.applets[applet_name]) { - that.view.$el.html('Not found'); + that.view.$el.html(_kiwi.global.i18n.translate('Not found').fetch()); return; } diff --git a/client/assets/src/models/application.js b/client/assets/src/models/application.js index 023d921..1ce101e 100644 --- a/client/assets/src/models/application.js +++ b/client/assets/src/models/application.js @@ -341,7 +341,7 @@ _kiwi.model.Application = function () { var gw_stat = 0; gw.on('disconnect', function (event) { - var msg = 'You have been disconnected. Attempting to reconnect for you..'; + var msg = _kiwi.global.i18n.translate('You have been disconnected. Attempting to reconnect for you').fetch() + '...'; that.message.text(msg, {timeout: 10000}); that.view.$el.removeClass('connected'); @@ -363,7 +363,7 @@ _kiwi.model.Application = function () { gw.on('reconnecting', function (event) { - var msg = 'You have been disconnected. Attempting to reconnect again in ' + (event.delay/1000) + ' seconds..'; + var msg = _kiwi.global.i18n.translate('You have been disconnected. Attempting to reconnect again in %d seconds').fetch(event.delay/1000) + '...'; // Only need to mention the repeating re-connection messages on server panels _kiwi.app.connections.forEach(function(connection) { @@ -376,7 +376,7 @@ _kiwi.model.Application = function () { that.view.$el.addClass('connected'); if (gw_stat !== 1) return; - var msg = 'It\'s OK, you\'re connected again :)'; + var msg = _kiwi.global.i18n.translate('It\'s OK, you\'re connected again').fetch() + ':)'; that.message.text(msg, {timeout: 5000}); // Mention the disconnection on every channel @@ -510,7 +510,7 @@ _kiwi.model.Application = function () { controlbox.on('command:save', function (ev) { _kiwi.global.settings.save(); - _kiwi.app.panels().active.addMsg('', 'Settings have been saved'); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('Settings have been saved').fetch()); }); @@ -552,12 +552,12 @@ _kiwi.model.Application = function () { // No parameters passed so list them if (!ev.params[0]) { if (list.length > 0) { - _kiwi.app.panels().active.addMsg(' ', 'Ignored nicks:'); + _kiwi.app.panels().active.addMsg(' ', _kiwi.global.i18n.translate('Ignored nicks').fetch() + ':'); $.each(list, function (idx, ignored_pattern) { _kiwi.app.panels().active.addMsg(' ', ignored_pattern); }); } else { - _kiwi.app.panels().active.addMsg(' ', 'Not ignoring anybody'); + _kiwi.app.panels().active.addMsg(' ', _kiwi.global.i18n.translate('Not ignoring anybody').fetch()); } return; } @@ -565,7 +565,7 @@ _kiwi.model.Application = function () { // We have a parameter, so add it list.push(ev.params[0]); _kiwi.gateway.set('ignore_list', list); - _kiwi.app.panels().active.addMsg(' ', 'Ignoring ' + ev.params[0]); + _kiwi.app.panels().active.addMsg(' ', _kiwi.global.i18n.translate('Ignoring %s').fetch(ev.params[0])); }); @@ -573,7 +573,7 @@ _kiwi.model.Application = function () { var list = _kiwi.gateway.get('ignore_list'); if (!ev.params[0]) { - _kiwi.app.panels().active.addMsg(' ', 'Specifiy which nick you wish to stop ignoring'); + _kiwi.app.panels().active.addMsg(' ', _kiwi.global.i18n.translate('Specifiy which nick you wish to stop ignoring').fetch()); return; } @@ -583,7 +583,7 @@ _kiwi.model.Application = function () { _kiwi.gateway.set('ignore_list', list); - _kiwi.app.panels().active.addMsg(' ', 'Stopped ignoring ' + ev.params[0]); + _kiwi.app.panels().active.addMsg(' ', _kiwi.global.i18n.translate('Stopped ignoring %s').fetch(ev.params[0])); }); @@ -753,7 +753,7 @@ _kiwi.model.Application = function () { if (_kiwi.applets[ev.params[0]]) { panel.load(new _kiwi.applets[ev.params[0]]()); } else { - _kiwi.app.panels().server.addMsg('', 'Applet "' + ev.params[0] + '" does not exist'); + _kiwi.app.panels().server.addMsg('', _kiwi.global.i18n.translate('Applet "%s" does not exist').fetch(ev.params[0])); return; } } @@ -794,14 +794,14 @@ _kiwi.model.Application = function () { if (ev.params[0]) { _kiwi.gateway.setEncoding(null, ev.params[0], function (success) { if (success) { - _kiwi.app.panels().active.addMsg('', "Encoding modified to "+ev.params[0]); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('Encoding modified to %s').fetch(ev.params[0])); } else { - _kiwi.app.panels().active.addMsg('', ev.params[0]+' is not a valid encoding'); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('%s is not a valid encoding').fetch(ev.params[0])); } }); } else { - _kiwi.app.panels().active.addMsg('', 'Encoding not specified'); - _kiwi.app.panels().active.addMsg('', 'Usage: /encoding [NEW-ENCODING]'); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('Encoding not specified').fetch()); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('Usage: /encoding [NEW-ENCODING]').fetch()); } } @@ -811,7 +811,7 @@ _kiwi.model.Application = function () { // If no server address given, show the new connection dialog if (!ev.params[0]) { - tmp = new _kiwi.view.MenuBox('New Connection'); + tmp = new _kiwi.view.MenuBox(_kiwi.global.i18n.translate('New Connection').fetch()); tmp.addItem('new_connection', new _kiwi.model.NewConnection().view.$el); tmp.show(); @@ -854,7 +854,7 @@ _kiwi.model.Application = function () { // Use the same nick as we currently have nick = _kiwi.app.connections.active_connection.get('nick'); - _kiwi.app.panels().active.addMsg('', 'Connecting to ' + server + ':' + port.toString() + '..'); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('Connecting to %s:%s...').fetch(server, port.toString())); _kiwi.gateway.newConnection({ nick: nick, @@ -864,7 +864,7 @@ _kiwi.model.Application = function () { password: password }, function(err, new_connection) { if (err) - _kiwi.app.panels().active.addMsg('', 'Error connecting to ' + server + ':' + port.toString() + ' (' + err.toString() + ')'); + _kiwi.app.panels().active.addMsg('', _kiwi.global.i18n.translate('Error connecting to %s:%s (%s)').fetch(server, port.toString(), err.toString())); }); } diff --git a/client/assets/src/models/channel.js b/client/assets/src/models/channel.js index cbd921b..369f389 100644 --- a/client/assets/src/models/channel.js +++ b/client/assets/src/models/channel.js @@ -22,7 +22,7 @@ _kiwi.model.Channel = _kiwi.model.Panel.extend({ return; } - this.addMsg(' ', '== ' + member.displayNick(true) + ' has joined', 'action join'); + this.addMsg(' ', '== ' + _kiwi.global.i18n.translate('%s has joined').fetch(member.displayNick(true)), 'action join'); }, this); members.bind("remove", function (member, members, options) { @@ -30,21 +30,21 @@ _kiwi.model.Channel = _kiwi.model.Panel.extend({ var msg = (options.message) ? '(' + options.message + ')' : ''; if (options.type === 'quit' && show_message) { - this.addMsg(' ', '== ' + member.displayNick(true) + ' has quit ' + msg, 'action quit'); + this.addMsg(' ', '== ' + _kiwi.global.i18n.translate('%s has quit %s').fetch(member.displayNick(true), msg), 'action quit'); } else if(options.type === 'kick') { if (!options.current_user_kicked) { //If user kicked someone, show the message regardless of settings. if (show_message || options.current_user_initiated) { - this.addMsg(' ', '== ' + member.displayNick(true) + ' was kicked by ' + options.by + ' ' + msg, 'action kick'); + this.addMsg(' ', '== ' + _kiwi.global.i18n.translate('%s was kicked by %s %s').fetch(member.displayNick(true), options.by, msg), 'action kick'); } } else { - this.addMsg(' ', '== You have been kicked by ' + options.by + ' ' + msg, 'action kick'); + this.addMsg(' ', '== ' + _kiwi.global.i18n.translate('You have been kicked by %s %s').fetch(options.by, msg), 'action kick'); } } else if (show_message) { - this.addMsg(' ', '== ' + member.displayNick(true) + ' has left ' + msg, 'action part'); + this.addMsg(' ', '== ' + _kiwi.global.i18n.translate('%s has left %s').fetch(member.displayNick(true), msg), 'action part'); } }, this); } diff --git a/client/assets/src/models/network.js b/client/assets/src/models/network.js index b6310b9..84ab239 100644 --- a/client/assets/src/models/network.js +++ b/client/assets/src/models/network.js @@ -112,8 +112,8 @@ // If not a valid channel name, display a warning if (!_kiwi.app.isChannelName(channel_name)) { - that.panels.server.addMsg('', channel_name + ' is not a valid channel name'); - _kiwi.app.message.text(channel_name + ' is not a valid channel name', {timeout: 5000}); + that.panels.server.addMsg('', _kiwi.global.i18n.translate('%s is not a valid channel name').fetch(channel_name)); + _kiwi.app.message.text(_kiwi.global.i18n.translate('%s is not a valid channel name').fetch(channel_name), {timeout: 5000}); return; } @@ -153,7 +153,7 @@ function onDisconnect(event) { $.each(this.panels.models, function (index, panel) { - panel.addMsg('', 'Disconnected from the IRC network', 'action quit'); + panel.addMsg('', _kiwi.global.i18n.translate('Disconnected from the IRC network').fetch(), 'action quit'); }); } @@ -345,7 +345,7 @@ member = panel.get('members').getByNick(event.nick); if (member) { member.set('nick', event.newnick); - panel.addMsg('', '== ' + event.nick + ' is now known as ' + event.newnick, 'action nick'); + panel.addMsg('', '== ' + _kiwi.global.i18n.translate('%s is now known as %s').fetch(event.nick, event.newnick) , 'action nick'); } }); } @@ -468,7 +468,7 @@ if (!c) return; when = formatDate(new Date(event.when * 1000)); - c.addMsg('', 'Topic set by ' + event.nick + ' at ' + when, 'topic'); + c.addMsg('', _kiwi.global.i18n.translate('Topic set by %s at %s').fetch(event.nick, when), 'topic'); } @@ -575,11 +575,11 @@ } } - channel.addMsg('', '== ' + event.nick + ' sets mode ' + friendlyModeString(), 'action mode'); + channel.addMsg('', '== ' + _kiwi.global.i18n.translate('%s sets mode %s').fetch(event.nick, friendlyModeString()), 'action mode'); } else { // This is probably a mode being set on us. if (event.target.toLowerCase() === this.get("nick").toLowerCase()) { - this.panels.server.addMsg('', '== ' + event.nick + ' set mode ' + friendlyModeString(), 'action mode'); + this.panels.server.addMsg('', '== ' + _kiwi.global.i18n.translate('%s set mode %s').fetch(event.nick, friendlyModeString()), 'action mode'); } else { console.log('MODE command recieved for unknown target %s: ', event.target, event); } @@ -603,9 +603,9 @@ if (event.ident) { panel.addMsg(event.nick, event.nick + ' [' + event.nick + '!' + event.ident + '@' + event.host + '] * ' + event.msg, 'whois'); } else if (event.chans) { - panel.addMsg(event.nick, 'Channels: ' + event.chans, 'whois'); + panel.addMsg(event.nick, _kiwi.global.i18n.translate('Channels: %s').fetch(event.chans), 'whois'); } else if (event.irc_server) { - panel.addMsg(event.nick, 'Connected to server: ' + event.irc_server + ' ' + event.server_info, 'whois'); + panel.addMsg(event.nick, _kiwi.global.i18n.translate('Connected to server: %s %s').fetch(event.irc_server, event.server_info), 'whois'); } else if (event.msg) { panel.addMsg(event.nick, event.msg, 'whois'); } else if (event.logon) { @@ -613,11 +613,11 @@ logon_date.setTime(event.logon * 1000); logon_date = formatDate(logon_date); - panel.addMsg(event.nick, 'idle for ' + idle_time + ', signed on ' + logon_date, 'whois'); + panel.addMsg(event.nick, _kiwi.global.i18n.translate('Idle for %s, signed on %s').fetch(idle_time, logon_date), 'whois'); } else if (event.away_reason) { - panel.addMsg(event.nick, 'Away: ' + event.away_reason, 'whois'); + panel.addMsg(event.nick, _kiwi.global.i18n.translate('Away: %s').fetch(event.away_reason), 'whois'); } else { - panel.addMsg(event.nick, 'idle for ' + idle_time, 'whois'); + panel.addMsg(event.nick, _kiwi.global.i18n.translate('Idle for %s').fetch(idle_time), 'whois'); } } @@ -631,7 +631,7 @@ if (event.host) { panel.addMsg(event.nick, event.nick + ' [' + event.nick + ((event.ident)? '!' + event.ident : '') + '@' + event.host + '] * ' + event.real_name, 'whois'); } else { - panel.addMsg(event.nick, 'No such nick', 'whois'); + panel.addMsg(event.nick, _kiwi.global.i18n.translate('No such nick').fetch(), 'whois'); } } @@ -665,20 +665,20 @@ switch (event.error) { case 'banned_from_channel': - panel.addMsg(' ', '== You are banned from ' + event.channel + '. ' + event.reason, 'status'); - _kiwi.app.message.text('You are banned from ' + event.channel + '. ' + event.reason); + panel.addMsg(' ', '== ' + _kiwi.global.i18n.translate('You are banned from %s. %s').fetch(event.channel, event.reason), 'status'); + _kiwi.app.message.text(_kiwi.global.i18n.translate('You are banned from %s. %s').fetch(event.channel, event.reason)); break; case 'bad_channel_key': - panel.addMsg(' ', '== Bad channel key for ' + event.channel, 'status'); - _kiwi.app.message.text('Bad channel key or password for ' + event.channel); + panel.addMsg(' ', '== ' + _kiwi.global.i18n.translate('Bad channel key for %s').fetch(event.channel), 'status'); + _kiwi.app.message.text(_kiwi.global.i18n.translate('Bad channel key for %s').fetch(event.channel)); break; case 'invite_only_channel': - panel.addMsg(' ', '== ' + event.channel + ' is invite only.', 'status'); - _kiwi.app.message.text(event.channel + ' is invite only'); + panel.addMsg(' ', '== ' + _kiwi.global.i18n.translate('%s is invite only.').fetch(event.channel), 'status'); + _kiwi.app.message.text(_kiwi.global.i18n.translate('%s is invite only').fetch(event.channel)); break; case 'channel_is_full': - panel.addMsg(' ', '== ' + event.channel + ' is full.', 'status'); - _kiwi.app.message.text(event.channel + ' is full'); + panel.addMsg(' ', '== ' + _kiwi.global.i18n.translate('%s is full.').fetch(event.channel), 'status'); + _kiwi.app.message.text(_kiwi.global.i18n.translate('%s is full').fetch(event.channel)); break; case 'chanop_privs_needed': panel.addMsg(' ', '== ' + event.reason, 'status'); @@ -693,9 +693,9 @@ } break; case 'nickname_in_use': - this.panels.server.addMsg(' ', '== The nickname ' + event.nick + ' is already in use. Please select a new nickname', 'status'); + this.panels.server.addMsg(' ', '== ' + _kiwi.global.i18n.translate('The nickname "%s" is already in use. Please select a new nickname').fetch( event.nick), 'status'); if (this.panels.server !== this.panels.active) { - _kiwi.app.message.text('The nickname "' + event.nick + '" is already in use. Please select a new nickname'); + _kiwi.app.message.text(_kiwi.global.i18n.translate('The nickname "%s" is already in use. Please select a new nickname').fetch(event.nick)); } // Only show the nickchange component if the controlbox is open @@ -706,7 +706,7 @@ break; case 'password_mismatch': - this.panels.server.addMsg(' ', '== Incorrect password given', 'status'); + this.panels.server.addMsg(' ', '== ' + _kiwi.global.i18n.translate('Incorrect password given').fetch(), 'status'); break; default: // We don't know what data contains, so don't do anything with it. diff --git a/client/assets/src/views/application.js b/client/assets/src/views/application.js index 17a5941..b252a37 100644 --- a/client/assets/src/views/application.js +++ b/client/assets/src/views/application.js @@ -23,7 +23,7 @@ _kiwi.view.Application = Backbone.View.extend({ // Confirmation require to leave the page window.onbeforeunload = function () { if (_kiwi.gateway.isConnected()) { - return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?'; + return _kiwi.global.i18n.translate('This will close all KiwiIRC conversations. Are you sure you want to close this window?').fetch(); } }; diff --git a/client/assets/src/views/channel.js b/client/assets/src/views/channel.js index aaef808..0eb2cb3 100644 --- a/client/assets/src/views/channel.js +++ b/client/assets/src/views/channel.js @@ -26,7 +26,7 @@ _kiwi.view.Channel = _kiwi.view.Panel.extend({ // Only show the loader if this is a channel (ie. not a query) if (this.model.isChannel()) { - this.$el.append('
Joining channel..
'); + this.$el.append('
' + _kiwi.global.i18n.translate('Joining channel..').fetch() + '
'); } }, @@ -40,7 +40,7 @@ _kiwi.view.Channel = _kiwi.view.Panel.extend({ topic = this.model.get("topic"); } - this.model.addMsg('', '== Topic for ' + this.model.get('name') + ' is: ' + topic, 'topic'); + this.model.addMsg('', '== ' + _kiwi.global.i18n.translate('Topic for %s is: %s').fetch(this.model.get('name'), topic), 'topic'); // If this is the active channel then update the topic bar if (_kiwi.app.panels().active === this) { diff --git a/client/assets/src/views/mediamessage.js b/client/assets/src/views/mediamessage.js index 9a86d0d..587b74d 100644 --- a/client/assets/src/views/mediamessage.js +++ b/client/assets/src/views/mediamessage.js @@ -20,8 +20,8 @@ _kiwi.view.MediaMessage = Backbone.View.extend({ open: function () { // Create the content div if we haven't already if (!this.$content) { - this.$content = $(''); - this.$content.find('.content').append(this.mediaTypes[this.$el.data('type')].apply(this, []) || 'Not found :('); + this.$content = $(''); + this.$content.find('.content').append(this.mediaTypes[this.$el.data('type')].apply(this, []) || _kiwi.global.i18n.translate('Not found').fetch() + ' :('); } // Now show the content if not already @@ -47,7 +47,7 @@ _kiwi.view.MediaMessage = Backbone.View.extend({ that.$content.find('.content').html(data.html); }); - return $('
Loading tweet..
'); + return $('
' + _kiwi.global.i18n.translate('Loading tweet').fetch() + '...
'); }, @@ -64,7 +64,7 @@ _kiwi.view.MediaMessage = Backbone.View.extend({ that.$content.find('.content').html(img_html); }); - return $('
Loading image..
'); + return $('
' + _kiwi.global.i18n.translate('Loading image').fetch() + '...
'); }, @@ -100,7 +100,7 @@ _kiwi.view.MediaMessage = Backbone.View.extend({ that.$content.find('.content').html(_.template(tmpl, post)); }); - return $('
Loading Reddit thread..
'); + return $('
' + _kiwi.global.i18n.translate('Loading Reddit thread').fetch() + '...
'); }, @@ -123,7 +123,7 @@ _kiwi.view.MediaMessage = Backbone.View.extend({ that.$content.find('.content').html(data.div); }); - return $('
Loading gist..
'); + return $('
' + _kiwi.global.i18n.translate('Loading gist').fetch() + '...
'); } } }, { diff --git a/client/assets/src/views/nickchangebox.js b/client/assets/src/views/nickchangebox.js index 8cea5e1..b75fc99 100644 --- a/client/assets/src/views/nickchangebox.js +++ b/client/assets/src/views/nickchangebox.js @@ -3,11 +3,16 @@ _kiwi.view.NickChangeBox = Backbone.View.extend({ 'submit': 'changeNick', 'click .cancel': 'close' }, - + initialize: function () { - this.$el = $($('#tmpl_nickchange').html().trim()); + var text = { + new_nick: _kiwi.global.i18n.translate('New nick').fetch(), + change: _kiwi.global.i18n.translate('Change').fetch(), + cancel: _kiwi.global.i18n.translate('Cancel').fetch() + }; + this.$el = $(_.template($('#tmpl_nickchange').html().trim(), text)); }, - + render: function () { // Add the UI component and give it focus _kiwi.app.controlbox.$el.prepend(this.$el); diff --git a/client/assets/src/views/panel.js b/client/assets/src/views/panel.js index 0ab799d..9546f1f 100644 --- a/client/assets/src/views/panel.js +++ b/client/assets/src/views/panel.js @@ -133,7 +133,7 @@ _kiwi.view.Panel = Backbone.View.extend({ this.alert('action'); } else if (is_highlight) { - _kiwi.app.view.alertWindow('* People are talking!'); + _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('People are talking!').fetch()); _kiwi.app.view.favicon.newHighlight(); _kiwi.app.view.playSound('highlight'); this.alert('highlight'); @@ -141,13 +141,13 @@ _kiwi.view.Panel = Backbone.View.extend({ } else { // If this is the active panel, send an alert out if (this.model.isActive()) { - _kiwi.app.view.alertWindow('* People are talking!'); + _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('People are talking!').fetch()); } this.alert('activity'); } if (this.model.isQuery() && !this.model.isActive()) { - _kiwi.app.view.alertWindow('* People are talking!'); + _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('People are talking!').fetch()); if (!is_highlight) { _kiwi.app.view.favicon.newHighlight(); } diff --git a/client/assets/src/views/serverselect.js b/client/assets/src/views/serverselect.js index eebdd64..a6dda5b 100644 --- a/client/assets/src/views/serverselect.js +++ b/client/assets/src/views/serverselect.js @@ -12,9 +12,24 @@ _kiwi.view.ServerSelect = function () { }, initialize: function () { - var that = this; - - this.$el = $($('#tmpl_server_select').html().trim()); + var that = this, + text = { + think_nick: _kiwi.global.i18n.translate('Think of a nickname...').fetch(), + nickname: _kiwi.global.i18n.translate('Nickname').fetch(), + have_password: _kiwi.global.i18n.translate('I have a password').fetch(), + password: _kiwi.global.i18n.translate('Password').fetch(), + channel: _kiwi.global.i18n.translate('Channel').fetch(), + channel_key: _kiwi.global.i18n.translate('Channel Key').fetch(), + require_key: _kiwi.global.i18n.translate('Channel requires a key').fetch(), + key: _kiwi.global.i18n.translate('Key').fetch(), + start: _kiwi.global.i18n.translate('Start...').fetch(), + server_network: _kiwi.global.i18n.translate('Server and network').fetch(), + server: _kiwi.global.i18n.translate('Server').fetch(), + port: _kiwi.global.i18n.translate('Port').fetch(), + powered_by: _kiwi.global.i18n.translate('Powered by Kiwi IRC').fetch() + }; + + this.$el = $(_.template($('#tmpl_server_select').html().trim(), text)); // Remove the 'more' link if the server has disabled server changing if (_kiwi.app.server_settings && _kiwi.app.server_settings.connection) { @@ -42,7 +57,7 @@ _kiwi.view.ServerSelect = function () { // Make sure a nick is chosen if (!$('input.nick', this.$el).val().trim()) { - this.setStatus('Select a nickname first!'); + this.setStatus(_kiwi.global.i18n.translate('Select a nickname first!').fetch()); $('input.nick', this.$el).select(); return; } @@ -196,12 +211,12 @@ _kiwi.view.ServerSelect = function () { }, networkConnected: function (event) { - this.setStatus('Connected :)', 'ok'); + this.setStatus(_kiwi.global.i18n.translate('Connected').fetch() + ' :)', 'ok'); $('form', this.$el).hide(); }, networkConnecting: function (event) { - this.setStatus('Connecting..', 'ok'); + this.setStatus(_kiwi.global.i18n.translate('Connecting..').fetch(), 'ok'); }, onIrcError: function (data) { @@ -209,17 +224,17 @@ _kiwi.view.ServerSelect = function () { switch(data.error) { case 'nickname_in_use': - this.setStatus('Nickname already taken'); + this.setStatus(_kiwi.global.i18n.translate('Nickname already taken').fetch()); this.show('nick_change'); this.$el.find('.nick').select(); break; case 'erroneus_nickname': - this.setStatus('Erroneus nickname'); + this.setStatus(_kiwi.global.i18n.translate('Erroneus nickname').fetch()); this.show('nick_change'); this.$el.find('.nick').select(); break; case 'password_mismatch': - this.setStatus('Incorrect Password'); + this.setStatus(_kiwi.global.i18n.translate('Incorrect Password').fetch()); this.show('nick_change'); this.$el.find('.password').select(); break; @@ -227,16 +242,16 @@ _kiwi.view.ServerSelect = function () { }, showError: function (error_reason) { - var err_text = 'Error Connecting'; + var err_text = _kiwi.global.i18n.translate('Error Connecting').fetch(); if (error_reason) { switch (error_reason) { case 'ENOTFOUND': - err_text = 'Server not found'; + err_text = _kiwi.global.i18n.translate('Server not found').fetch(); break; case 'ECONNREFUSED': - err_text += ' (Connection refused)'; + err_text += ' (' + _kiwi.global.i18n.translate('Connection refused').fetch() + ')'; break; default: diff --git a/client/assets/src/views/userbox.js b/client/assets/src/views/userbox.js index 416e5c9..de9d0c3 100644 --- a/client/assets/src/views/userbox.js +++ b/client/assets/src/views/userbox.js @@ -12,7 +12,18 @@ _kiwi.view.UserBox = Backbone.View.extend({ }, initialize: function () { - this.$el = $($('#tmpl_userbox').html().trim()); + var text = { + op: _kiwi.global.i18n.translate('Op').fetch(), + de_op: _kiwi.global.i18n.translate('De-op').fetch(), + voice: _kiwi.global.i18n.translate('Voice').fetch(), + de_voice: _kiwi.global.i18n.translate('De-voice').fetch(), + kick: _kiwi.global.i18n.translate('Kick').fetch(), + ban: _kiwi.global.i18n.translate('Ban').fetch(), + message: _kiwi.global.i18n.translate('Message').fetch(), + info: _kiwi.global.i18n.translate('Info').fetch(), + slap: _kiwi.global.i18n.translate('Slap!').fetch() + }; + this.$el = $(_.template($('#tmpl_userbox').html().trim(), text)); }, queryClick: function (event) { -- 2.25.1
Channel NameUsersTopic<%= channel_name %><%= users %><%= topic %>