ci-18n 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -0
- data/Guardfile +4 -0
- data/README.md +151 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/build/.gitkeep +0 -0
- data/build/ci-18n.js +464 -0
- data/build/ci-18n.min.js +52 -0
- data/ci-18n.gemspec +81 -0
- data/coffeescripts/I18n.coffee +220 -0
- data/javascripts/sprintf-0.7-beta1.js +183 -0
- data/lib/ci-18n.rb +1 -0
- data/lib/ci18n.rb +17 -0
- data/spec/coffeescripts/autoloadSpec.coffee +122 -0
- data/spec/coffeescripts/helpers/SpecHelper.coffee +6 -0
- data/spec/coffeescripts/languageRepositorySpec.coffee +34 -0
- data/spec/coffeescripts/localizeSpec.coffee +139 -0
- data/spec/coffeescripts/translateSpec.coffee +74 -0
- data/spec/coffeescripts/utilsSpec.coffee +61 -0
- data/spec/javascripts/support/jasmine.yml +74 -0
- data/spec/javascripts/support/jasmine_config.rb +23 -0
- data/spec/javascripts/support/jasmine_runner.rb +32 -0
- metadata +211 -0
data/build/ci-18n.min.js
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
(function(){var I18n;I18n=(function(){var innerLookup;function I18n(currentLocale,defaultLocale){if(currentLocale==null){currentLocale={};}
|
2
|
+
if(defaultLocale==null){defaultLocale=void 0;}
|
3
|
+
if(typeof currentLocale==='string'){this.localeString=currentLocale;}else{this.localeVal=currentLocale;}
|
4
|
+
if(typeof defaultLocale==='string'){this.defaultString=defaultLocale;}else if(!(defaultLocale!=null)){this.defaultString=I18n.getDefaultLanguage();}else{this.defaultVal=defaultLocale||{};}}
|
5
|
+
I18n.prototype.locale=function(){return this.localeVal||(this.localeVal=I18n.language(this.localeString));};I18n.prototype.defaultLocale=function(){return this.defaultVal||(this.defaultVal=I18n.language(this.defaultString));};innerLookup=function(locale,keywordList){var keyword,_i,_len;for(_i=0,_len=keywordList.length;_i<_len;_i++){keyword=keywordList[_i];if(locale==null){break;}
|
6
|
+
locale=locale[keyword];}
|
7
|
+
return locale;};I18n.prototype.translate=function(keywordList,options){var lookup;if(options==null){options={};}
|
8
|
+
keywordList=I18n.normalizeKeys(keywordList,options);lookup=innerLookup(this.locale(),keywordList)||options["default"]||innerLookup(this.defaultLocale(),keywordList);delete options.scope;delete options["default"];return I18n.interpolate(lookup,options);};I18n.prototype.localize=function(date,options){var match,matches,regexp,replacement_builder,string,_i,_len;if(!(date instanceof Date)){throw"Argument Error: "+date+" is not localizable";}
|
9
|
+
regexp=/%([a-z]|%)/ig;string=options.format;matches=string.match(regexp);if(matches==null){if(options.type==null){throw"Argument Error: missing type";}
|
10
|
+
if(options.type==="datetime"){options.type="time";}
|
11
|
+
string=this.translate(""+options.type+".formats."+options.format);if(string!=null){matches=string.match(regexp);}}
|
12
|
+
if(matches==null){throw"Argument Error: no such format";}
|
13
|
+
for(_i=0,_len=matches.length;_i<_len;_i++){match=matches[_i];match=match.slice(-1);replacement_builder=I18n.strftime[match];if(replacement_builder!=null){string=string.replace("%"+match,replacement_builder(date,this));}}
|
14
|
+
return string;};I18n.prototype.t=I18n.prototype.translate;I18n.prototype.l=I18n.prototype.localize;return I18n;})();I18n.normalizeKeys=function(keywords,options){var keyword,splitted_keywords,_i,_len,_ref;if(keywords==null){keywords=[];}
|
15
|
+
if(options==null){options={scope:[]};}
|
16
|
+
if(keywords instanceof Array){return keywords;}
|
17
|
+
splitted_keywords=[];_ref=keywords.split(".");for(_i=0,_len=_ref.length;_i<_len;_i++){keyword=_ref[_i];if((keyword!=null)&&keyword!==''){splitted_keywords.push(keyword);}}
|
18
|
+
return I18n.normalizeKeys(options.scope).concat(splitted_keywords);};(function(){var interpolate_basic,interpolate_sprintf;interpolate_basic=function(string,option,value){var new_string;new_string=string.replace(RegExp("%{"+option+"}","g"),value);if(string===new_string){return;}
|
19
|
+
return new_string;};interpolate_sprintf=function(string,option,value){var match,regexp,result;regexp=RegExp("%<"+option+">(.*?\\d*\\.?\\d*[bBdiouxXeEfgGcps])");match=string.match(regexp);if(match==null){return;}
|
20
|
+
result=sprintf("%(keyword)"+match[1],{keyword:value});return string.replace(match[0],result);};return I18n.interpolate=function(string,options){var new_string,option,value;if(options==null){options={};}
|
21
|
+
if(!(string!=null)){return string;}
|
22
|
+
for(option in options){value=options[option];new_string=interpolate_basic(string,option,value);new_string||(new_string=interpolate_sprintf(string,option,value));if(new_string==null){throw new Error("Missing placeholder for keyword \""+option+"\"");}
|
23
|
+
string=new_string;}
|
24
|
+
return string;};})();I18n.strftime={'d':function(date){return('0'+date.getDate()).slice(-2);},'b':function(date,i18n){return i18n.t("date.abbr_month_names")[date.getMonth()];},'B':function(date,i18n){return i18n.t("date.month_names")[date.getMonth()];},'a':function(date,i18n){return i18n.t("date.abbr_day_names")[date.getDay()];},'A':function(date,i18n){return i18n.t("date.day_names")[date.getDay()];},'Y':function(date){return date.getFullYear();},'m':function(date){return('0'+(date.getMonth()+1)).slice(-2);},'H':function(date){return('0'+(date.getHours())).slice(-2);},'M':function(date){return('0'+(date.getMinutes())).slice(-2);},'S':function(date){return('0'+(date.getSeconds())).slice(-2);},'z':function(date){var tz_offset;tz_offset=date.getTimezoneOffset();return(tz_offset>0&&'-'||'+')+('0'+(tz_offset/60)).slice(-2)+('0'+(tz_offset%60)).slice(-2);},'p':function(date,i18n){return i18n.t("time")[date.getHours()>=12&&'pm'||'am'];},'e':function(date){return date.getDate();},'I':function(date){return('0'+(date.getHours()%12)).slice(-2);},'j':function(date){return(((date.getTime()-new Date("Jan 1 "+date.getFullYear()).getTime())/(1000*60*60*24)+1)+'').split(/\./)[0];},'k':function(date){return date.getHours();},'l':function(date){return date.getHours()%12;},'w':function(date){return date.getDay();},'y':function(date){return(""+(date.getYear())).slice(-2);},'%':function(){return'%';}};(function(){var defaultLanguage,languages;languages={};defaultLanguage=void 0;I18n.addLanguage=function(name,lang){return languages[name]=lang;};I18n.clearLanguages=function(){return languages={};};I18n.language=function(name){return languages[name];};I18n.setDefaultLanguage=function(name){return defaultLanguage=name;};return I18n.getDefaultLanguage=function(){return defaultLanguage;};})();I18n.load=function(path,lang){var script,url;url=""+path+"/"+lang+".js";script=document.createElement('script');script.setAttribute('src',url);return document.getElementsByTagName('head')[0].appendChild(script);};I18n.detectLanguage=function(navigator){var name,_i,_len,_ref;_ref=["language","browserLanguage"];for(_i=0,_len=_ref.length;_i<_len;_i++){name=_ref[_i];if(navigator[name]!=null){return navigator[name];}}};I18n.autoloadAndSetup=function(options){var lang,langsToLoad,_i,_len;if(options.language==null){options.language=I18n.detectLanguage(window.navigator);}
|
25
|
+
langsToLoad=[options.language];if(options["default"]!=null){langsToLoad.push(options["default"]);}
|
26
|
+
for(_i=0,_len=langsToLoad.length;_i<_len;_i++){lang=langsToLoad[_i];I18n.load(options.path,lang);}
|
27
|
+
return I18n.setup(options.language,options["default"]);};I18n.setup=function(locale,defaultLocale){return window.$i18n=new I18n(locale,defaultLocale);};I18n.autosetup=function(defaultLocale){var locale;locale=I18n.detectLanguage(window.navigator);return I18n.setup(locale,defaultLocale);};window.I18n=I18n;}).call(this);
|
28
|
+
var sprintf=(function(){function get_type(variable){return Object.prototype.toString.call(variable).slice(8,-1).toLowerCase();}
|
29
|
+
function str_repeat(input,multiplier){for(var output=[];multiplier>0;output[--multiplier]=input){}
|
30
|
+
return output.join('');}
|
31
|
+
var str_format=function(){if(!str_format.cache.hasOwnProperty(arguments[0])){str_format.cache[arguments[0]]=str_format.parse(arguments[0]);}
|
32
|
+
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]);}
|
33
|
+
else if(node_type==='array'){match=parse_tree[i];if(match[2]){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]));}
|
34
|
+
arg=arg[match[2][k]];}}
|
35
|
+
else if(match[1]){arg=argv[match[1]];}
|
36
|
+
else{arg=argv[cursor++];}
|
37
|
+
if(/[^s]/.test(match[8])&&(get_type(arg)!='number')){throw(sprintf('[sprintf] expecting number but found %s',get_type(arg)));}
|
38
|
+
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;}
|
39
|
+
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);}}
|
40
|
+
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]);}
|
41
|
+
else if((match=/^\x25{2}/.exec(_fmt))!==null){parse_tree.push('%');}
|
42
|
+
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]);}
|
43
|
+
else if((field_match=/^\[(\d+)\]/.exec(replacement_field))!==null){field_list.push(field_match[1]);}
|
44
|
+
else{throw('[sprintf] huh?');}}}
|
45
|
+
else{throw('[sprintf] huh?');}
|
46
|
+
match[2]=field_list;}
|
47
|
+
else{arg_names|=2;}
|
48
|
+
if(arg_names===3){throw('[sprintf] mixing positional and named placeholders is not (yet) supported');}
|
49
|
+
parse_tree.push(match);}
|
50
|
+
else{throw('[sprintf] huh?');}
|
51
|
+
_fmt=_fmt.substring(match[0].length);}
|
52
|
+
return parse_tree;};return str_format;})();var vsprintf=function(fmt,argv){argv.unshift(fmt);return sprintf.apply(null,argv);};
|
data/ci-18n.gemspec
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ci-18n}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Matteo Collina"]
|
12
|
+
s.date = %q{2011-06-08}
|
13
|
+
s.description = %q{A localization library for javascript files in ruby on rails.}
|
14
|
+
s.email = %q{hello@matteocollina.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".rvmrc",
|
20
|
+
"Guardfile",
|
21
|
+
"README.md",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"build/.gitkeep",
|
25
|
+
"build/ci-18n.js",
|
26
|
+
"build/ci-18n.min.js",
|
27
|
+
"ci-18n.gemspec",
|
28
|
+
"coffeescripts/I18n.coffee",
|
29
|
+
"javascripts/sprintf-0.7-beta1.js",
|
30
|
+
"lib/ci-18n.rb",
|
31
|
+
"lib/ci18n.rb",
|
32
|
+
"spec/coffeescripts/autoloadSpec.coffee",
|
33
|
+
"spec/coffeescripts/helpers/SpecHelper.coffee",
|
34
|
+
"spec/coffeescripts/languageRepositorySpec.coffee",
|
35
|
+
"spec/coffeescripts/localizeSpec.coffee",
|
36
|
+
"spec/coffeescripts/translateSpec.coffee",
|
37
|
+
"spec/coffeescripts/utilsSpec.coffee",
|
38
|
+
"spec/javascripts/support/jasmine.yml",
|
39
|
+
"spec/javascripts/support/jasmine_config.rb",
|
40
|
+
"spec/javascripts/support/jasmine_runner.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/mcollina/ci-18n}
|
43
|
+
s.licenses = ["MIT"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.5.3}
|
46
|
+
s.summary = %q{A localization library for javascript files in ruby on rails.}
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
53
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<coffee-script>, ["~> 2.2.0"])
|
55
|
+
s.add_development_dependency(%q<jasmine>, ["~> 1.0"])
|
56
|
+
s.add_development_dependency(%q<guard>, ["~> 0.3.1"])
|
57
|
+
s.add_development_dependency(%q<guard-coffeescript>, ["~> 0.2.0"])
|
58
|
+
s.add_development_dependency(%q<guard-livereload>, ["~> 0.1.9"])
|
59
|
+
s.add_development_dependency(%q<rake-minify>, ["~> 0.3"])
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
63
|
+
s.add_dependency(%q<coffee-script>, ["~> 2.2.0"])
|
64
|
+
s.add_dependency(%q<jasmine>, ["~> 1.0"])
|
65
|
+
s.add_dependency(%q<guard>, ["~> 0.3.1"])
|
66
|
+
s.add_dependency(%q<guard-coffeescript>, ["~> 0.2.0"])
|
67
|
+
s.add_dependency(%q<guard-livereload>, ["~> 0.1.9"])
|
68
|
+
s.add_dependency(%q<rake-minify>, ["~> 0.3"])
|
69
|
+
end
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
72
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
73
|
+
s.add_dependency(%q<coffee-script>, ["~> 2.2.0"])
|
74
|
+
s.add_dependency(%q<jasmine>, ["~> 1.0"])
|
75
|
+
s.add_dependency(%q<guard>, ["~> 0.3.1"])
|
76
|
+
s.add_dependency(%q<guard-coffeescript>, ["~> 0.2.0"])
|
77
|
+
s.add_dependency(%q<guard-livereload>, ["~> 0.1.9"])
|
78
|
+
s.add_dependency(%q<rake-minify>, ["~> 0.3"])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,220 @@
|
|
1
|
+
|
2
|
+
class I18n
|
3
|
+
|
4
|
+
# the first parameter is the main locale object
|
5
|
+
# while the second is the fallback (default)
|
6
|
+
constructor: (currentLocale = {}, defaultLocale = undefined)->
|
7
|
+
if typeof currentLocale == 'string'
|
8
|
+
@localeString = currentLocale
|
9
|
+
else
|
10
|
+
@localeVal = currentLocale
|
11
|
+
|
12
|
+
if typeof defaultLocale == 'string'
|
13
|
+
@defaultString = defaultLocale
|
14
|
+
else if not defaultLocale?
|
15
|
+
@defaultString = I18n.getDefaultLanguage()
|
16
|
+
else
|
17
|
+
@defaultVal = defaultLocale || {}
|
18
|
+
|
19
|
+
locale: ->
|
20
|
+
@localeVal ||= I18n.language(@localeString)
|
21
|
+
|
22
|
+
defaultLocale: ->
|
23
|
+
@defaultVal ||= I18n.language(@defaultString)
|
24
|
+
|
25
|
+
|
26
|
+
# this is a private function
|
27
|
+
innerLookup = (locale, keywordList) ->
|
28
|
+
for keyword in keywordList
|
29
|
+
break unless locale?
|
30
|
+
locale = locale[keyword]
|
31
|
+
locale
|
32
|
+
|
33
|
+
translate: (keywordList, options = {}) ->
|
34
|
+
keywordList = I18n.normalizeKeys(keywordList, options)
|
35
|
+
lookup = innerLookup(this.locale(), keywordList) || options.default || innerLookup(this.defaultLocale(), keywordList)
|
36
|
+
|
37
|
+
# the scope is used by normalizeKeys, but it will be
|
38
|
+
# interpreted as a keyword placeholder by I18n.interpolate
|
39
|
+
delete options.scope
|
40
|
+
|
41
|
+
# the default is used here, but it will be interpreted
|
42
|
+
# as a keyword placeholder by I18n.interpolate
|
43
|
+
delete options.default
|
44
|
+
|
45
|
+
I18n.interpolate(lookup, options)
|
46
|
+
|
47
|
+
localize: (date, options) ->
|
48
|
+
throw "Argument Error: #{date} is not localizable" unless date instanceof Date
|
49
|
+
regexp = /%([a-z]|%)/ig
|
50
|
+
string = options.format
|
51
|
+
matches = string.match(regexp)
|
52
|
+
unless matches?
|
53
|
+
throw "Argument Error: missing type" unless options.type?
|
54
|
+
options.type = "time" if options.type == "datetime"
|
55
|
+
string = this.translate("#{options.type}.formats.#{options.format}")
|
56
|
+
matches = string.match(regexp) if string?
|
57
|
+
|
58
|
+
throw "Argument Error: no such format" unless matches?
|
59
|
+
|
60
|
+
for match in matches
|
61
|
+
match = match.slice(-1)
|
62
|
+
replacement_builder = I18n.strftime[match]
|
63
|
+
string = string.replace("%#{match}", replacement_builder(date, this)) if replacement_builder?
|
64
|
+
string
|
65
|
+
|
66
|
+
t: this::translate # coffeescript syntax to alias a method
|
67
|
+
l: this::localize # coffeescript syntax to alias a method
|
68
|
+
|
69
|
+
# extract an array of keys from the dot separated string
|
70
|
+
I18n.normalizeKeys = (keywords = [], options = { scope: [] }) ->
|
71
|
+
return keywords if keywords instanceof Array
|
72
|
+
|
73
|
+
splitted_keywords = []
|
74
|
+
for keyword in keywords.split(".")
|
75
|
+
# it will be much better if we could just use filter
|
76
|
+
splitted_keywords.push(keyword) if keyword? and keyword != ''
|
77
|
+
I18n.normalizeKeys(options.scope).concat(splitted_keywords)
|
78
|
+
|
79
|
+
# interpolate function wrapper
|
80
|
+
( ->
|
81
|
+
interpolate_basic = (string, option, value) ->
|
82
|
+
new_string = string.replace(///%{#{option}}///g, value)
|
83
|
+
return undefined if string == new_string
|
84
|
+
new_string
|
85
|
+
|
86
|
+
interpolate_sprintf = (string, option, value) ->
|
87
|
+
# this regexp was taken from https://github.com/svenfuchs/i18n/blob/master/lib/i18n/interpolate/ruby.rb
|
88
|
+
regexp = ///%<#{option}>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/// # matches placeholders like "%<foo>.d"
|
89
|
+
match = string.match(regexp)
|
90
|
+
return undefined unless match?
|
91
|
+
|
92
|
+
result = sprintf("%(keyword)#{match[1]}", keyword: value)
|
93
|
+
string.replace(match[0], result)
|
94
|
+
|
95
|
+
I18n.interpolate = (string, options = {}) ->
|
96
|
+
return string if not string?
|
97
|
+
for option, value of options
|
98
|
+
new_string = interpolate_basic(string, option, value)
|
99
|
+
new_string ||= interpolate_sprintf(string, option, value)
|
100
|
+
unless new_string?
|
101
|
+
throw new Error("Missing placeholder for keyword \"#{option}\"")
|
102
|
+
string = new_string
|
103
|
+
string
|
104
|
+
)()
|
105
|
+
|
106
|
+
I18n.strftime = {
|
107
|
+
'd': (date) ->
|
108
|
+
('0' + date.getDate()).slice(-2)
|
109
|
+
|
110
|
+
'b': (date, i18n) ->
|
111
|
+
i18n.t("date.abbr_month_names")[date.getMonth()]
|
112
|
+
|
113
|
+
'B': (date, i18n) ->
|
114
|
+
i18n.t("date.month_names")[date.getMonth()]
|
115
|
+
|
116
|
+
'a': (date, i18n) ->
|
117
|
+
i18n.t("date.abbr_day_names")[date.getDay()]
|
118
|
+
|
119
|
+
'A': (date, i18n) ->
|
120
|
+
i18n.t("date.day_names")[date.getDay()]
|
121
|
+
|
122
|
+
'Y': (date) ->
|
123
|
+
date.getFullYear()
|
124
|
+
|
125
|
+
'm': (date) ->
|
126
|
+
('0'+(date.getMonth() + 1)).slice(-2)
|
127
|
+
|
128
|
+
'H': (date) ->
|
129
|
+
('0'+(date.getHours())).slice(-2)
|
130
|
+
|
131
|
+
'M': (date) ->
|
132
|
+
('0'+(date.getMinutes())).slice(-2)
|
133
|
+
|
134
|
+
'S': (date) ->
|
135
|
+
('0'+(date.getSeconds())).slice(-2)
|
136
|
+
|
137
|
+
'z': (date) ->
|
138
|
+
tz_offset = date.getTimezoneOffset()
|
139
|
+
(tz_offset > 0 and '-' or '+') + ('0' + (tz_offset / 60)).slice(-2) + ('0' + (tz_offset % 60)).slice(-2)
|
140
|
+
|
141
|
+
'p': (date, i18n) ->
|
142
|
+
i18n.t("time")[date.getHours() >= 12 and 'pm' or 'am']
|
143
|
+
|
144
|
+
'e': (date) ->
|
145
|
+
date.getDate()
|
146
|
+
|
147
|
+
'I': (date) ->
|
148
|
+
('0'+(date.getHours() % 12)).slice(-2)
|
149
|
+
|
150
|
+
'j': (date) ->
|
151
|
+
(((date.getTime() - new Date("Jan 1 " + date.getFullYear()).getTime()) / (1000 * 60 * 60 * 24) + 1) + '').split(/\./)[0]
|
152
|
+
|
153
|
+
'k': (date) ->
|
154
|
+
date.getHours()
|
155
|
+
|
156
|
+
'l': (date) ->
|
157
|
+
date.getHours() % 12
|
158
|
+
|
159
|
+
'w': (date) ->
|
160
|
+
date.getDay()
|
161
|
+
|
162
|
+
'y': (date) ->
|
163
|
+
"#{date.getYear()}".slice(-2)
|
164
|
+
|
165
|
+
'%': -> '%'
|
166
|
+
}
|
167
|
+
|
168
|
+
(->
|
169
|
+
languages = {}
|
170
|
+
defaultLanguage = undefined
|
171
|
+
|
172
|
+
I18n.addLanguage = (name, lang) ->
|
173
|
+
languages[name] = lang
|
174
|
+
|
175
|
+
I18n.clearLanguages = ->
|
176
|
+
languages = {}
|
177
|
+
|
178
|
+
I18n.language = (name) ->
|
179
|
+
languages[name]
|
180
|
+
|
181
|
+
I18n.setDefaultLanguage = (name) ->
|
182
|
+
defaultLanguage = name
|
183
|
+
|
184
|
+
I18n.getDefaultLanguage = ->
|
185
|
+
defaultLanguage
|
186
|
+
)()
|
187
|
+
|
188
|
+
I18n.load = (path, lang) ->
|
189
|
+
url = "#{path}/#{lang}.js" # url of the external script
|
190
|
+
|
191
|
+
# dynamic script insertion
|
192
|
+
script = document.createElement('script')
|
193
|
+
script.setAttribute('src', url)
|
194
|
+
|
195
|
+
# load the script
|
196
|
+
document.getElementsByTagName('head')[0].appendChild(script);
|
197
|
+
|
198
|
+
I18n.detectLanguage = (navigator) ->
|
199
|
+
for name in ["language", "browserLanguage"]
|
200
|
+
return navigator[name] if navigator[name]?
|
201
|
+
|
202
|
+
I18n.autoloadAndSetup = (options) ->
|
203
|
+
options.language = I18n.detectLanguage(window.navigator) unless options.language?
|
204
|
+
langsToLoad = [options.language]
|
205
|
+
langsToLoad.push(options.default) if options.default?
|
206
|
+
|
207
|
+
for lang in langsToLoad
|
208
|
+
I18n.load(options.path, lang)
|
209
|
+
|
210
|
+
I18n.setup(options.language, options.default)
|
211
|
+
|
212
|
+
I18n.setup = (locale, defaultLocale) ->
|
213
|
+
window.$i18n = new I18n(locale, defaultLocale)
|
214
|
+
|
215
|
+
I18n.autosetup = (defaultLocale) ->
|
216
|
+
locale = I18n.detectLanguage(window.navigator)
|
217
|
+
I18n.setup(locale, defaultLocale)
|
218
|
+
|
219
|
+
# export I18n object to the world!
|
220
|
+
window.I18n = I18n
|
@@ -0,0 +1,183 @@
|
|
1
|
+
/**
|
2
|
+
sprintf() for JavaScript 0.7-beta1
|
3
|
+
http://www.diveintojavascript.com/projects/javascript-sprintf
|
4
|
+
|
5
|
+
Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
|
6
|
+
All rights reserved.
|
7
|
+
|
8
|
+
Redistribution and use in source and binary forms, with or without
|
9
|
+
modification, are permitted provided that the following conditions are met:
|
10
|
+
* Redistributions of source code must retain the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer.
|
12
|
+
* Redistributions in binary form must reproduce the above copyright
|
13
|
+
notice, this list of conditions and the following disclaimer in the
|
14
|
+
documentation and/or other materials provided with the distribution.
|
15
|
+
* Neither the name of sprintf() for JavaScript nor the
|
16
|
+
names of its contributors may be used to endorse or promote products
|
17
|
+
derived from this software without specific prior written permission.
|
18
|
+
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
20
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
21
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
|
23
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
24
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
25
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
26
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
27
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
28
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
|
30
|
+
|
31
|
+
Changelog:
|
32
|
+
2010.09.06 - 0.7-beta1
|
33
|
+
- features: vsprintf, support for named placeholders
|
34
|
+
- enhancements: format cache, reduced global namespace pollution
|
35
|
+
|
36
|
+
2010.05.22 - 0.6:
|
37
|
+
- reverted to 0.4 and fixed the bug regarding the sign of the number 0
|
38
|
+
Note:
|
39
|
+
Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
|
40
|
+
who warned me about a bug in 0.5, I discovered that the last update was
|
41
|
+
a regress. I appologize for that.
|
42
|
+
|
43
|
+
2010.05.09 - 0.5:
|
44
|
+
- bug fix: 0 is now preceeded with a + sign
|
45
|
+
- bug fix: the sign was not at the right position on padded results (Kamal Abdali)
|
46
|
+
- switched from GPL to BSD license
|
47
|
+
|
48
|
+
2007.10.21 - 0.4:
|
49
|
+
- unit test and patch (David Baird)
|
50
|
+
|
51
|
+
2007.09.17 - 0.3:
|
52
|
+
- bug fix: no longer throws exception on empty paramenters (Hans Pufal)
|
53
|
+
|
54
|
+
2007.09.11 - 0.2:
|
55
|
+
- feature: added argument swapping
|
56
|
+
|
57
|
+
2007.04.03 - 0.1:
|
58
|
+
- initial release
|
59
|
+
**/
|
60
|
+
|
61
|
+
var sprintf = (function() {
|
62
|
+
function get_type(variable) {
|
63
|
+
return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
|
64
|
+
}
|
65
|
+
function str_repeat(input, multiplier) {
|
66
|
+
for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
|
67
|
+
return output.join('');
|
68
|
+
}
|
69
|
+
|
70
|
+
var str_format = function() {
|
71
|
+
if (!str_format.cache.hasOwnProperty(arguments[0])) {
|
72
|
+
str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
|
73
|
+
}
|
74
|
+
return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
|
75
|
+
};
|
76
|
+
|
77
|
+
str_format.format = function(parse_tree, argv) {
|
78
|
+
var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
|
79
|
+
for (i = 0; i < tree_length; i++) {
|
80
|
+
node_type = get_type(parse_tree[i]);
|
81
|
+
if (node_type === 'string') {
|
82
|
+
output.push(parse_tree[i]);
|
83
|
+
}
|
84
|
+
else if (node_type === 'array') {
|
85
|
+
match = parse_tree[i]; // convenience purposes only
|
86
|
+
if (match[2]) { // keyword argument
|
87
|
+
arg = argv[cursor];
|
88
|
+
for (k = 0; k < match[2].length; k++) {
|
89
|
+
if (!arg.hasOwnProperty(match[2][k])) {
|
90
|
+
throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
|
91
|
+
}
|
92
|
+
arg = arg[match[2][k]];
|
93
|
+
}
|
94
|
+
}
|
95
|
+
else if (match[1]) { // positional argument (explicit)
|
96
|
+
arg = argv[match[1]];
|
97
|
+
}
|
98
|
+
else { // positional argument (implicit)
|
99
|
+
arg = argv[cursor++];
|
100
|
+
}
|
101
|
+
|
102
|
+
if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
|
103
|
+
throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
|
104
|
+
}
|
105
|
+
switch (match[8]) {
|
106
|
+
case 'b': arg = arg.toString(2); break;
|
107
|
+
case 'c': arg = String.fromCharCode(arg); break;
|
108
|
+
case 'd': arg = parseInt(arg, 10); break;
|
109
|
+
case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
|
110
|
+
case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
|
111
|
+
case 'o': arg = arg.toString(8); break;
|
112
|
+
case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
|
113
|
+
case 'u': arg = Math.abs(arg); break;
|
114
|
+
case 'x': arg = arg.toString(16); break;
|
115
|
+
case 'X': arg = arg.toString(16).toUpperCase(); break;
|
116
|
+
}
|
117
|
+
arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
|
118
|
+
pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
|
119
|
+
pad_length = match[6] - String(arg).length;
|
120
|
+
pad = match[6] ? str_repeat(pad_character, pad_length) : '';
|
121
|
+
output.push(match[5] ? arg + pad : pad + arg);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
return output.join('');
|
125
|
+
};
|
126
|
+
|
127
|
+
str_format.cache = {};
|
128
|
+
|
129
|
+
str_format.parse = function(fmt) {
|
130
|
+
var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
|
131
|
+
while (_fmt) {
|
132
|
+
if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
|
133
|
+
parse_tree.push(match[0]);
|
134
|
+
}
|
135
|
+
else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
|
136
|
+
parse_tree.push('%');
|
137
|
+
}
|
138
|
+
else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
|
139
|
+
if (match[2]) {
|
140
|
+
arg_names |= 1;
|
141
|
+
var field_list = [], replacement_field = match[2], field_match = [];
|
142
|
+
if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
|
143
|
+
field_list.push(field_match[1]);
|
144
|
+
while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
|
145
|
+
if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
|
146
|
+
field_list.push(field_match[1]);
|
147
|
+
}
|
148
|
+
else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
|
149
|
+
field_list.push(field_match[1]);
|
150
|
+
}
|
151
|
+
else {
|
152
|
+
throw('[sprintf] huh?');
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
else {
|
157
|
+
throw('[sprintf] huh?');
|
158
|
+
}
|
159
|
+
match[2] = field_list;
|
160
|
+
}
|
161
|
+
else {
|
162
|
+
arg_names |= 2;
|
163
|
+
}
|
164
|
+
if (arg_names === 3) {
|
165
|
+
throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
|
166
|
+
}
|
167
|
+
parse_tree.push(match);
|
168
|
+
}
|
169
|
+
else {
|
170
|
+
throw('[sprintf] huh?');
|
171
|
+
}
|
172
|
+
_fmt = _fmt.substring(match[0].length);
|
173
|
+
}
|
174
|
+
return parse_tree;
|
175
|
+
};
|
176
|
+
|
177
|
+
return str_format;
|
178
|
+
})();
|
179
|
+
|
180
|
+
var vsprintf = function(fmt, argv) {
|
181
|
+
argv.unshift(fmt);
|
182
|
+
return sprintf.apply(null, argv);
|
183
|
+
};
|