ci-18n 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create @coffee-i18n
@@ -0,0 +1,4 @@
1
+
2
+ guard 'coffeescript', :input => 'coffeescripts', :output =>'build/compiled', :bare => false
3
+
4
+ guard 'coffeescript', :input => 'spec/coffeescripts', :output => 'spec/javascripts', :bare => false
@@ -0,0 +1,151 @@
1
+ ci-18n
2
+ ======
3
+
4
+ This javascript library tries to bring the power of the
5
+ [I18n ruby library](https://github.com/svenfuchs/i18n.git)
6
+ to javascript.
7
+
8
+ Currently this library is on active development, so use it with caution.
9
+
10
+ Installation
11
+ ------
12
+
13
+ This library has been though to be used inside a [Ruby on Rails][rails]
14
+ application, namely rails 3.1 and its new asset pipeline.
15
+
16
+ [rails]: http://www.rubyonrails.org
17
+
18
+ ### Installation within rails 3.1
19
+
20
+ It's as simple as running in your rails root:
21
+
22
+ echo 'gem "ci-18n"' >> Gemfile
23
+
24
+ Then you should simply include the `ci-18n` js file where you want.
25
+ Namely you could include it inside your `application.js` or externalize
26
+ it. Note that running `rake assets:precompile` will compile it.
27
+
28
+ ### Installation without rails 3.1
29
+
30
+ Download the file stored [here][download_link] and put it near
31
+ your other javascripts.
32
+
33
+ [download_link]: https://github.com/mcollina/ci-18n/raw/master/build/ci-18n.min.js
34
+
35
+ General usage
36
+ --------------------
37
+
38
+ ### Translation files loading
39
+
40
+ There are two possible strategies for loading the translation files:
41
+
42
+ 1. include +all of them+ inside your `application.js` for faster loading,
43
+ and in this case you should add `I18n.autosetup("en")` to your main
44
+ js file (if en is your default language);
45
+ 2. autoload it at runtime from the browser, and in this case you have
46
+ to:
47
+ * publish your translation files under the `/locales` folder.
48
+ * add `I18.autoloadAndSetup({ path: "/locales", default: "en" })` if
49
+ en is your default language.
50
+
51
+ ### Translation file syntax
52
+
53
+ I18n.addLanguage("en", { hello: "world", another: "aaa" });
54
+
55
+ For translating date and times, it follows the very same rules of the
56
+ I18n ruby gem, and you might want to use the same data once ported to
57
+ javascript, like so:
58
+
59
+ I18n.addLanguage("de", {
60
+ date: {
61
+ formats: {
62
+ "default": "%d.%m.%Y",
63
+ short: "%d. %b",
64
+ long: "%d. %B %Y"
65
+ },
66
+ day_names: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
67
+ abbr_day_names: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
68
+ month_names: ["Januar", "Februar", "März", "April", "Mai",
69
+ "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember", null],
70
+ abbr_month_names: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"]
71
+ },
72
+ time: {
73
+ formats: {
74
+ "default": "%a, %d. %b %Y %H:%M:%S %z",
75
+ short: "%d. %b %H:%M",
76
+ long: "%d. %B %Y %H:%M"
77
+ },
78
+ am: 'am',
79
+ pm: 'pm'
80
+ }
81
+ });
82
+
83
+ ### Translation & Localization
84
+
85
+ Really, if you are familiar with the ruby [I18n][i18n] library, it just works
86
+ the same.
87
+
88
+ You can translate with:
89
+ $i18n.t("hello.world")
90
+
91
+ If you want to localize a datetime:
92
+ $i18n.l(new Date(), format: "default", type: "datetime")
93
+
94
+ or if you want to localize just a date:
95
+ $i18n.l(new Date(), format: "default", type: "date")
96
+
97
+ [i18n]: https://github.com/svenfuchs/i18n
98
+
99
+ TODO
100
+ ----
101
+
102
+ * Testing on all major browser.
103
+ * Add some documentation.
104
+ * More documentation.
105
+ * Examples.
106
+ * Build a website.
107
+ * Build a translation repository like the one for I18n.
108
+ * Use of the rails asset pipeline to build a langs.js with all language
109
+ translations.
110
+ * Simplify autoloading.
111
+ * Clean up the development environment.
112
+
113
+ Development Environment
114
+ ------
115
+
116
+ Currently the build uses heavily some ruby gems, so run:
117
+
118
+ bundle install
119
+
120
+ This library is built with [CoffeeScript](https://github.com/jashkenas/coffee-script),
121
+ so you need to install it before doing everything else.
122
+
123
+ As it's built with CoffeeScript, we need to rebuild the sources before each test run,
124
+ and for that purpose start [Guard](https://github.com/guard/guard) with
125
+ the command:
126
+
127
+ guard
128
+
129
+ To build everything, hit
130
+
131
+ CTRL-\
132
+
133
+ To run the specs in the browser, launch the command
134
+
135
+ rake jasmine
136
+
137
+ and head to http://localhost:8888.
138
+
139
+ Finally, to build the js version to use in your code, run:
140
+
141
+ rake minify
142
+
143
+ That generates two files under the build/ directory, ci-18n.js and ci-18n.min.js.
144
+
145
+ Development
146
+ -----
147
+
148
+ * Source hosted at GitHub.
149
+ * Report Issues/Questions/Feature requests on GitHub Issues.
150
+
151
+ Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change you make.
@@ -0,0 +1,47 @@
1
+
2
+ require 'active_support/concern'
3
+ require 'jasmine'
4
+ load 'jasmine/tasks/jasmine.rake'
5
+ require 'rake/minify'
6
+
7
+ Rake::Minify.new do
8
+ group("build/ci-18n.min.js") do
9
+ add("coffeescripts/I18n.coffee")
10
+ add("javascripts/sprintf-0.7-beta1.js")
11
+ end
12
+
13
+ group("build/ci-18n.js") do
14
+ add("coffeescripts/I18n.coffee", :minify => false)
15
+ add("javascripts/sprintf-0.7-beta1.js", :minify => false)
16
+ end
17
+ end
18
+
19
+ task :build_vendor => :minify do
20
+ dir = File.dirname(__FILE__) + "/vendor/assets/javascripts"
21
+ FileUtils.mkdir_p(dir)
22
+ FileUtils.cp("build/ci-18n.js", dir)
23
+ FileUtils.cp("build/ci-18n.min.js", dir)
24
+ end
25
+
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gem|
28
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
29
+ gem.name = "ci-18n"
30
+ gem.homepage = "http://github.com/mcollina/ci-18n"
31
+ gem.license = "MIT"
32
+ gem.summary = %Q{A localization library for javascript files in ruby on rails.}
33
+ gem.description = %Q{A localization library for javascript files in ruby on rails.}
34
+ gem.email = "hello@matteocollina.com"
35
+ gem.authors = ["Matteo Collina"]
36
+ gem.add_development_dependency "bundler", "~> 1.0.0"
37
+ gem.add_development_dependency 'jeweler', '>= 0'
38
+ gem.add_development_dependency 'coffee-script', '~> 2.2.0'
39
+ gem.add_development_dependency 'jasmine', '~> 1.0'
40
+ gem.add_development_dependency 'guard', '~> 0.3.1'
41
+ gem.add_development_dependency 'guard-coffeescript', '~> 0.2.0'
42
+ gem.add_development_dependency 'guard-livereload', '~> 0.1.9'
43
+ gem.add_development_dependency 'rake-minify', '~> 0.3'
44
+ end
45
+ Jeweler::RubygemsDotOrgTasks.new
46
+
47
+ task :build => :build_vendor
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
File without changes
@@ -0,0 +1,464 @@
1
+ (function() {
2
+ var I18n;
3
+ I18n = (function() {
4
+ var innerLookup;
5
+ function I18n(currentLocale, defaultLocale) {
6
+ if (currentLocale == null) {
7
+ currentLocale = {};
8
+ }
9
+ if (defaultLocale == null) {
10
+ defaultLocale = void 0;
11
+ }
12
+ if (typeof currentLocale === 'string') {
13
+ this.localeString = currentLocale;
14
+ } else {
15
+ this.localeVal = currentLocale;
16
+ }
17
+ if (typeof defaultLocale === 'string') {
18
+ this.defaultString = defaultLocale;
19
+ } else if (!(defaultLocale != null)) {
20
+ this.defaultString = I18n.getDefaultLanguage();
21
+ } else {
22
+ this.defaultVal = defaultLocale || {};
23
+ }
24
+ }
25
+ I18n.prototype.locale = function() {
26
+ return this.localeVal || (this.localeVal = I18n.language(this.localeString));
27
+ };
28
+ I18n.prototype.defaultLocale = function() {
29
+ return this.defaultVal || (this.defaultVal = I18n.language(this.defaultString));
30
+ };
31
+ innerLookup = function(locale, keywordList) {
32
+ var keyword, _i, _len;
33
+ for (_i = 0, _len = keywordList.length; _i < _len; _i++) {
34
+ keyword = keywordList[_i];
35
+ if (locale == null) {
36
+ break;
37
+ }
38
+ locale = locale[keyword];
39
+ }
40
+ return locale;
41
+ };
42
+ I18n.prototype.translate = function(keywordList, options) {
43
+ var lookup;
44
+ if (options == null) {
45
+ options = {};
46
+ }
47
+ keywordList = I18n.normalizeKeys(keywordList, options);
48
+ lookup = innerLookup(this.locale(), keywordList) || options["default"] || innerLookup(this.defaultLocale(), keywordList);
49
+ delete options.scope;
50
+ delete options["default"];
51
+ return I18n.interpolate(lookup, options);
52
+ };
53
+ I18n.prototype.localize = function(date, options) {
54
+ var match, matches, regexp, replacement_builder, string, _i, _len;
55
+ if (!(date instanceof Date)) {
56
+ throw "Argument Error: " + date + " is not localizable";
57
+ }
58
+ regexp = /%([a-z]|%)/ig;
59
+ string = options.format;
60
+ matches = string.match(regexp);
61
+ if (matches == null) {
62
+ if (options.type == null) {
63
+ throw "Argument Error: missing type";
64
+ }
65
+ if (options.type === "datetime") {
66
+ options.type = "time";
67
+ }
68
+ string = this.translate("" + options.type + ".formats." + options.format);
69
+ if (string != null) {
70
+ matches = string.match(regexp);
71
+ }
72
+ }
73
+ if (matches == null) {
74
+ throw "Argument Error: no such format";
75
+ }
76
+ for (_i = 0, _len = matches.length; _i < _len; _i++) {
77
+ match = matches[_i];
78
+ match = match.slice(-1);
79
+ replacement_builder = I18n.strftime[match];
80
+ if (replacement_builder != null) {
81
+ string = string.replace("%" + match, replacement_builder(date, this));
82
+ }
83
+ }
84
+ return string;
85
+ };
86
+ I18n.prototype.t = I18n.prototype.translate;
87
+ I18n.prototype.l = I18n.prototype.localize;
88
+ return I18n;
89
+ })();
90
+ I18n.normalizeKeys = function(keywords, options) {
91
+ var keyword, splitted_keywords, _i, _len, _ref;
92
+ if (keywords == null) {
93
+ keywords = [];
94
+ }
95
+ if (options == null) {
96
+ options = {
97
+ scope: []
98
+ };
99
+ }
100
+ if (keywords instanceof Array) {
101
+ return keywords;
102
+ }
103
+ splitted_keywords = [];
104
+ _ref = keywords.split(".");
105
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
106
+ keyword = _ref[_i];
107
+ if ((keyword != null) && keyword !== '') {
108
+ splitted_keywords.push(keyword);
109
+ }
110
+ }
111
+ return I18n.normalizeKeys(options.scope).concat(splitted_keywords);
112
+ };
113
+ (function() {
114
+ var interpolate_basic, interpolate_sprintf;
115
+ interpolate_basic = function(string, option, value) {
116
+ var new_string;
117
+ new_string = string.replace(RegExp("%{" + option + "}", "g"), value);
118
+ if (string === new_string) {
119
+ return;
120
+ }
121
+ return new_string;
122
+ };
123
+ interpolate_sprintf = function(string, option, value) {
124
+ var match, regexp, result;
125
+ regexp = RegExp("%<" + option + ">(.*?\\d*\\.?\\d*[bBdiouxXeEfgGcps])");
126
+ match = string.match(regexp);
127
+ if (match == null) {
128
+ return;
129
+ }
130
+ result = sprintf("%(keyword)" + match[1], {
131
+ keyword: value
132
+ });
133
+ return string.replace(match[0], result);
134
+ };
135
+ return I18n.interpolate = function(string, options) {
136
+ var new_string, option, value;
137
+ if (options == null) {
138
+ options = {};
139
+ }
140
+ if (!(string != null)) {
141
+ return string;
142
+ }
143
+ for (option in options) {
144
+ value = options[option];
145
+ new_string = interpolate_basic(string, option, value);
146
+ new_string || (new_string = interpolate_sprintf(string, option, value));
147
+ if (new_string == null) {
148
+ throw new Error("Missing placeholder for keyword \"" + option + "\"");
149
+ }
150
+ string = new_string;
151
+ }
152
+ return string;
153
+ };
154
+ })();
155
+ I18n.strftime = {
156
+ 'd': function(date) {
157
+ return ('0' + date.getDate()).slice(-2);
158
+ },
159
+ 'b': function(date, i18n) {
160
+ return i18n.t("date.abbr_month_names")[date.getMonth()];
161
+ },
162
+ 'B': function(date, i18n) {
163
+ return i18n.t("date.month_names")[date.getMonth()];
164
+ },
165
+ 'a': function(date, i18n) {
166
+ return i18n.t("date.abbr_day_names")[date.getDay()];
167
+ },
168
+ 'A': function(date, i18n) {
169
+ return i18n.t("date.day_names")[date.getDay()];
170
+ },
171
+ 'Y': function(date) {
172
+ return date.getFullYear();
173
+ },
174
+ 'm': function(date) {
175
+ return ('0' + (date.getMonth() + 1)).slice(-2);
176
+ },
177
+ 'H': function(date) {
178
+ return ('0' + (date.getHours())).slice(-2);
179
+ },
180
+ 'M': function(date) {
181
+ return ('0' + (date.getMinutes())).slice(-2);
182
+ },
183
+ 'S': function(date) {
184
+ return ('0' + (date.getSeconds())).slice(-2);
185
+ },
186
+ 'z': function(date) {
187
+ var tz_offset;
188
+ tz_offset = date.getTimezoneOffset();
189
+ return (tz_offset > 0 && '-' || '+') + ('0' + (tz_offset / 60)).slice(-2) + ('0' + (tz_offset % 60)).slice(-2);
190
+ },
191
+ 'p': function(date, i18n) {
192
+ return i18n.t("time")[date.getHours() >= 12 && 'pm' || 'am'];
193
+ },
194
+ 'e': function(date) {
195
+ return date.getDate();
196
+ },
197
+ 'I': function(date) {
198
+ return ('0' + (date.getHours() % 12)).slice(-2);
199
+ },
200
+ 'j': function(date) {
201
+ return (((date.getTime() - new Date("Jan 1 " + date.getFullYear()).getTime()) / (1000 * 60 * 60 * 24) + 1) + '').split(/\./)[0];
202
+ },
203
+ 'k': function(date) {
204
+ return date.getHours();
205
+ },
206
+ 'l': function(date) {
207
+ return date.getHours() % 12;
208
+ },
209
+ 'w': function(date) {
210
+ return date.getDay();
211
+ },
212
+ 'y': function(date) {
213
+ return ("" + (date.getYear())).slice(-2);
214
+ },
215
+ '%': function() {
216
+ return '%';
217
+ }
218
+ };
219
+ (function() {
220
+ var defaultLanguage, languages;
221
+ languages = {};
222
+ defaultLanguage = void 0;
223
+ I18n.addLanguage = function(name, lang) {
224
+ return languages[name] = lang;
225
+ };
226
+ I18n.clearLanguages = function() {
227
+ return languages = {};
228
+ };
229
+ I18n.language = function(name) {
230
+ return languages[name];
231
+ };
232
+ I18n.setDefaultLanguage = function(name) {
233
+ return defaultLanguage = name;
234
+ };
235
+ return I18n.getDefaultLanguage = function() {
236
+ return defaultLanguage;
237
+ };
238
+ })();
239
+ I18n.load = function(path, lang) {
240
+ var script, url;
241
+ url = "" + path + "/" + lang + ".js";
242
+ script = document.createElement('script');
243
+ script.setAttribute('src', url);
244
+ return document.getElementsByTagName('head')[0].appendChild(script);
245
+ };
246
+ I18n.detectLanguage = function(navigator) {
247
+ var name, _i, _len, _ref;
248
+ _ref = ["language", "browserLanguage"];
249
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
250
+ name = _ref[_i];
251
+ if (navigator[name] != null) {
252
+ return navigator[name];
253
+ }
254
+ }
255
+ };
256
+ I18n.autoloadAndSetup = function(options) {
257
+ var lang, langsToLoad, _i, _len;
258
+ if (options.language == null) {
259
+ options.language = I18n.detectLanguage(window.navigator);
260
+ }
261
+ langsToLoad = [options.language];
262
+ if (options["default"] != null) {
263
+ langsToLoad.push(options["default"]);
264
+ }
265
+ for (_i = 0, _len = langsToLoad.length; _i < _len; _i++) {
266
+ lang = langsToLoad[_i];
267
+ I18n.load(options.path, lang);
268
+ }
269
+ return I18n.setup(options.language, options["default"]);
270
+ };
271
+ I18n.setup = function(locale, defaultLocale) {
272
+ return window.$i18n = new I18n(locale, defaultLocale);
273
+ };
274
+ I18n.autosetup = function(defaultLocale) {
275
+ var locale;
276
+ locale = I18n.detectLanguage(window.navigator);
277
+ return I18n.setup(locale, defaultLocale);
278
+ };
279
+ window.I18n = I18n;
280
+ }).call(this);
281
+
282
+ /**
283
+ sprintf() for JavaScript 0.7-beta1
284
+ http://www.diveintojavascript.com/projects/javascript-sprintf
285
+
286
+ Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
287
+ All rights reserved.
288
+
289
+ Redistribution and use in source and binary forms, with or without
290
+ modification, are permitted provided that the following conditions are met:
291
+ * Redistributions of source code must retain the above copyright
292
+ notice, this list of conditions and the following disclaimer.
293
+ * Redistributions in binary form must reproduce the above copyright
294
+ notice, this list of conditions and the following disclaimer in the
295
+ documentation and/or other materials provided with the distribution.
296
+ * Neither the name of sprintf() for JavaScript nor the
297
+ names of its contributors may be used to endorse or promote products
298
+ derived from this software without specific prior written permission.
299
+
300
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
301
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
302
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
303
+ DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
304
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
305
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
306
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
307
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
308
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
309
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
310
+
311
+
312
+ Changelog:
313
+ 2010.09.06 - 0.7-beta1
314
+ - features: vsprintf, support for named placeholders
315
+ - enhancements: format cache, reduced global namespace pollution
316
+
317
+ 2010.05.22 - 0.6:
318
+ - reverted to 0.4 and fixed the bug regarding the sign of the number 0
319
+ Note:
320
+ Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
321
+ who warned me about a bug in 0.5, I discovered that the last update was
322
+ a regress. I appologize for that.
323
+
324
+ 2010.05.09 - 0.5:
325
+ - bug fix: 0 is now preceeded with a + sign
326
+ - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
327
+ - switched from GPL to BSD license
328
+
329
+ 2007.10.21 - 0.4:
330
+ - unit test and patch (David Baird)
331
+
332
+ 2007.09.17 - 0.3:
333
+ - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
334
+
335
+ 2007.09.11 - 0.2:
336
+ - feature: added argument swapping
337
+
338
+ 2007.04.03 - 0.1:
339
+ - initial release
340
+ **/
341
+
342
+ var sprintf = (function() {
343
+ function get_type(variable) {
344
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
345
+ }
346
+ function str_repeat(input, multiplier) {
347
+ for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
348
+ return output.join('');
349
+ }
350
+
351
+ var str_format = function() {
352
+ if (!str_format.cache.hasOwnProperty(arguments[0])) {
353
+ str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
354
+ }
355
+ return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
356
+ };
357
+
358
+ str_format.format = function(parse_tree, argv) {
359
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
360
+ for (i = 0; i < tree_length; i++) {
361
+ node_type = get_type(parse_tree[i]);
362
+ if (node_type === 'string') {
363
+ output.push(parse_tree[i]);
364
+ }
365
+ else if (node_type === 'array') {
366
+ match = parse_tree[i]; // convenience purposes only
367
+ if (match[2]) { // keyword argument
368
+ arg = argv[cursor];
369
+ for (k = 0; k < match[2].length; k++) {
370
+ if (!arg.hasOwnProperty(match[2][k])) {
371
+ throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
372
+ }
373
+ arg = arg[match[2][k]];
374
+ }
375
+ }
376
+ else if (match[1]) { // positional argument (explicit)
377
+ arg = argv[match[1]];
378
+ }
379
+ else { // positional argument (implicit)
380
+ arg = argv[cursor++];
381
+ }
382
+
383
+ if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
384
+ throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
385
+ }
386
+ switch (match[8]) {
387
+ case 'b': arg = arg.toString(2); break;
388
+ case 'c': arg = String.fromCharCode(arg); break;
389
+ case 'd': arg = parseInt(arg, 10); break;
390
+ case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
391
+ case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
392
+ case 'o': arg = arg.toString(8); break;
393
+ case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
394
+ case 'u': arg = Math.abs(arg); break;
395
+ case 'x': arg = arg.toString(16); break;
396
+ case 'X': arg = arg.toString(16).toUpperCase(); break;
397
+ }
398
+ arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
399
+ pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
400
+ pad_length = match[6] - String(arg).length;
401
+ pad = match[6] ? str_repeat(pad_character, pad_length) : '';
402
+ output.push(match[5] ? arg + pad : pad + arg);
403
+ }
404
+ }
405
+ return output.join('');
406
+ };
407
+
408
+ str_format.cache = {};
409
+
410
+ str_format.parse = function(fmt) {
411
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
412
+ while (_fmt) {
413
+ if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
414
+ parse_tree.push(match[0]);
415
+ }
416
+ else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
417
+ parse_tree.push('%');
418
+ }
419
+ else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
420
+ if (match[2]) {
421
+ arg_names |= 1;
422
+ var field_list = [], replacement_field = match[2], field_match = [];
423
+ if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
424
+ field_list.push(field_match[1]);
425
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
426
+ if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
427
+ field_list.push(field_match[1]);
428
+ }
429
+ else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
430
+ field_list.push(field_match[1]);
431
+ }
432
+ else {
433
+ throw('[sprintf] huh?');
434
+ }
435
+ }
436
+ }
437
+ else {
438
+ throw('[sprintf] huh?');
439
+ }
440
+ match[2] = field_list;
441
+ }
442
+ else {
443
+ arg_names |= 2;
444
+ }
445
+ if (arg_names === 3) {
446
+ throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
447
+ }
448
+ parse_tree.push(match);
449
+ }
450
+ else {
451
+ throw('[sprintf] huh?');
452
+ }
453
+ _fmt = _fmt.substring(match[0].length);
454
+ }
455
+ return parse_tree;
456
+ };
457
+
458
+ return str_format;
459
+ })();
460
+
461
+ var vsprintf = function(fmt, argv) {
462
+ argv.unshift(fmt);
463
+ return sprintf.apply(null, argv);
464
+ };