moonwalkair 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +43 -0
  6. data/VERSION.yml +5 -0
  7. data/bin/moonwalk +5 -0
  8. data/lib/initializer.rb +40 -0
  9. data/lib/moonwalkair/generator/application.rb +36 -0
  10. data/lib/moonwalkair/generator/options.rb +47 -0
  11. data/lib/moonwalkair/generator.rb +125 -0
  12. data/lib/moonwalkair/templates/LICENSE +20 -0
  13. data/lib/moonwalkair/templates/README +7 -0
  14. data/lib/moonwalkair/templates/app/assets/css/jqtouch.css +373 -0
  15. data/lib/moonwalkair/templates/app/assets/css/theme.css +677 -0
  16. data/lib/moonwalkair/templates/app/assets/images/apple/backButton.png +0 -0
  17. data/lib/moonwalkair/templates/app/assets/images/apple/blueButton.png +0 -0
  18. data/lib/moonwalkair/templates/app/assets/images/apple/cancel.png +0 -0
  19. data/lib/moonwalkair/templates/app/assets/images/apple/chevron.png +0 -0
  20. data/lib/moonwalkair/templates/app/assets/images/apple/grayButton.png +0 -0
  21. data/lib/moonwalkair/templates/app/assets/images/apple/listArrowSel.png +0 -0
  22. data/lib/moonwalkair/templates/app/assets/images/apple/listGroup.png +0 -0
  23. data/lib/moonwalkair/templates/app/assets/images/apple/loading.gif +0 -0
  24. data/lib/moonwalkair/templates/app/assets/images/apple/on_off.png +0 -0
  25. data/lib/moonwalkair/templates/app/assets/images/apple/pinstripes.png +0 -0
  26. data/lib/moonwalkair/templates/app/assets/images/apple/selection.png +0 -0
  27. data/lib/moonwalkair/templates/app/assets/images/apple/thumb.png +0 -0
  28. data/lib/moonwalkair/templates/app/assets/images/apple/toggle.png +0 -0
  29. data/lib/moonwalkair/templates/app/assets/images/apple/toggleOn.png +0 -0
  30. data/lib/moonwalkair/templates/app/assets/images/apple/toolButton.png +0 -0
  31. data/lib/moonwalkair/templates/app/assets/images/apple/toolbar.png +0 -0
  32. data/lib/moonwalkair/templates/app/assets/images/apple/whiteButton.png +0 -0
  33. data/lib/moonwalkair/templates/app/assets/images/icons/128.png +0 -0
  34. data/lib/moonwalkair/templates/app/assets/images/icons/16.png +0 -0
  35. data/lib/moonwalkair/templates/app/assets/images/icons/32.png +0 -0
  36. data/lib/moonwalkair/templates/app/assets/images/icons/48.png +0 -0
  37. data/lib/moonwalkair/templates/app/lib/air/AIRAliases.js +233 -0
  38. data/lib/moonwalkair/templates/app/lib/air/AIRIntrospector.js +1949 -0
  39. data/lib/moonwalkair/templates/app/lib/air/AIRLocalizer.js +1325 -0
  40. data/lib/moonwalkair/templates/app/lib/air/AIRMenuBuilder.js +1322 -0
  41. data/lib/moonwalkair/templates/app/lib/air/AIRSourceViewer.js +2933 -0
  42. data/lib/moonwalkair/templates/app/lib/air/airglobal.abc +0 -0
  43. data/lib/moonwalkair/templates/app/lib/air/applicationupdater.swf +0 -0
  44. data/lib/moonwalkair/templates/app/lib/air/applicationupdater_ui.swf +0 -0
  45. data/lib/moonwalkair/templates/app/lib/air/servicemonitor.swf +0 -0
  46. data/lib/moonwalkair/templates/app/lib/jquery.js +4376 -0
  47. data/lib/moonwalkair/templates/app/lib/plugins/jqtouch/jqtouch.js +634 -0
  48. data/lib/moonwalkair/templates/app/lib/plugins/jqtouch/jqtouch.transitions.js +60 -0
  49. data/lib/moonwalkair/templates/app/views/index.html +19 -0
  50. data/lib/moonwalkair/templates/config/boot.rb +15 -0
  51. data/lib/moonwalkair/templates/config/build.yml +9 -0
  52. data/lib/moonwalkair/templates/config/config.yml +2 -0
  53. data/lib/moonwalkair/templates/config/update_config.xml +5 -0
  54. data/lib/moonwalkair/templates/descriptor.xml +122 -0
  55. data/lib/moonwalkair/templates/script/build +51 -0
  56. data/lib/moonwalkair/templates/script/certificate +26 -0
  57. data/lib/moonwalkair/templates/script/run +38 -0
  58. data/lib/moonwalkair.rb +15 -0
  59. data/moonwalkair.gemspec +108 -0
  60. data/spec/moonwalkair_spec.rb +7 -0
  61. data/spec/spec.opts +1 -0
  62. data/spec/spec_helper.rb +9 -0
  63. metadata +127 -0
@@ -0,0 +1,1325 @@
1
+ /* AIRLocalizer.js - Revision: 1.5 */
2
+
3
+ /*
4
+ ADOBE SYSTEMS INCORPORATED
5
+ Copyright 2007-2008 Adobe Systems Incorporated. All Rights Reserved.
6
+
7
+ NOTICE: Adobe permits you to modify and distribute this file only in accordance with
8
+ the terms of Adobe AIR SDK license agreement. You may have received this file from a
9
+ source other than Adobe. Nonetheless, you may modify or
10
+ distribute this file only in accordance with such agreement.
11
+ */
12
+
13
+
14
+ // Using the function method to obfuscate inner globals to the main script, so that the
15
+ // "window" remains untouched.
16
+ (function(){
17
+ /**
18
+ * This is automatically replaced while building
19
+ * @version "1.5"
20
+ * @private
21
+ */
22
+ var version = "1.5";
23
+
24
+
25
+ /**
26
+ * Just to make sure we have the right shortcuts included
27
+ * @namespace window.air
28
+ * @private
29
+ */
30
+ var air = {
31
+ File : window.runtime.flash.filesystem.File,
32
+ FileStream : window.runtime.flash.filesystem.FileStream,
33
+ FileMode : window.runtime.flash.filesystem.FileMode,
34
+ Capabilities : window.runtime.flash.system.Capabilities
35
+ };
36
+
37
+ /**
38
+ * Utility functions
39
+ * @namespace util
40
+ * @private
41
+ */
42
+ var util = {
43
+ /**
44
+ * Returns the content of a file
45
+ * @param file air.File File that should be read
46
+ * @return string
47
+ */
48
+ stringFromFile : function stringFromFile(file){
49
+ var fileStream = new air.FileStream();
50
+ fileStream.open(file, air.FileMode.READ);
51
+ var result = fileStream.readUTFBytes(file.size);
52
+ fileStream.close();
53
+ return result;
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Used for sorting the locales array
59
+ * @private
60
+ */
61
+
62
+ function LocaleId(){
63
+ this.lang = '';
64
+ this.script = '';
65
+ this.region = '';
66
+ this.extended_langs = [];
67
+ this.variants = [];
68
+ this.extensions = {};
69
+ this.privates = [];
70
+ this.privateLangs = false;
71
+ };
72
+
73
+
74
+ LocaleId.prototype = {
75
+ transformToParent: function(){
76
+ if(this.privates.length>0){
77
+ this.privates.splice(this.privates.length-1, 1);
78
+ return true;
79
+ }
80
+
81
+ var lastExtensionName = null;
82
+ for(var i in this.extensions){
83
+ if(this.extensions.hasOwnProperty(i)){
84
+ lastExtensionName = i;
85
+ }
86
+ }
87
+ if(lastExtensionName){
88
+ var lastExtension = this.extensions[lastExtensionName];
89
+ if(lastExtension.length==1){
90
+ delete this.extensions[ lastExtensionName ];
91
+ return true;
92
+ }
93
+ lastExtension.splice(lastExtension.length-1, 1);
94
+ return true;
95
+ }
96
+
97
+ if(this.variants.length>0){
98
+ this.variants.splice(this.variants.length-1, 1);
99
+ return true;
100
+ }
101
+
102
+ if(this.script!=''){
103
+ //check if we can surpress the script
104
+ if(LocaleId.getScriptByLang(this.lang)!=''){
105
+ this.script='';
106
+ return true;
107
+ }else if(this.region==''){
108
+ //maybe the default region can surpress the script
109
+ var region = LocaleId.getDefaultRegionForLangAndScript(this.lang, this.script);
110
+ if(region!=''){
111
+ this.region = region;
112
+ this.script = '';
113
+ return true;
114
+ }
115
+ }
116
+ }
117
+
118
+ if(this.region!=''){
119
+ if(!(this.script=='' && LocaleId.getScriptByLang(this.lang) == '')){
120
+ this.region='';
121
+ return true;
122
+ }
123
+ }
124
+
125
+
126
+ if(this.extended_langs.length>0){
127
+ this.extended_langs.splice(this.extended_langs.length-1, 1);
128
+ return true;
129
+ }
130
+
131
+ return false;
132
+ },
133
+
134
+ isSiblingOf: function(other){
135
+ if(this.lang==other.lang&&this.script==other.script){
136
+ return true;
137
+ }
138
+ return false;
139
+ },
140
+
141
+ equals: function(locale){
142
+ return this.toString() == locale.toString();
143
+ },
144
+ toString: function(){
145
+ var stack = [ this.lang ];
146
+ Array.prototype.push.apply(stack, this.extended_langs);
147
+ if(this.script!='') stack.push(this.script);
148
+ if(this.region!='') stack.push(this.region);
149
+ Array.prototype.push.apply(stack, this.variants);
150
+ for(var i in this.extensions){
151
+ if(this.extensions.hasOwnProperty(i)){
152
+ stack.push(i);
153
+ Array.prototype.push.apply(stack, this.extensions[i]);
154
+ }
155
+ }
156
+ if(this.privates.length>0){
157
+ stack.push('x');
158
+ Array.prototype.push.apply(stack, this.privates);
159
+ }
160
+ return stack.join('_');
161
+ },
162
+ canonicalize: function(){
163
+ for(var i in this.extensions){
164
+ if(this.extensions.hasOwnProperty(i)){
165
+ //also clear zero length extensions
166
+ if(this.extensions[i].length==0) delete this.extensions[i];
167
+ else this.extensions[i] = this.extensions[i].sort();
168
+ }
169
+ }
170
+ this.extended_langs = this.extended_langs.sort();
171
+ this.variants = this.variants.sort();
172
+ this.privates = this.privates.sort();
173
+ if(this.script == ''){
174
+ this.script = LocaleId.getScriptByLang(this.lang);
175
+ }
176
+ //still no script, check the region
177
+ if(this.script == '' && this.region!=''){
178
+ this.script = LocaleId.getScriptByLangAndRegion(this.lang, this.region);
179
+ }
180
+
181
+ if(this.region=='' && this.script!=''){
182
+ this.region = LocaleId.getDefaultRegionForLangAndScript(this.lang, this.script);
183
+ }
184
+ }
185
+ }
186
+
187
+ LocaleId.registry = {
188
+ "scripts": ["cyrl", "latn", "ethi", "arab", "beng", "cyrl", "thaa", "tibt", "grek", "gujr", "hebr", "deva", "armn", "jpan", "geor", "khmr", "knda", "kore", "laoo", "mlym", "mymr", "orya", "guru", "sinh", "taml", "telu", "thai", "nkoo", "blis", "hans", "hant", "mong", "syrc"],
189
+ "scriptById": {"cyrl": 5, "latn": 1, "ethi": 2, "arab": 3, "beng": 4, "thaa": 6, "tibt": 7, "grek": 8, "gujr": 9, "hebr": 10, "deva": 11, "armn": 12, "jpan": 13, "geor": 14, "khmr": 15, "knda": 16, "kore": 17, "laoo": 18, "mlym": 19, "mymr": 20, "orya": 21, "guru": 22, "sinh": 23, "taml": 24, "telu": 25, "thai": 26, "nkoo": 27, "blis": 28, "hans": 29, "hant": 30, "mong": 31, "syrc": 32} ,
190
+ "defaultRegionByLangAndScript": {"bg": {5: "bg"}, "ca": {1: "es"}, "zh": {30: "tw", 29: "cn"}, "cs": {1: "cz"}, "da": {1: "dk"}, "de": {1: "de"}, "el": {8: "gr"}, "en": {1: "us"}, "es": {1: "es"}, "fi": {1: "fi"}, "fr": {1: "fr"}, "he": {10: "il"}, "hu": {1: "hu"}, "is": {1: "is"}, "it": {1: "it"}, "ja": {13: "jp"}, "ko": {17: "kr"}, "nl": {1: "nl"}, "nb": {1: "no"}, "pl": {1: "pl"}, "pt": {1: "br"}, "ro": {1: "ro"}, "ru": {5: "ru"}, "hr": {1: "hr"}, "sk": {1: "sk"}, "sq": {1: "al"}, "sv": {1: "se"}, "th": {26: "th"}, "tr": {1: "tr"}, "ur": {3: "pk"}, "id": {1: "id"}, "uk": {5: "ua"}, "be": {5: "by"}, "sl": {1: "si"}, "et": {1: "ee"}, "lv": {1: "lv"}, "lt": {1: "lt"}, "fa": {3: "ir"}, "vi": {1: "vn"}, "hy": {12: "am"}, "az": {1: "az", 5: "az"}, "eu": {1: "es"}, "mk": {5: "mk"}, "af": {1: "za"}, "ka": {14: "ge"}, "fo": {1: "fo"}, "hi": {11: "in"}, "ms": {1: "my"}, "kk": {5: "kz"}, "ky": {5: "kg"}, "sw": {1: "ke"}, "uz": {1: "uz", 5: "uz"}, "tt": {5: "ru"}, "pa": {22: "in"}, "gu": {9: "in"}, "ta": {24: "in"}, "te": {25: "in"}, "kn": {16: "in"}, "mr": {11: "in"}, "sa": {11: "in"}, "mn": {5: "mn"}, "gl": {1: "es"}, "kok": {11: "in"}, "syr": {32: "sy"}, "dv": {6: "mv"}, "nn": {1: "no"}, "sr": {1: "cs", 5: "cs"}, "cy": {1: "gb"}, "mi": {1: "nz"}, "mt": {1: "mt"}, "quz": {1: "bo"}, "tn": {1: "za"}, "xh": {1: "za"}, "zu": {1: "za"}, "nso": {1: "za"}, "se": {1: "no"}, "smj": {1: "no"}, "sma": {1: "no"}, "sms": {1: "fi"}, "smn": {1: "fi"}, "bs": {1: "ba"}},
191
+ "scriptIdByLang": {"ab": 0, "af": 1, "am": 2, "ar": 3, "as": 4, "ay": 1, "be": 5, "bg": 5, "bn": 4, "bs": 1, "ca": 1, "ch": 1, "cs": 1, "cy": 1, "da": 1, "de": 1, "dv": 6, "dz": 7, "el": 8, "en": 1, "eo": 1, "es": 1, "et": 1, "eu": 1, "fa": 3, "fi": 1, "fj": 1, "fo": 1, "fr": 1, "frr": 1, "fy": 1, "ga": 1, "gl": 1, "gn": 1, "gu": 9, "gv": 1, "he": 10, "hi": 11, "hr": 1, "ht": 1, "hu": 1, "hy": 12, "id": 1, "in": 1, "is": 1, "it": 1, "iw": 10, "ja": 13, "ka": 14, "kk": 5, "kl": 1, "km": 15, "kn": 16, "ko": 17, "la": 1, "lb": 1, "ln": 1, "lo": 18, "lt": 1, "lv": 1, "mg": 1, "mh": 1, "mk": 5, "ml": 19, "mo": 1, "mr": 11, "ms": 1, "mt": 1, "my": 20, "na": 1, "nb": 1, "nd": 1, "ne": 11, "nl": 1, "nn": 1, "no": 1, "nr": 1, "ny": 1, "om": 1, "or": 21, "pa": 22, "pl": 1, "ps": 3, "pt": 1, "qu": 1, "rn": 1, "ro": 1, "ru": 5, "rw": 1, "sg": 1, "si": 23, "sk": 1, "sl": 1, "sm": 1, "so": 1, "sq": 1, "ss": 1, "st": 1, "sv": 1, "sw": 1, "ta": 24, "te": 25, "th": 26, "ti": 2, "tl": 1, "tn": 1, "to": 1, "tr": 1, "ts": 1, "uk": 5, "ur": 3, "ve": 1, "vi": 1, "wo": 1, "xh": 1, "yi": 10, "zu": 1, "cpe": 1, "dsb": 1, "frs": 1, "gsw": 1, "hsb": 1, "kok": 11, "mai": 11, "men": 1, "nds": 1, "niu": 1, "nqo": 27, "nso": 1, "son": 1, "tem": 1, "tkl": 1, "tmh": 1, "tpi": 1, "tvl": 1, "zbl": 28},
192
+ "scriptIdByLangAndRegion": {"zh": {"cn": 29, "sg": 29, "tw": 30, "hk": 30, "mo": 30}, "mn": {"cn": 31, "sg": 5}, "pa": {"pk": 3, "in": 22}, "ha": {"gh": 1, "ne": 1}}};
193
+
194
+
195
+ LocaleId.getScriptByLangAndRegion = function(lang, region){
196
+ var langRegions = LocaleId.registry.scriptIdByLangAndRegion[ lang ];
197
+ if(typeof langRegions=='undefined') return '';
198
+ var scriptId = langRegions[region];
199
+ if(typeof scriptId!='undefined'){
200
+ return LocaleId.registry.scripts[scriptId].toLowerCase();
201
+ }
202
+ return '';
203
+ }
204
+
205
+ LocaleId.getScriptByLang = function(lang){
206
+ var scriptId = LocaleId.registry.scriptIdByLang[ lang ];
207
+ if(typeof scriptId!='undefined'){
208
+ return LocaleId.registry.scripts[scriptId].toLowerCase();
209
+ }
210
+ return '';
211
+ }
212
+
213
+ LocaleId.getDefaultRegionForLangAndScript = function(lang, script){
214
+ var lang = LocaleId.registry.defaultRegionByLangAndScript[ lang ];
215
+ var scriptId = LocaleId.registry.scriptById[ script ];
216
+ if(typeof lang!='undefined' && typeof scriptId !='undefined' ){
217
+ return lang[scriptId] || "";
218
+ }
219
+ return '';
220
+ }
221
+
222
+
223
+ LocaleId.fromString = function(str){
224
+ //states
225
+ {
226
+ var PrimaryLanguage = 0,
227
+ ExtendedLanguages = 1,
228
+ Script = 2,
229
+ Region = 3,
230
+ Variants = 5,
231
+ Extensions = 6,
232
+ Privates = 7;
233
+ }
234
+ var localeId = new LocaleId();
235
+
236
+ var state = PrimaryLanguage;
237
+ var subtags = str.replace(/-/g, '_').split('_');
238
+
239
+ var last_extension;
240
+
241
+ for(var i=0, l=subtags.length; i<l ;i++){
242
+ var subtag = subtags[i].toLowerCase();
243
+
244
+ if(state==PrimaryLanguage){
245
+ if(subtag=='x'){
246
+ localeId.privateLangs = true; //not used in our implementation, but makes the tag private
247
+ }else if(subtag=='i'){
248
+ localeId.lang += 'i-'; //and wait the next subtag to complete the language name
249
+ }else{
250
+ localeId.lang += subtag;
251
+ state ++;
252
+ }
253
+ }else{
254
+ //looging for an extended language - 3 chars
255
+ // a script - 4 chars
256
+ // a region - 2-3 chars
257
+ // a variant - alpha with at least 5 chars or numeric with at least 4 chars
258
+ // an extension/private singleton - 1 char
259
+
260
+ var subtag_length = subtag.length; //store it for faster use later
261
+ if(subtag_length==0) continue; //skip zero-lengthed subtags
262
+ var firstChar = subtag[0].toLowerCase();
263
+
264
+ if(state<=ExtendedLanguages && subtag_length==3){
265
+ localeId.extended_langs.push(subtag);
266
+ if(localeId.extended_langs.length==3){ //allow a maximum of 3 extended langs
267
+ state = Script;
268
+ }
269
+ }else if ( state <= Script && subtag_length==4 ){
270
+ localeId.script = subtag;
271
+ state = Region;
272
+ }else if( state <= Region && (subtag_length==2 || subtag_length==3) ){
273
+ localeId.region = subtag;
274
+ state = Variants;
275
+ }else if ( state <= Variants &&
276
+ (
277
+ ( firstChar>='a' && firstChar<='z' && subtag_length>=5 )
278
+ ||
279
+ ( firstChar>='0' && firstChar<='9' && subtag_length>=4 )
280
+ )
281
+ ){
282
+ //variant
283
+ localeId.variants.push(subtag);
284
+ state = Variants;
285
+ }else if ( state < Privates && subtag_length==1 ){ //singleton
286
+ if(subtag == 'x'){
287
+ state = Privates;
288
+ last_extension = localeId.privates;
289
+ } else {
290
+ state = Extensions;
291
+ last_extension = localeId.extensions[subtag] || [];
292
+ localeId.extensions[subtag] = last_extension;
293
+ }
294
+ }else if(state >= Extensions){
295
+ last_extension.push(subtag);
296
+ }
297
+ }
298
+ }
299
+ localeId.canonicalize();
300
+ return localeId;
301
+ }
302
+
303
+ var LocaleSorter = {
304
+ /**
305
+ * Promote only that locales from preferenceLocales that have parents in locales
306
+ * @private
307
+ * @param locales String[] List of locales to be sorted.
308
+ * @param preferenceLocales String[] List of locales in the preference order
309
+ * @param addAll Adds all the locales at the end, even though no locale is in the preferences list. Default is true
310
+ * @return String[]
311
+ */
312
+ sortLocalesUsingPreferences: function(_locales, _preferenceLocales, ultimateFallbackLocale, addAll){
313
+ var result = [];
314
+ var haveLocale = {};
315
+
316
+ function prepare(list){
317
+ var resultList = [];
318
+ for(var i=0,l=list.length;i<l;i++) {
319
+ resultList.push (list[i].toLowerCase().replace(/-/g,'_'));
320
+ }
321
+ return resultList;
322
+ }
323
+
324
+ var locales = prepare(_locales);
325
+ var preferenceLocales = prepare(_preferenceLocales);
326
+
327
+ if(ultimateFallbackLocale&&ultimateFallbackLocale!=''){
328
+ var ultimateFallbackLocale = ultimateFallbackLocale.toLowerCase().replace(/-/g,'_');
329
+ var found = false;
330
+ for(var i=0, l=preferenceLocales.length; i<l; i++){
331
+ if(preferenceLocales[i]==ultimateFallbackLocale){
332
+ found = true;
333
+ }
334
+ }
335
+ if(!found){
336
+ preferenceLocales.push(ultimateFallbackLocale);
337
+ }
338
+ }
339
+
340
+ for(var j=0, k=locales.length; j<k; j++){
341
+ haveLocale[ locales[j] ] = j;
342
+ }
343
+
344
+ function promote(locale){
345
+ if(typeof haveLocale[locale]!='undefined'){
346
+ result.push( _locales[ haveLocale[locale] ] );
347
+ delete haveLocale[locale];
348
+ }
349
+ }
350
+
351
+
352
+
353
+ for(var i=0, l=preferenceLocales.length; i<l; i++){
354
+ var plocale = LocaleId.fromString( preferenceLocales[i] );
355
+
356
+ // step 1: promote the perfect match
357
+ promote(preferenceLocales[i]);
358
+
359
+ promote(plocale.toString());
360
+
361
+ // step 2: promote the parent chain
362
+ while(plocale.transformToParent()){
363
+ promote(plocale.toString());
364
+ }
365
+
366
+ //parse it again
367
+ plocale = LocaleId.fromString( preferenceLocales[i] );
368
+ // step 3: promote the order of siblings from preferenceLocales
369
+ for(var j=0; j<l; j++){
370
+ var locale = preferenceLocales[j];
371
+ if( plocale.isSiblingOf ( LocaleId.fromString( locale ) ) ){
372
+ promote(locale);
373
+ }
374
+ }
375
+ // step 4: promote all remaining siblings (aka not in preferenceLocales)
376
+ for(var j=0, k=locales.length; j<k; j++){
377
+ var locale = locales[j];
378
+ if( plocale.isSiblingOf( LocaleId.fromString( locale ) ) ){
379
+ promote( locale );
380
+ }
381
+ }
382
+
383
+ }
384
+ if(addAll){
385
+ // check what locales are not already loaded and push them.
386
+ // using the "for" because we want to preserve order
387
+ for(var j=0, k=locales.length; j<k; j++){
388
+ promote(locales[j]);
389
+ }
390
+ }
391
+ return result;
392
+ },
393
+ };
394
+
395
+ /**
396
+ * LocalizerEvent
397
+ * @constructor
398
+ * @private
399
+ */
400
+ function LocalizerEvent(name, config){
401
+ for(var prop in config){
402
+ this[prop] = config[prop];
403
+ }
404
+ this.name = name;
405
+ };
406
+
407
+ LocalizerEvent.prototype = {
408
+ toString: function(){
409
+ return "[LocalizerEvent: "+this.name+"]";
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Parses the property files and generates a collection of keys and values
415
+ * @constructor
416
+ * @private
417
+ */
418
+ function ResourceBundle(bundleFile){
419
+ this.keys = {};
420
+ try{
421
+ this.parse(util.stringFromFile(bundleFile));
422
+ this.found = true;
423
+ }catch(e){
424
+ // file not found
425
+ // die silently
426
+ this.found = false;
427
+ }
428
+ };
429
+
430
+ ResourceBundle.prototype = {
431
+ /**
432
+ * Parses the property file
433
+ * @param source Content of the property file
434
+ */
435
+ parse: function(source){
436
+ var source = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/^(?:\s*?)(?:!|#)([^\n\r]*)$/gm, '');
437
+ var rg = /^(?:[ \t\f]*)((?:\\=|\\:|\\\\|[^=:\s])+)(?:[ \t\f]*)(?:$|(?:[=: \t\f](?:[ \t\f]*)((?:[^\n\r]*(?:\\(?:\r\n|\n|\r))+)*[^\n\r]*)$))/gm;
438
+ var reformat = /\\(?:\r\n|\n|\r)(?:\s*)/g;
439
+ var decoder = /\\(.)/g;
440
+ var decoderCodes = { ':' : ':', '=':'=', 'n': "\n", 'r':"\r", 't':"\t", "\\":"\\" };
441
+ var decoderFn = function(r, a){ return decoderCodes.hasOwnProperty(a) ? decoderCodes[a] : r }
442
+ var line;
443
+ while( line = rg.exec(source) ){
444
+ var nodeName = line[1].replace(/\uFEFF/g, '').replace(decoder, decoderFn);
445
+ var value = line[2] || "";
446
+ this.keys[nodeName] = value.replace(reformat, "").replace(decoder, decoderFn);
447
+ }
448
+ },
449
+ /**
450
+ * Returns the value of a resource
451
+ * @param key string The key name of the resource. This is case sensitvie
452
+ * @return string
453
+ */
454
+ get: function(key){
455
+ return this.keys[key] || null;
456
+ }
457
+ };
458
+
459
+ /**
460
+ * Holds the content of a localized file
461
+ * @constructor
462
+ * @private
463
+ */
464
+ function ResourceFile(bundleFile){
465
+ try{
466
+ this.content = util.stringFromFile(bundleFile);
467
+ this.found = true;
468
+ }catch(e){
469
+ // file not found
470
+ // die silently
471
+ this.content = null;
472
+ this.found = false;
473
+ }
474
+ };
475
+
476
+ ResourceFile.prototype = {
477
+ /**
478
+ * Returns the content of the file
479
+ * @return string
480
+ */
481
+ getContent: function(){
482
+ return this.content;
483
+ }
484
+ };
485
+
486
+ /**
487
+ * Takes every node on the DOM and replaces attributes based on a prefix
488
+ * @param parent LocalizerPrivate The localizer object used to get the resource strings
489
+ * @constructor
490
+ * @private
491
+ */
492
+ function DOMWalker(parent){
493
+ this.parent = parent;
494
+ }
495
+
496
+ DOMWalker.prototype = {
497
+ /**
498
+ * Chechs if an attribute is a local attribute
499
+ * @param attr DOMAttribute The attribute that should be checked
500
+ * @return bool
501
+ */
502
+ isLocalAttribute: function(attr){
503
+ return attr.nodeName.toLowerCase().substr(0, this.attributePrefixLength)==this.attributePrefix;
504
+ },
505
+
506
+ /**
507
+ * Returns the attribute name that should be replace on a local attribute
508
+ * @param attr DOMAttribute The attribute that should be checked
509
+ * @return string
510
+ */
511
+ getLocalAttribute: function(attr){
512
+ return attr.nodeName.toLowerCase().substr(this.attributePrefixLength);
513
+ },
514
+
515
+ /**
516
+ * Replaces attributes on a single node
517
+ * @private
518
+ */
519
+ walkNode: function(node){
520
+ var attributePrefix = this.attributePrefix;
521
+ var attributeInnerPrefix = attributePrefix+'innerhtml';
522
+ if(!node.attributes) return;
523
+ var params = [];
524
+ var setInner = false;
525
+ var innerValue = null;
526
+ for(var i=node.attributes.length-1;i>=0;i--){
527
+ var attr = node.attributes[i];
528
+ if(attr.name.toLowerCase()==attributeInnerPrefix){
529
+ setInner = true;
530
+ innerValue = attr.value;
531
+ }else if(this.isLocalAttribute(attr)){
532
+ params.push([this.getLocalAttribute(attr), attr.value]);
533
+ }
534
+ }
535
+ if(setInner){
536
+ var value = this.getHtmlString(innerValue);
537
+ try{
538
+ node.innerHTML = value||innerValue;
539
+ }catch(e){
540
+ node.textContent = value||innerValue;
541
+ }
542
+ }
543
+ for(var i=params.length-1;i>=0;i--){
544
+ var param = params[i];
545
+ var value = this.getHtmlString(param[1]);
546
+ node.setAttribute(param[0], value||param[1]);
547
+ }
548
+ },
549
+
550
+ /**
551
+ * Replaces each node under 'node'
552
+ * @param node DOMElement
553
+ * @public
554
+ */
555
+ run: function (node){
556
+ this.attributePrefix = this.parent.attributePrefix.toLowerCase();
557
+ this.attributePrefixLength = this.attributePrefix.length;
558
+ var treeWalker = document.createTreeWalker(
559
+ node||document,
560
+ NodeFilter.SHOW_ELEMENT,
561
+ { acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } },
562
+ false
563
+ );
564
+ while(treeWalker.nextNode()) this.walkNode(treeWalker.currentNode);
565
+ },
566
+
567
+ /**
568
+ * Separates the bundleName and resourceName and returns the string from locale chain
569
+ * @param resourceName String Default bundle is "default"
570
+ * @return String
571
+ */
572
+ getHtmlString: function(resourceName){
573
+ var bundleName;
574
+ var i = resourceName.indexOf('.');
575
+ if(i!=-1){ bundleName = resourceName.substr(0, i); resourceName=resourceName.substr(i+1); }
576
+ else bundleName = "default"
577
+ return this.parent.getStringFromChain(bundleName, resourceName);
578
+ }
579
+ };
580
+
581
+
582
+
583
+ /**
584
+ * Creates a new object used in Localizer to store loaded bundles, locale chain and bundles path
585
+ * @private
586
+ * @constructor
587
+ * @v
588
+ */
589
+ function LocalizerPrivate(parent){
590
+ this.parent = parent;
591
+
592
+ // dom walker needed to update nodes
593
+ this.domWalker = new DOMWalker(this);
594
+
595
+
596
+ // Locale chain is the internal order that the framework uses to search for locales
597
+ this.localeChain = [];
598
+
599
+ // When the developer sets the locale chain we should disable the automatic detection
600
+ this.autoLocaleChain = true;
601
+
602
+ // Loaded locales should be sorted for later use
603
+ this.localeCache = {};
604
+
605
+ // Default bundle path used to solve the locales directories
606
+ this.bundlePath = new air.File('app:/locale/');
607
+
608
+ // Store the system capabilities for later use
609
+ this.userPreference = air.Capabilities.languages || [air.Capabilities.language];
610
+
611
+ // Event listeners
612
+ this.eventListeners = {};
613
+
614
+ this.attributePrefix = 'local_';
615
+
616
+ }
617
+ LocalizerPrivate.prototype = {
618
+
619
+ /**
620
+ * Creates a new locale object that caches bundles and files
621
+ * registers the locale for later use
622
+ * @private
623
+ * @param locale The localeName to be created
624
+ * @return LocaleHash
625
+ */
626
+ createLocaleCache: function(locale){
627
+ var obj = {
628
+ name : locale,
629
+ bundles: {},
630
+ files: {}
631
+ };
632
+ this.localeCache[locale] = obj;
633
+ return obj;
634
+ },
635
+
636
+ /**
637
+ * Returns the locale cache object. In case it is not already loaded it is created.
638
+ * @private
639
+ * @param locale String The localeName to be returned
640
+ * @return LocaleHash
641
+ */
642
+ getLocaleCache: function(locale){
643
+ return this.localeCache[locale] || this.createLocaleCache(locale);
644
+ },
645
+
646
+
647
+ /**
648
+ * Removes all the cached locales that are not in the locale chain
649
+ * @private
650
+ */
651
+ cleanupCache: function (){
652
+ var isInChain = {};
653
+ for(var i in this.localeChain) isInChain[ this.localeChain[i] ] = true;
654
+
655
+ for(var locale in this.localeCache ) {
656
+ if( ! isInChain[locale] ){
657
+ this.localeCache [ locale ] = null;
658
+ delete this.localeCache [locale];
659
+ }
660
+ }
661
+ },
662
+
663
+ /**
664
+ * Removes all the cached locales
665
+ * @private
666
+ */
667
+ clearCache: function (){
668
+ for(var locale in this.localeCache ) {
669
+ this.localeCache [ locale ] = null;
670
+ delete this.localeCache [locale];
671
+ }
672
+ },
673
+ /**
674
+ * Creates the bundle object. Also caches it for later use
675
+ * @private
676
+ * @param locale String The locale name where it should search the bundle
677
+ * @param bundlename String The bundle name for the returned bundle
678
+ * @return ResourceBundle
679
+ */
680
+ createBundle: function(locale, bundleName){
681
+ var localeCache = this.getLocaleCache(locale);
682
+ var file = this.bundlePath.resolvePath(locale).resolvePath(bundleName+".properties");
683
+ var resourceBundle = new ResourceBundle(file);
684
+ localeCache.bundles[bundleName] = resourceBundle;
685
+ if(!resourceBundle.found){
686
+ setTimeout(function(obj){
687
+ obj.dispatchEvent(new LocalizerEvent(Localizer.BUNDLE_NOT_FOUND, {
688
+ bundleName: bundleName,
689
+ locale: locale
690
+ }));
691
+ }, 0, this);
692
+ }
693
+ return resourceBundle;
694
+ },
695
+
696
+ /**
697
+ * Loads a bundle and returns it.
698
+ * @private
699
+ * @param locale String The locale name where it should search the bundle
700
+ * @param bundleName String The bundle name to be loaded
701
+ * @param useCache Bool Setting this to false will force the function to reread the bundle. Default is true
702
+ * @return ResourceBundle
703
+ */
704
+ loadBundle: function(locale, bundleName, useCache){
705
+ var localeCache = this.getLocaleCache(locale);
706
+ useCache=(typeof useCache=='undefined')?true:useCache;
707
+ return (useCache&&localeCache.bundles[bundleName]) || this.createBundle(locale, bundleName);
708
+ },
709
+
710
+ /**
711
+ * Loads a bundle and returns its keys and values as an object.
712
+ * @private
713
+ * @param locale String The locale name where it should search the bundle
714
+ * @param bundleName String The bundle name to be loaded
715
+ * @return Object Returns null if the bundle is not found.
716
+ */
717
+ getResourceBundle: function(locale, bundleName){
718
+ var bundle = this.loadBundle(locale, bundleName);
719
+ if(bundle==null)
720
+ return null;
721
+ //make a copy of the bundle
722
+ var result = {};
723
+ for (var key in bundle.keys){
724
+ result [ key ] = bundle.keys [key];
725
+ }
726
+ return result;
727
+ },
728
+
729
+ /**
730
+ * Creates a new ResourceFile based on the fileName
731
+ * @private
732
+ * @param locale String The locale name where it should search the file
733
+ * @param fileName String Filename that should be loaded
734
+ * @return ResourceFile
735
+ */
736
+ createFile: function(locale, fileName){
737
+ var localeCache = this.getLocaleCache(locale);
738
+ var file = this.bundlePath.resolvePath(locale).resolvePath(fileName);
739
+ var resourceFile = new ResourceFile(file);
740
+ localeCache.files[fileName] = resourceFile;
741
+ if(!resourceFile.found){
742
+ setTimeout(function(obj){
743
+ obj.dispatchEvent(new LocalizerEvent(Localizer.FILE_NOT_FOUND, {
744
+ fileName: fileName,
745
+ locale: locale
746
+ }));
747
+ }, 0, this);
748
+ }
749
+ return resourceFile;
750
+ },
751
+
752
+ /**
753
+ * Loads a file and returns it.
754
+ * @private
755
+ * @param locale String The locale name where it should search the file
756
+ * @param bundleName String The file name to be loaded
757
+ * @param useCache Bool Setting this to false will force the function to reread the file. Default is true
758
+ * @return ResourceFile
759
+ */
760
+ loadFile: function(locale, fileName, useCache){
761
+ var localeCache = this.getLocaleCache(locale);
762
+ useCache=(typeof useCache=='undefined')?true:useCache;
763
+ return (useCache&&localeCache.files[fileName]) || this.createFile(locale, fileName);
764
+ },
765
+
766
+ /**
767
+ * Automatically discovers the available locales using the listing directory of the bundle path,
768
+ * returns an array of locales
769
+ * @private
770
+ * @return StringArray
771
+ */
772
+ discoverAndReturnAvailableLocales: function (){
773
+ var folder = this.bundlePath;
774
+ // check if the file exists and is a directory
775
+ if(!(folder.exists&&folder.isDirectory)){
776
+ // just die sillently, because a higher level should take care of that
777
+ return [];
778
+ }
779
+
780
+ var localeChain = []
781
+ var localeFolders = folder.getDirectoryListing();
782
+ for(var i=0, l=localeFolders.length; i<l; i++){
783
+ var folder = localeFolders[i];
784
+ // checking if we got a real folder
785
+ if(folder.isDirectory){
786
+ localeChain.push(folder.name);
787
+ }
788
+ }
789
+ return localeChain;
790
+ },
791
+
792
+
793
+
794
+ /**
795
+ * Same as sortLocalesUsingPreferences, but this time uses the systems pref
796
+ * @param locales String[] List of locales to be sorted.
797
+ * @param addAll Adds all the locales at the end, even though no locale is in the preferences list. Default is true
798
+ * @return String[]
799
+ */
800
+ sortLocalesUsingSystemPreferences: function(locales, addAll){
801
+ return LocaleSorter.sortLocalesUsingPreferences( locales, this.userPreference, Localizer.ultimateFallbackLocale, addAll)
802
+ },
803
+
804
+ /**
805
+ * Discovers locale chain the bundle path and sets the current locale chain.
806
+ * The function will fail silently if the locale chain has been set by the user
807
+ * @param fireEvent bool If true it will fire change events. Default is true
808
+ */
809
+ autoDiscoverLocaleChain: function(fireEvent){
810
+ if(this.autoLocaleChain){
811
+ // set the new locale chain
812
+ var oldLocaleChain = this.localeChain;
813
+ this.localeChain = this.sortLocalesUsingSystemPreferences( this.discoverAndReturnAvailableLocales(), true);
814
+ // remove from cache not neeed locales
815
+ this.cleanupCache();
816
+ if(typeof fireEvent=='undefined') fireEvent = true;
817
+ if(fireEvent){
818
+ this.diffChainsAndFireEvent(this.localeChain, oldLocaleChain);
819
+ }
820
+ }
821
+ },
822
+
823
+ /**
824
+ * Diffs two chains and returns true if they are not equal
825
+ * @param newChain String[]
826
+ * @param oldChain String[]
827
+ * @return String[]
828
+ */
829
+ diffChains: function(newChain, oldChain){
830
+ if(newChain.length!=oldChain.length) return true;
831
+ for(var i=0, l=newChain.length; i<l; i++){
832
+ if(newChain[i]!=oldChain[i])
833
+ return true;
834
+ }
835
+ return false;
836
+ },
837
+
838
+ /**
839
+ * Clones a chain list
840
+ * @param chain String[]
841
+ * @return String[]
842
+ */
843
+ cloneChain: function(chain){
844
+ var result = [];
845
+ for(var i=0, l=chain.length; i<l; i++){
846
+ result.push( chain[i] );
847
+ }
848
+ return result;
849
+ },
850
+
851
+ /**
852
+ * diffs two locale chains and fires an event if changed
853
+ * @param newChain String[]
854
+ * @param oldChain String[]
855
+ * @private
856
+ */
857
+ diffChainsAndFireEvent: function(newChain, oldChain){
858
+ if(this.diffChains(newChain, oldChain)){
859
+ this.dispatchEvent(new LocalizerEvent(Localizer.LOCALE_CHANGE, {
860
+ localeChain: this.cloneChain(newChain)
861
+ }));
862
+ }
863
+ },
864
+
865
+ /**
866
+ * Sets the locale chain and disables automatic locale chain detection
867
+ * @param chain String[]
868
+ */
869
+ setLocaleChain: function ( chain ){
870
+ if(chain && chain.hasOwnProperty && chain.hasOwnProperty('length') && chain.length>0 ){
871
+ var oldChain = this.localeChain;
872
+ this.localeChain = this.cloneChain(chain);
873
+
874
+ // switch off auto locale chain detection
875
+ this.autoLocaleChain = false;
876
+
877
+ // send some events
878
+ this.diffChainsAndFireEvent(this.localeChain, oldChain);
879
+ }
880
+ },
881
+
882
+ /**
883
+ * Returns the current locale chain
884
+ * @return String[]
885
+ */
886
+ getLocaleChain: function(){
887
+ return this.cloneChain(this.localeChain);
888
+ },
889
+
890
+ /**
891
+ * Returns the source string, but replaces the "{i}" string with args[i]
892
+ * @param source String
893
+ * @param args String[]
894
+ * @return String
895
+ */
896
+ template: function(source, args){
897
+ var parser = /{([^}]*)}/g;
898
+ var a = source.split(parser);
899
+ var result = [];
900
+ var d=0;
901
+ for(var i=0,l=a.length;i<l;i++,d=1-d){
902
+ if(d){
903
+ if(args.hasOwnProperty(a[i]) && (args[a[i]]!=null) && (typeof args[a[i]]!='undefined')){
904
+ result.push(args[a[i]]);
905
+ }else{
906
+ result.push("{"+a[i]+"}");
907
+ }
908
+ }else{
909
+ result.push(a[i]);
910
+ }
911
+ }
912
+ return result.join('');
913
+ },
914
+
915
+ // typical add/remove dispatch listener object
916
+
917
+ addEventListener: function(eventName, handler){
918
+ var eventListeners = this.eventListeners;
919
+ if(!eventListeners[eventName]){
920
+ eventListeners[eventName] = [];
921
+ }else{
922
+ // prevent adding the same listener twice
923
+ this.removeEventListener(eventName, handler);
924
+ }
925
+ eventListeners[eventName].push( {
926
+ eventName:eventName,
927
+ handler:handler
928
+ });
929
+ },
930
+
931
+ removeEventListener: function(eventName, handler){
932
+ var handlers = this.eventListeners[eventName];
933
+ if(!handlers) return;
934
+ for(var i=handlers.length-1;i>=0;i--){
935
+ var evh = handlers[i];
936
+ if(evh.eventName==eventName&&evh.handler==handler){
937
+ handlers.splice(i, 1);
938
+ }
939
+ }
940
+ },
941
+
942
+ dispatchEvent: function(ev){
943
+ var handlers = this.eventListeners[ev.name];
944
+ if(!handlers) return;
945
+ for(var i=handlers.length-1;i>=0;i--){
946
+ handlers[i].handler.call(this.parent, ev);
947
+ }
948
+ },
949
+
950
+
951
+ /**
952
+ * Loads the bundle and returns the resource value
953
+ * @public
954
+ * @param bundleName String
955
+ * @param resourceName String
956
+ * @param locale String
957
+ * @return String
958
+ */
959
+ getString: function(bundleName, resourceName, locale){
960
+ var bundle = this.loadBundle(locale, bundleName);
961
+ var result = bundle.get(resourceName);
962
+ if(result==null){
963
+ setTimeout(function(obj){
964
+ obj.dispatchEvent(new LocalizerEvent(Localizer.RESOURCE_NOT_FOUND, {
965
+ bundleName: bundleName,
966
+ resourceName: resourceName,
967
+ locale: locale
968
+ }));
969
+ }, 0, this);
970
+ }
971
+ return result;
972
+ },
973
+
974
+ /**
975
+ * loads the file and returns the contents
976
+ * @public
977
+ * @param fileName String
978
+ * @param locale String
979
+ * @return String
980
+ */
981
+ getFile: function(fileName, locale){
982
+ var file = this.loadFile(locale, fileName);
983
+ return file.getContent();
984
+ },
985
+
986
+ /**
987
+ * Uses the locale chain to get the first defined value
988
+ * @public
989
+ * @param bundleName String
990
+ * @param resourcename String
991
+ * @return String
992
+ */
993
+ getStringFromChain: function(bundleName, resourceName){
994
+ var result;
995
+ var chain = this.localeChain;
996
+ if(chain){
997
+ for(var i=0, l=chain.length; i<l; i++){
998
+ result = this.getString( bundleName, resourceName, chain[i] );
999
+ if(result!=null) return result;
1000
+ }
1001
+ }
1002
+ return null;
1003
+ },
1004
+
1005
+ /**
1006
+ * Uses the locale chain to get the first defined file
1007
+ * @public
1008
+ * @param fileName
1009
+ * @return String
1010
+ */
1011
+ getFileFromChain: function(fileName){
1012
+ var result;
1013
+ var chain = this.localeChain;
1014
+ if(chain){
1015
+ for(var i=0, l=chain.length; i<l; i++){
1016
+ result = this.getFile( fileName, chain[i] );
1017
+ if(result!=null) return result;
1018
+ }
1019
+ }
1020
+ return null;
1021
+ },
1022
+
1023
+ /**
1024
+ * Updates the dom
1025
+ * @param domElement DOMElement Optional, default is document
1026
+ * @public
1027
+ */
1028
+ update: function(domElement){
1029
+ this.domWalker.run( domElement || document );
1030
+ }
1031
+ };
1032
+
1033
+
1034
+ // Localizer constructor, this should be callable by this script only. Use the canCreate trick in order
1035
+ // to throw an error when it is not set to true.
1036
+ // Also note that the canCreateLocalizer is visible just in this script
1037
+ var canCreateLocalizer = false;
1038
+ function Localizer(){
1039
+ // Throw an error when the users wants to create the Localizer using the new keyword. This is a
1040
+ // singleton and there's only one creator for it (air.Localizer.localizer getter)
1041
+ if(!canCreateLocalizer){
1042
+ throw new Error("Cannot create an air.Localizer instance using the 'new' keyword. Use air.Localizer.localizer instead.");
1043
+ }
1044
+ this._private = new LocalizerPrivate( this );
1045
+ this._private.autoDiscoverLocaleChain(
1046
+ false // do not fire events while loading
1047
+ );
1048
+ }
1049
+
1050
+ Localizer.prototype = {
1051
+
1052
+ // * Sets the path to the localization files
1053
+ //
1054
+ // * Default Bundle path is “app:/locale/”;
1055
+ //
1056
+ // * NOTE: If setLocaleChain hasn’t been called, the directory listing of the bundle path is used to
1057
+ // automatically figure out what locales are supported by the application and than call
1058
+ // “sortLanguagesByPreference” in order to sort them using the Preferences defined by the user
1059
+ // in the “Capabilities.languages” array; If it fails to list the directory it will throw
1060
+ // “air.Localizer.BundlePathNotFoundError” exception.
1061
+ //
1062
+ // * NOTE: In order to have automatically locales detection the path must point to an existing
1063
+ // directory, that can be listed using runtime’s File API;
1064
+
1065
+ setBundlesDirectory: function setBundlesDirectory(/* String */ path){
1066
+ var file;
1067
+ try{
1068
+ file = air.File(path);
1069
+ }catch(e){
1070
+ //coercion failed. this must be a string
1071
+ file = air.File.applicationDirectory.resolvePath(path);
1072
+ if(!file.exists){ // treating cases like "app:/"
1073
+ try{
1074
+ file = new air.File(path);
1075
+ }catch(e) { ; }
1076
+ }
1077
+ }
1078
+ // Checking that the path points to an existing folder
1079
+ if(!file||!(file.exists&&file.isDirectory)){
1080
+ throw new Localizer.BundlePathNotFoundError(path);
1081
+ }
1082
+
1083
+ this._private.clearCache();
1084
+
1085
+ this._private.bundlePath = file ;
1086
+ // autodiscover will not change the locale chain if the developer already called setLocaleChain
1087
+ this._private.autoDiscoverLocaleChain(
1088
+ true // it should fire an event when the chain is changed
1089
+ );
1090
+ },
1091
+
1092
+ // * Walks all the elements in the DOM tree under the specified “parentNode” and applies the localization process:
1093
+ // * finds all the attributes prefixed with the current prefix (”local_” by default)
1094
+ // * recreates the attributes by removing the prefix and setting the value to the
1095
+ // value defined by the specified key in the resource bundle
1096
+ // * in one particular case “{prefix}innerHTML” the innerHTML will be changed to the value defined by the key;
1097
+ // * Uses the locale chain returned by “getLocaleChain”.
1098
+ // * Note: When the “parentNode” is missing the window.document node is used instead.
1099
+ // * The key should be in the following format: “{bundleName}.{resourceName}”.
1100
+ //
1101
+ // E.g. this element:
1102
+ // <a local_href="default.urlLogin" local_innerHTML="default.cmdLogin"></a>
1103
+ // becomes
1104
+ // <a local_href="default.urlLogin" local_innerHTML="default.cmdLogin" href="app:/login.en.html">Click here to login</a>
1105
+
1106
+ update: function update(/* DOMElement, optional */ parentNode /* = document */){
1107
+ this._private.update(parentNode);
1108
+ },
1109
+
1110
+ // * If “locale” argument is provided it is used to return the value of the localization resource
1111
+ // called “resourceName” located in the bundle called “bundleName” only for the locale “locale”.
1112
+ // Otherwise, the locale chain returned by “getLocaleChain” is used to lookup the first locale
1113
+ // that that provides the “resourceName”. For example if the locale chain is [fr_CA, fr] and a
1114
+ // particular resource is not found in “fr_CA”, the framework will also search that resource in the “fr”.
1115
+ //
1116
+ // * It will automatically load the bundles if needed.
1117
+ //
1118
+ // * Returns null if the resource is not found and fires one of the following events:
1119
+ // * RESOURCE_NOT_FOUND when the bundle does not contain the specified “resourceName”;
1120
+ // * BUNDLE_NOT_FOUND when the bundle file is not found.
1121
+ //
1122
+ // * If “templateArgs” is provided the function will use it to replace bracketed numbers in the resource
1123
+ // with the correspondent values from the “templateArgs” array (only where applicable, meaning
1124
+ // that if templateArgs[n] is not defined, “{n}” will not be changed):
1125
+ // * “{0}”, “{1}”, .... “{n}” will be replaced with templateArgs[0], templateArgs[1] ... templateArgs[n].
1126
+ // * in order to skip replacement for one number, just set that “templateArgs” item to undefined or null;
1127
+
1128
+ getString: function getString(/* String */ bundleName, /*String */ resourceName, /* optional String[] */ templateArgs, /* optional, String */ locale ) /*: String*/{
1129
+ var result = null;
1130
+ if(locale){
1131
+ result = this._private.getString(bundleName, resourceName, locale);
1132
+ }else{
1133
+ result = this._private.getStringFromChain(bundleName, resourceName);
1134
+ }
1135
+ if(templateArgs&&result!=null){
1136
+ result = this._private.template(result, templateArgs);
1137
+ }
1138
+ return result;
1139
+ },
1140
+
1141
+ //
1142
+ // * Returns the keys found in a bundle
1143
+ //
1144
+ // @param locale String The locale name where it should search the bundle
1145
+ // @param bundleName String The bundle name to be loaded
1146
+ //
1147
+ // * Returns null if the bundle is not found.
1148
+ //
1149
+ getResourceBundle: function(locale, bundleName){
1150
+ return this._private.getResourceBundle(locale, bundleName);
1151
+ },
1152
+
1153
+ // * If “locale” argument is provided, it returns the contents of the file
1154
+ // “{bundlesDirectory}/{locale}/{resourceFileName}”. Otherwise, the locale
1155
+ // chain returned by “getLocaleChain” is used to lookup the first locale that
1156
+ // that provides the “resourceFileName” file. For example if the locale chain is
1157
+ // [fr_FR, fr] the file will be searched using that order: first will search
1158
+ // “{bundlesDirectory}/fr_FR/{resourceFileName}” and only if it is not found the
1159
+ // framework will continue to search for “{bundlesDirectory}/fr/{resourceFileName}”.
1160
+ //
1161
+ // * {bundlesPath} is the current bundle path set using “setBundlesDirectory”;
1162
+ //
1163
+ // * Returns null if the resource file is not found and fires:
1164
+ // * FILE_NOT_FOUND event;
1165
+ //
1166
+ // * If “templateArgs” is provided the function will use it to replace bracketed numbers in the
1167
+ // resource file with the correspondent values from the “templateArgs” array (only where applicable,
1168
+ // meaning that if templateArgs[n] is not defined, “{n}” will not be changed):
1169
+ // * “{0}”, “{1}”, .... “{n}” will be replaced with templateArgs[0], templateArgs[1] ... templateArgs[n].
1170
+ // * in order to skip replacement for one number, just set that “templateArgs” item to undefined;
1171
+
1172
+ getFile: function getFile(/* String */ resourceFileName, /* optional String[] */ templateArgs, /* String */ locale ) /*: String*/ {
1173
+ var result = null;
1174
+ if(locale){
1175
+ result = this._private.getFile(resourceFileName, locale);
1176
+ }else{
1177
+ result = this._private.getFileFromChain(resourceFileName);
1178
+ }
1179
+ if(templateArgs&&result!=null){
1180
+ result = this._private.template(result, templateArgs);
1181
+ }
1182
+ return result;
1183
+ },
1184
+
1185
+ // * Sets the locale chain and updates the current locale used by all other functions.
1186
+ //
1187
+ // * Note: When “chain” argument is missing, is not an array or has zero length the function fails
1188
+ // and throws “air.Localizer.IllegalArgumentsError” exception
1189
+ //
1190
+ // * Fires LOCALE_CHANGE event, the developer should update the DOM using update function. This event
1191
+ // is fired synchronous whenever the locale chain has changed.
1192
+ setLocaleChain: function setLocaleChain(/* optional, String[] */ chain){
1193
+ if(!(chain&&chain.hasOwnProperty&&chain.hasOwnProperty('length')&&chain.length>0)){
1194
+ throw new Localizer.IllegalArgumentsError("Locale chain should be an array.");
1195
+ }
1196
+ this._private.setLocaleChain(chain);
1197
+ },
1198
+
1199
+ // * Returns the locale chain set by the previous call to “setLocaleChain”.
1200
+ //
1201
+ // * NOTE: If “setLocaleChain” hasn’t been called the function returns the automatically
1202
+ // detected locale chain computed when the Localizer is instantiated (see also the property called “localizer”);
1203
+
1204
+ getLocaleChain: function getLocaleChain()/* :String[] */ {
1205
+ return this._private.getLocaleChain();
1206
+ },
1207
+
1208
+ // * Sets the prefix for local attributes used in the “update” function.
1209
+ //
1210
+ // * Default prefix is “local_”.
1211
+
1212
+ setLocalAttributePrefix: function setLocalAttributePrefix(value){
1213
+ this._private.attributePrefix = value+'';
1214
+ },
1215
+
1216
+ // typical add/remove Event Dispatcher
1217
+
1218
+ addEventListener: function addEventListener(/* String */ eventName, /* callback function */ callback){
1219
+ this._private.addEventListener(eventName, callback);
1220
+ },
1221
+ removeEventListener: function removeEventListener(/* String */ eventName, /* callback function */ callback){
1222
+ this._private.removeEventListener(eventName, callback);
1223
+ }
1224
+
1225
+ };
1226
+
1227
+
1228
+ // Define the getter function. This will temporararly set canCreateLocalizer to true
1229
+ // and create a new Localizer. It writes the newly created localizer to the localizerInstance variable;
1230
+
1231
+ var localizerInstance = null;
1232
+ Localizer.__defineGetter__("localizer", function(){
1233
+ if(!localizerInstance){
1234
+ canCreateLocalizer = true;
1235
+ localizerInstance = new Localizer();
1236
+ canCreateLocalizer = false;
1237
+ }
1238
+ return localizerInstance;
1239
+ });
1240
+
1241
+
1242
+ // EVENTS
1243
+
1244
+ // Fired by “setLocaleChain” when the current locale is changed (synchronous)
1245
+ // Event object strcuture is : { localeChain /* : String [] */ }
1246
+ Localizer.LOCALE_CHANGE = "change";
1247
+
1248
+ // Fired by “getString” and “update” functions when a resource is not found in the specified bundle (fired asynchronous)
1249
+ // Event object structure is : { resourceName /* : String */, bundleName /* : String */ }
1250
+ Localizer.RESOURCE_NOT_FOUND = "resourceNotFound";
1251
+
1252
+ // Fired by “getFile” when a resource file is not found (fired asynchronous)
1253
+ // Event object structure is : { resourceFileName /* : String */ }
1254
+ Localizer.FILE_NOT_FOUND = "fileNotFound";
1255
+
1256
+ // Fired by “getString” and “update” functions when a bundle file is not found (fired asynchronous)
1257
+ // Event object structure is : { bundleName /* : String */ }
1258
+ Localizer.BUNDLE_NOT_FOUND = "bundleNotFound";
1259
+
1260
+ // Version number
1261
+ Localizer.version = version;
1262
+
1263
+ (function(){
1264
+
1265
+ // ERROR
1266
+ function BundlePathNotFoundError(path){
1267
+ this.name = 'air.Localizer.BundlePathNotFoundError';
1268
+ this.message = "Bundle directory not found "+path;
1269
+ }
1270
+ BundlePathNotFoundError.prototype = new Error;
1271
+ Localizer.BundlePathNotFoundError = BundlePathNotFoundError;
1272
+
1273
+
1274
+ function IllegalArgumentsError(msg){
1275
+ this.name = 'air.Localizer.IllegalArgumentsError';
1276
+ this.message = msg;
1277
+ }
1278
+ IllegalArgumentsError.prototype = new Error;
1279
+ Localizer.IllegalArgumentsError = IllegalArgumentsError;
1280
+
1281
+ }());
1282
+
1283
+ Localizer.LocaleId = LocaleId;
1284
+
1285
+ Localizer.ultimateFallbackLocale = 'en';
1286
+
1287
+
1288
+ // * This function is for internal use only.
1289
+ //
1290
+ // * Sort a languages array using the order given by the system capabilities languages array.
1291
+ //
1292
+ // * Setting removeUnsupported to true removes system unsupported languages. When removeUnsupported is false it makes the system unsupported languages be the last ones preserving order from original array.
1293
+ //
1294
+ // * Default value for removeUnsupported is true.
1295
+ //
1296
+ // *NOTE: It uses the same approach that the ADOBE AIR application Installer uses:
1297
+ // * it tries to find the perfect match for locale name;
1298
+ // * otherwise it fallbacks to finding locales with same parents (eg. “en_US” will fallback to “en” )
1299
+ // * user preference will be promoted;
1300
+ //
1301
+ // * (system supported means they are in the Capabilities.languages array from the runtime)
1302
+ //
1303
+ // * eg: If Capabilities.languages = [ “fr_CA”, “en_UK”, “ja” ] and the “languages” argument
1304
+ // is [ “en”, “fr_FR”, “zn_ZN” ] the returning array will be [ “fr_FR”, “en” ]
1305
+ //
1306
+ // * eg2: When the “languages” argument is [ fr, fr_CA, fr_FR, ro, en, en_US ]:
1307
+ // * and the Capabilities.languages = [ fr_CA ] the result will be [ fr_CA, fr, fr_FR ]
1308
+ // * and the Capabilities.languages = [ fr_CA, en ] the result will be [ fr_CA, fr, fr_FR, en, en_US ]
1309
+
1310
+ Localizer.sortLanguagesByPreference = function sortLanguagesByPreference( /* String[] */ appLocales, /* String[] */ systemPreferences, /* String, optional */ ultimateFallbackLocale /* = null*/, /*Boolean, optional */ keepAllLocales /* = true */) /* : String[] */{
1311
+ if(!( appLocales && appLocales.hasOwnProperty('length') && systemPreferences&&systemPreferences.hasOwnProperty('length') )){
1312
+ throw new Localizer.IllegalArgumentsError("Expected at least two arguments: appLocales and systemPreferences.");
1313
+ }
1314
+ return LocaleSorter.sortLocalesUsingPreferences( appLocales, systemPreferences, ultimateFallbackLocale, keepAllLocales)
1315
+ };
1316
+
1317
+
1318
+
1319
+ if(!window.air){
1320
+ window.air = {};
1321
+ }
1322
+
1323
+ window.air.Localizer = Localizer;
1324
+
1325
+ }());