rails-asset-localization 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +85 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/i18n/translations.coffee.erb +13 -0
  6. data/app/controllers/rails_asset_localization/locales_controller.rb +13 -0
  7. data/config/routes.rb +3 -0
  8. data/lib/rails-asset-localization.rb +1 -0
  9. data/lib/rails_asset_localization.rb +9 -0
  10. data/lib/rails_asset_localization/engine.rb +4 -0
  11. data/lib/rails_asset_localization/locales_exporter.rb +20 -0
  12. data/lib/rails_asset_localization/version.rb +3 -0
  13. data/test/dummy/Rakefile +7 -0
  14. data/test/dummy/app/assets/javascripts/application.js +9 -0
  15. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  16. data/test/dummy/app/controllers/application_controller.rb +3 -0
  17. data/test/dummy/app/helpers/application_helper.rb +2 -0
  18. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  19. data/test/dummy/config.ru +4 -0
  20. data/test/dummy/config/application.rb +45 -0
  21. data/test/dummy/config/boot.rb +10 -0
  22. data/test/dummy/config/database.yml +25 -0
  23. data/test/dummy/config/environment.rb +5 -0
  24. data/test/dummy/config/environments/development.rb +30 -0
  25. data/test/dummy/config/environments/production.rb +60 -0
  26. data/test/dummy/config/environments/test.rb +42 -0
  27. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  28. data/test/dummy/config/initializers/inflections.rb +10 -0
  29. data/test/dummy/config/initializers/mime_types.rb +5 -0
  30. data/test/dummy/config/initializers/secret_token.rb +7 -0
  31. data/test/dummy/config/initializers/session_store.rb +8 -0
  32. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  33. data/test/dummy/config/locales/en.yml +5 -0
  34. data/test/dummy/config/routes.rb +58 -0
  35. data/test/dummy/public/404.html +26 -0
  36. data/test/dummy/public/422.html +26 -0
  37. data/test/dummy/public/500.html +26 -0
  38. data/test/dummy/public/favicon.ico +0 -0
  39. data/test/dummy/script/rails +6 -0
  40. data/test/rails-localization-engine_test.rb +7 -0
  41. data/test/test_helper.rb +10 -0
  42. data/vendor/assets/javascripts/i18next.js +2436 -0
  43. data/vendor/assets/javascripts/i18next.min.js +5 -0
  44. metadata +130 -0
@@ -0,0 +1,42 @@
1
+ Dummy::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb
3
+
4
+ # The test environment is used exclusively to run your application's
5
+ # test suite. You never need to work with it otherwise. Remember that
6
+ # your test database is "scratch space" for the test suite and is wiped
7
+ # and recreated between test runs. Don't rely on the data there!
8
+ config.cache_classes = true
9
+
10
+ # Configure static asset server for tests with Cache-Control for performance
11
+ config.serve_static_assets = true
12
+ config.static_cache_control = "public, max-age=3600"
13
+
14
+ # Log error messages when you accidentally call methods on nil
15
+ config.whiny_nils = true
16
+
17
+ # Show full error reports and disable caching
18
+ config.consider_all_requests_local = true
19
+ config.action_controller.perform_caching = false
20
+
21
+ # Raise exceptions instead of rendering exception templates
22
+ config.action_dispatch.show_exceptions = false
23
+
24
+ # Disable request forgery protection in test environment
25
+ config.action_controller.allow_forgery_protection = false
26
+
27
+ # Tell Action Mailer not to deliver emails to the real world.
28
+ # The :test delivery method accumulates sent emails in the
29
+ # ActionMailer::Base.deliveries array.
30
+ config.action_mailer.delivery_method = :test
31
+
32
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
33
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
34
+ # like if you have constraints or database-specific column types
35
+ # config.active_record.schema_format = :sql
36
+
37
+ # Print deprecation notices to the stderr
38
+ config.active_support.deprecation = :stderr
39
+
40
+ # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
41
+ config.assets.allow_debugging = true
42
+ end
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4
+ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
+
6
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
+ # Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,10 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new inflection rules using the following format
4
+ # (all these examples are active by default):
5
+ # ActiveSupport::Inflector.inflections do |inflect|
6
+ # inflect.plural /^(ox)$/i, '\1en'
7
+ # inflect.singular /^(ox)en/i, '\1'
8
+ # inflect.irregular 'person', 'people'
9
+ # inflect.uncountable %w( fish sheep )
10
+ # end
@@ -0,0 +1,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
5
+ # Mime::Type.register_alias "text/html", :iphone
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key for verifying the integrity of signed cookies.
4
+ # If you change this key, all old signed cookies will become invalid!
5
+ # Make sure the secret is at least 30 characters and all random,
6
+ # no regular words or you'll be exposed to dictionary attacks.
7
+ Dummy::Application.config.secret_token = '89d6339f6e9a99304ecc7f9ff4b47b3dbd9e2aba1efbb37ddc663e91c7e5086bc0d0860006f48582e6e1e4266533a0a5889155a5428e65982c925ba428e8ee57'
@@ -0,0 +1,8 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4
+
5
+ # Use the database for sessions instead of the cookie-based default,
6
+ # which shouldn't be used to store highly confidential information
7
+ # (create the session table with "rails generate session_migration")
8
+ # Dummy::Application.config.session_store :active_record_store
@@ -0,0 +1,14 @@
1
+ # Be sure to restart your server when you modify this file.
2
+ #
3
+ # This file contains settings for ActionController::ParamsWrapper which
4
+ # is enabled by default.
5
+
6
+ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7
+ ActiveSupport.on_load(:action_controller) do
8
+ wrap_parameters format: [:json]
9
+ end
10
+
11
+ # Disable root element in JSON by default.
12
+ ActiveSupport.on_load(:active_record) do
13
+ self.include_root_in_json = false
14
+ end
@@ -0,0 +1,5 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ hello: "Hello world"
@@ -0,0 +1,58 @@
1
+ Dummy::Application.routes.draw do
2
+ # The priority is based upon order of creation:
3
+ # first created -> highest priority.
4
+
5
+ # Sample of regular route:
6
+ # match 'products/:id' => 'catalog#view'
7
+ # Keep in mind you can assign values other than :controller and :action
8
+
9
+ # Sample of named route:
10
+ # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
11
+ # This route can be invoked with purchase_url(:id => product.id)
12
+
13
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
14
+ # resources :products
15
+
16
+ # Sample resource route with options:
17
+ # resources :products do
18
+ # member do
19
+ # get 'short'
20
+ # post 'toggle'
21
+ # end
22
+ #
23
+ # collection do
24
+ # get 'sold'
25
+ # end
26
+ # end
27
+
28
+ # Sample resource route with sub-resources:
29
+ # resources :products do
30
+ # resources :comments, :sales
31
+ # resource :seller
32
+ # end
33
+
34
+ # Sample resource route with more complex sub-resources
35
+ # resources :products do
36
+ # resources :comments
37
+ # resources :sales do
38
+ # get 'recent', :on => :collection
39
+ # end
40
+ # end
41
+
42
+ # Sample resource route within a namespace:
43
+ # namespace :admin do
44
+ # # Directs /admin/products/* to Admin::ProductsController
45
+ # # (app/controllers/admin/products_controller.rb)
46
+ # resources :products
47
+ # end
48
+
49
+ # You can have the root of your site routed with "root"
50
+ # just remember to delete public/index.html.
51
+ # root :to => 'welcome#index'
52
+
53
+ # See how all your routes lay out with "rake routes"
54
+
55
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
56
+ # Note: This route will make all actions in every controller accessible via GET requests.
57
+ # match ':controller(/:action(/:id(.:format)))'
58
+ end
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
24
+ </div>
25
+ </body>
26
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class RailsLocalizationEngineTest < ActiveSupport::TestCase
4
+ test "truth" do
5
+ assert_kind_of Module, RailsLocalizationEngine
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
6
+
7
+ Rails.backtrace_cleaner.remove_silencers!
8
+
9
+ # Load support files
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
@@ -0,0 +1,2436 @@
1
+ // i18next, v1.6.0
2
+ // Copyright (c)2013 Jan Mühlemann (jamuhl).
3
+ // Distributed under MIT license
4
+ // http://i18next.com
5
+ (function() {
6
+
7
+ // add indexOf to non ECMA-262 standard compliant browsers
8
+ if (!Array.prototype.indexOf) {
9
+ Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
10
+ "use strict";
11
+ if (this == null) {
12
+ throw new TypeError();
13
+ }
14
+ var t = Object(this);
15
+ var len = t.length >>> 0;
16
+ if (len === 0) {
17
+ return -1;
18
+ }
19
+ var n = 0;
20
+ if (arguments.length > 0) {
21
+ n = Number(arguments[1]);
22
+ if (n != n) { // shortcut for verifying if it's NaN
23
+ n = 0;
24
+ } else if (n != 0 && n != Infinity && n != -Infinity) {
25
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
26
+ }
27
+ }
28
+ if (n >= len) {
29
+ return -1;
30
+ }
31
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
32
+ for (; k < len; k++) {
33
+ if (k in t && t[k] === searchElement) {
34
+ return k;
35
+ }
36
+ }
37
+ return -1;
38
+ }
39
+ }
40
+
41
+ var root = this
42
+ , $ = root.jQuery
43
+ , i18n = {}
44
+ , resStore = {}
45
+ , currentLng
46
+ , replacementCounter = 0
47
+ , languages = [];
48
+
49
+
50
+ // Export the i18next object for **CommonJS**.
51
+ // If we're not in CommonJS, add `i18n` to the
52
+ // global object or to jquery.
53
+ if (typeof module !== 'undefined' && module.exports) {
54
+ module.exports = i18n;
55
+ } else {
56
+ if ($) {
57
+ $.i18n = $.i18n || i18n;
58
+ }
59
+
60
+ root.i18n = root.i18n || i18n;
61
+ }
62
+ // defaults
63
+ var o = {
64
+ lng: undefined,
65
+ load: 'all',
66
+ preload: [],
67
+ lowerCaseLng: false,
68
+ returnObjectTrees: false,
69
+ fallbackLng: 'dev',
70
+ detectLngQS: 'setLng',
71
+ ns: 'translation',
72
+ fallbackToDefaultNS: false,
73
+ nsseparator: ':',
74
+ keyseparator: '.',
75
+ selectorAttr: 'data-i18n',
76
+ debug: false,
77
+
78
+ resGetPath: 'locales/__lng__/__ns__.json',
79
+ resPostPath: 'locales/add/__lng__/__ns__',
80
+
81
+ getAsync: true,
82
+ postAsync: true,
83
+
84
+ resStore: undefined,
85
+ useLocalStorage: false,
86
+ localStorageExpirationTime: 7*24*60*60*1000,
87
+
88
+ dynamicLoad: false,
89
+ sendMissing: false,
90
+ sendMissingTo: 'fallback', // current | all
91
+ sendType: 'POST',
92
+
93
+ interpolationPrefix: '__',
94
+ interpolationSuffix: '__',
95
+ reusePrefix: '$t(',
96
+ reuseSuffix: ')',
97
+ pluralSuffix: '_plural',
98
+ pluralNotFound: ['plural_not_found', Math.random()].join(''),
99
+ contextNotFound: ['context_not_found', Math.random()].join(''),
100
+
101
+ setJqueryExt: true,
102
+ defaultValueFromContent: true,
103
+ useDataAttrOptions: false,
104
+ cookieExpirationTime: undefined,
105
+ useCookie: true,
106
+ cookieName: 'i18next',
107
+
108
+ postProcess: undefined
109
+ };
110
+
111
+ function _extend(target, source) {
112
+ if (!source || typeof source === 'function') {
113
+ return target;
114
+ }
115
+
116
+ for (var attr in source) { target[attr] = source[attr]; }
117
+ return target;
118
+ }
119
+
120
+ function _each(object, callback, args) {
121
+ var name, i = 0,
122
+ length = object.length,
123
+ isObj = length === undefined || typeof object === "function";
124
+
125
+ if (args) {
126
+ if (isObj) {
127
+ for (name in object) {
128
+ if (callback.apply(object[name], args) === false) {
129
+ break;
130
+ }
131
+ }
132
+ } else {
133
+ for ( ; i < length; ) {
134
+ if (callback.apply(object[i++], args) === false) {
135
+ break;
136
+ }
137
+ }
138
+ }
139
+
140
+ // A special, fast, case for the most common use of each
141
+ } else {
142
+ if (isObj) {
143
+ for (name in object) {
144
+ if (callback.call(object[name], name, object[name]) === false) {
145
+ break;
146
+ }
147
+ }
148
+ } else {
149
+ for ( ; i < length; ) {
150
+ if (callback.call(object[i], i, object[i++]) === false) {
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ return object;
158
+ }
159
+
160
+ function _ajax(options) {
161
+
162
+ // v0.5.0 of https://github.com/goloroden/http.js
163
+ var getXhr = function (callback) {
164
+ // Use the native XHR object if the browser supports it.
165
+ if (window.XMLHttpRequest) {
166
+ return callback(null, new XMLHttpRequest());
167
+ } else if (window.ActiveXObject) {
168
+ // In Internet Explorer check for ActiveX versions of the XHR object.
169
+ try {
170
+ return callback(null, new ActiveXObject("Msxml2.XMLHTTP"));
171
+ } catch (e) {
172
+ return callback(null, new ActiveXObject("Microsoft.XMLHTTP"));
173
+ }
174
+ }
175
+
176
+ // If no XHR support was found, throw an error.
177
+ return callback(new Error());
178
+ };
179
+
180
+ var encodeUsingUrlEncoding = function (data) {
181
+ if(typeof data === 'string') {
182
+ return data;
183
+ }
184
+
185
+ var result = [];
186
+ for(var dataItem in data) {
187
+ if(data.hasOwnProperty(dataItem)) {
188
+ result.push(encodeURIComponent(dataItem) + '=' + encodeURIComponent(data[dataItem]));
189
+ }
190
+ }
191
+
192
+ return result.join('&');
193
+ };
194
+
195
+ var utf8 = function (text) {
196
+ text = text.replace(/\r\n/g, '\n');
197
+ var result = '';
198
+
199
+ for(var i = 0; i < text.length; i++) {
200
+ var c = text.charCodeAt(i);
201
+
202
+ if(c < 128) {
203
+ result += String.fromCharCode(c);
204
+ } else if((c > 127) && (c < 2048)) {
205
+ result += String.fromCharCode((c >> 6) | 192);
206
+ result += String.fromCharCode((c & 63) | 128);
207
+ } else {
208
+ result += String.fromCharCode((c >> 12) | 224);
209
+ result += String.fromCharCode(((c >> 6) & 63) | 128);
210
+ result += String.fromCharCode((c & 63) | 128);
211
+ }
212
+ }
213
+
214
+ return result;
215
+ };
216
+
217
+ var base64 = function (text) {
218
+ var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
219
+
220
+ text = utf8(text);
221
+ var result = '',
222
+ chr1, chr2, chr3,
223
+ enc1, enc2, enc3, enc4,
224
+ i = 0;
225
+
226
+ do {
227
+ chr1 = text.charCodeAt(i++);
228
+ chr2 = text.charCodeAt(i++);
229
+ chr3 = text.charCodeAt(i++);
230
+
231
+ enc1 = chr1 >> 2;
232
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
233
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
234
+ enc4 = chr3 & 63;
235
+
236
+ if(isNaN(chr2)) {
237
+ enc3 = enc4 = 64;
238
+ } else if(isNaN(chr3)) {
239
+ enc4 = 64;
240
+ }
241
+
242
+ result +=
243
+ keyStr.charAt(enc1) +
244
+ keyStr.charAt(enc2) +
245
+ keyStr.charAt(enc3) +
246
+ keyStr.charAt(enc4);
247
+ chr1 = chr2 = chr3 = '';
248
+ enc1 = enc2 = enc3 = enc4 = '';
249
+ } while(i < text.length);
250
+
251
+ return result;
252
+ };
253
+
254
+ var mergeHeaders = function () {
255
+ // Use the first header object as base.
256
+ var result = arguments[0];
257
+
258
+ // Iterate through the remaining header objects and add them.
259
+ for(var i = 1; i < arguments.length; i++) {
260
+ var currentHeaders = arguments[i];
261
+ for(var header in currentHeaders) {
262
+ if(currentHeaders.hasOwnProperty(header)) {
263
+ result[header] = currentHeaders[header];
264
+ }
265
+ }
266
+ }
267
+
268
+ // Return the merged headers.
269
+ return result;
270
+ };
271
+
272
+ var ajax = function (method, url, options, callback) {
273
+ // Adjust parameters.
274
+ if(typeof options === 'function') {
275
+ callback = options;
276
+ options = {};
277
+ }
278
+
279
+ // Set default parameter values.
280
+ options.cache = options.cache || false;
281
+ options.data = options.data || {};
282
+ options.headers = options.headers || {};
283
+ options.jsonp = options.jsonp || false;
284
+ options.async = options.async === undefined ? true : options.async;
285
+
286
+ // Merge the various header objects.
287
+ var headers = mergeHeaders({
288
+ 'accept': '*/*',
289
+ 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'
290
+ }, ajax.headers, options.headers);
291
+
292
+ // Encode the data according to the content-type.
293
+ var payload;
294
+ if (headers['content-type'] === 'application/json') {
295
+ payload = JSON.stringify(options.data);
296
+ } else {
297
+ payload = encodeUsingUrlEncoding(options.data);
298
+ }
299
+
300
+ // Specially prepare GET requests: Setup the query string, handle caching and make a JSONP call
301
+ // if neccessary.
302
+ if(method === 'GET') {
303
+ // Setup the query string.
304
+ var queryString = [];
305
+ if(payload) {
306
+ queryString.push(payload);
307
+ payload = null;
308
+ }
309
+
310
+ // Handle caching.
311
+ if(!options.cache) {
312
+ queryString.push('_=' + (new Date()).getTime());
313
+ }
314
+
315
+ // If neccessary prepare the query string for a JSONP call.
316
+ if(options.jsonp) {
317
+ queryString.push('callback=' + options.jsonp);
318
+ queryString.push('jsonp=' + options.jsonp);
319
+ }
320
+
321
+ // Merge the query string and attach it to the url.
322
+ queryString = queryString.join('&');
323
+ if (queryString.length > 1) {
324
+ if (url.indexOf('?') > -1) {
325
+ url += '&' + queryString;
326
+ } else {
327
+ url += '?' + queryString;
328
+ }
329
+ }
330
+
331
+ // Make a JSONP call if neccessary.
332
+ if(options.jsonp) {
333
+ var head = document.getElementsByTagName('head')[0];
334
+ var script = document.createElement('script');
335
+ script.type = 'text/javascript';
336
+ script.src = url;
337
+ head.appendChild(script);
338
+ return;
339
+ }
340
+ }
341
+
342
+ // Since we got here, it is no JSONP request, so make a normal XHR request.
343
+ getXhr(function (err, xhr) {
344
+ if(err) return callback(err);
345
+
346
+ // Open the request.
347
+ xhr.open(method, url, options.async);
348
+
349
+ // Set the request headers.
350
+ for(var header in headers) {
351
+ if(headers.hasOwnProperty(header)) {
352
+ xhr.setRequestHeader(header, headers[header]);
353
+ }
354
+ }
355
+
356
+ // Handle the request events.
357
+ xhr.onreadystatechange = function () {
358
+ if(xhr.readyState === 4) {
359
+ var data = xhr.responseText || '';
360
+
361
+ // If no callback is given, return.
362
+ if(!callback) {
363
+ return;
364
+ }
365
+
366
+ // Return an object that provides access to the data as text and JSON.
367
+ callback(xhr.status, {
368
+ text: function () {
369
+ return data;
370
+ },
371
+
372
+ json: function () {
373
+ return JSON.parse(data);
374
+ }
375
+ });
376
+ }
377
+ };
378
+
379
+ // Actually send the XHR request.
380
+ xhr.send(payload);
381
+ });
382
+ };
383
+
384
+ // Define the external interface.
385
+ var http = {
386
+ authBasic: function (username, password) {
387
+ ajax.headers['Authorization'] = 'Basic ' + base64(username + ':' + password);
388
+ },
389
+
390
+ connect: function (url, options, callback) {
391
+ return ajax('CONNECT', url, options, callback);
392
+ },
393
+
394
+ del: function (url, options, callback) {
395
+ return ajax('DELETE', url, options, callback);
396
+ },
397
+
398
+ get: function (url, options, callback) {
399
+ return ajax('GET', url, options, callback);
400
+ },
401
+
402
+ head: function (url, options, callback) {
403
+ return ajax('HEAD', url, options, callback);
404
+ },
405
+
406
+ headers: function (headers) {
407
+ ajax.headers = headers || {};
408
+ },
409
+
410
+ isAllowed: function (url, verb, callback) {
411
+ this.options(url, function (status, data) {
412
+ callback(data.text().indexOf(verb) !== -1);
413
+ });
414
+ },
415
+
416
+ options: function (url, options, callback) {
417
+ return ajax('OPTIONS', url, options, callback);
418
+ },
419
+
420
+ patch: function (url, options, callback) {
421
+ return ajax('PATCH', url, options, callback);
422
+ },
423
+
424
+ post: function (url, options, callback) {
425
+ return ajax('POST', url, options, callback);
426
+ },
427
+
428
+ put: function (url, options, callback) {
429
+ return ajax('PUT', url, options, callback);
430
+ },
431
+
432
+ trace: function (url, options, callback) {
433
+ return ajax('TRACE', url, options, callback);
434
+ }
435
+ };
436
+
437
+
438
+ var methode = options.type ? options.type.toLowerCase() : 'get';
439
+
440
+ http[methode](options.url, options, function (status, data) {
441
+ if (status === 200) {
442
+ options.success(data.json(), status, null);
443
+ } else {
444
+ options.error(data.text(), status, null);
445
+ }
446
+ });
447
+ }
448
+
449
+ var _cookie = {
450
+ create: function(name,value,minutes) {
451
+ var expires;
452
+ if (minutes) {
453
+ var date = new Date();
454
+ date.setTime(date.getTime()+(minutes*60*1000));
455
+ expires = "; expires="+date.toGMTString();
456
+ }
457
+ else expires = "";
458
+ document.cookie = name+"="+value+expires+"; path=/";
459
+ },
460
+
461
+ read: function(name) {
462
+ var nameEQ = name + "=";
463
+ var ca = document.cookie.split(';');
464
+ for(var i=0;i < ca.length;i++) {
465
+ var c = ca[i];
466
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
467
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
468
+ }
469
+ return null;
470
+ },
471
+
472
+ remove: function(name) {
473
+ this.create(name,"",-1);
474
+ }
475
+ };
476
+
477
+ var cookie_noop = {
478
+ create: function(name,value,minutes) {},
479
+ read: function(name) { return null; },
480
+ remove: function(name) {}
481
+ };
482
+
483
+
484
+
485
+ // move dependent functions to a container so that
486
+ // they can be overriden easier in no jquery environment (node.js)
487
+ var f = {
488
+ extend: $ ? $.extend : _extend,
489
+ each: $ ? $.each : _each,
490
+ ajax: $ ? $.ajax : _ajax,
491
+ cookie: typeof document !== 'undefined' ? _cookie : cookie_noop,
492
+ detectLanguage: detectLanguage,
493
+ log: function(str) {
494
+ if (o.debug && typeof console !== "undefined") console.log(str);
495
+ },
496
+ toLanguages: function(lng) {
497
+ var languages = [];
498
+ if (typeof lng === 'string' && lng.indexOf('-') > -1) {
499
+ var parts = lng.split('-');
500
+
501
+ lng = o.lowerCaseLng ?
502
+ parts[0].toLowerCase() + '-' + parts[1].toLowerCase() :
503
+ parts[0].toLowerCase() + '-' + parts[1].toUpperCase();
504
+
505
+ if (o.load !== 'unspecific') languages.push(lng);
506
+ if (o.load !== 'current') languages.push(parts[0]);
507
+ } else {
508
+ languages.push(lng);
509
+ }
510
+
511
+ if (languages.indexOf(o.fallbackLng) === -1 && o.fallbackLng) languages.push(o.fallbackLng);
512
+
513
+ return languages;
514
+ },
515
+ regexEscape: function(str) {
516
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
517
+ }
518
+ };
519
+ function init(options, cb) {
520
+
521
+ if (typeof options === 'function') {
522
+ cb = options;
523
+ options = {};
524
+ }
525
+ options = options || {};
526
+
527
+ // override defaults with passed in options
528
+ f.extend(o, options);
529
+
530
+ // create namespace object if namespace is passed in as string
531
+ if (typeof o.ns == 'string') {
532
+ o.ns = { namespaces: [o.ns], defaultNs: o.ns};
533
+ }
534
+
535
+ // escape prefix/suffix
536
+ o.interpolationPrefixEscaped = f.regexEscape(o.interpolationPrefix);
537
+ o.interpolationSuffixEscaped = f.regexEscape(o.interpolationSuffix);
538
+
539
+ if (!o.lng) o.lng = f.detectLanguage();
540
+ if (o.lng) {
541
+ // set cookie with lng set (as detectLanguage will set cookie on need)
542
+ if (o.useCookie) f.cookie.create(o.cookieName, o.lng, o.cookieExpirationTime);
543
+ } else {
544
+ o.lng = o.fallbackLng;
545
+ if (o.useCookie) f.cookie.remove(o.cookieName);
546
+ }
547
+
548
+ languages = f.toLanguages(o.lng);
549
+ currentLng = languages[0];
550
+ f.log('currentLng set to: ' + currentLng);
551
+
552
+ pluralExtensions.setCurrentLng(currentLng);
553
+
554
+ // add JQuery extensions
555
+ if ($ && o.setJqueryExt) addJqueryFunct();
556
+
557
+ // jQuery deferred
558
+ var deferred;
559
+ if ($ && $.Deferred) {
560
+ deferred = $.Deferred();
561
+ }
562
+
563
+ // return immidiatly if res are passed in
564
+ if (o.resStore) {
565
+ resStore = o.resStore;
566
+ if (cb) cb(translate);
567
+ if (deferred) deferred.resolve();
568
+ if (deferred) return deferred.promise();
569
+ return;
570
+ }
571
+
572
+ // languages to load
573
+ var lngsToLoad = f.toLanguages(o.lng);
574
+ if (typeof o.preload === 'string') o.preload = [o.preload];
575
+ for (var i = 0, l = o.preload.length; i < l; i++) {
576
+ var pres = f.toLanguages(o.preload[i]);
577
+ for (var y = 0, len = pres.length; y < len; y++) {
578
+ if (lngsToLoad.indexOf(pres[y]) < 0) {
579
+ lngsToLoad.push(pres[y]);
580
+ }
581
+ }
582
+ }
583
+
584
+ // else load them
585
+ i18n.sync.load(lngsToLoad, o, function(err, store) {
586
+ resStore = store;
587
+
588
+ if (cb) cb(translate);
589
+ if (deferred) deferred.resolve();
590
+ });
591
+
592
+ if (deferred) return deferred.promise();
593
+ }
594
+ function preload(lngs, cb) {
595
+ if (typeof lngs === 'string') lngs = [lngs];
596
+ for (var i = 0, l = lngs.length; i < l; i++) {
597
+ if (o.preload.indexOf(lngs[i]) < 0) {
598
+ o.preload.push(lngs[i]);
599
+ }
600
+ }
601
+ return init(cb);
602
+ }
603
+
604
+ function addResourceBundle(lng, ns, resources) {
605
+ if (typeof ns !== 'string') {
606
+ resources = ns;
607
+ ns = o.ns.defaultNs;
608
+ }
609
+
610
+ resStore[lng] = resStore[lng] || {};
611
+ resStore[lng][ns] = resStore[lng][ns] || {};
612
+
613
+ f.extend(resStore[lng][ns], resources);
614
+ }
615
+
616
+ function setDefaultNamespace(ns) {
617
+ o.ns.defaultNs = ns;
618
+ }
619
+
620
+ function loadNamespace(namespace, cb) {
621
+ loadNamespaces([namespace], cb);
622
+ }
623
+
624
+ function loadNamespaces(namespaces, cb) {
625
+ var opts = {
626
+ dynamicLoad: o.dynamicLoad,
627
+ resGetPath: o.resGetPath,
628
+ getAsync: o.getAsync,
629
+ ns: { namespaces: namespaces, defaultNs: ''} /* new namespaces to load */
630
+ };
631
+
632
+ // languages to load
633
+ var lngsToLoad = f.toLanguages(o.lng);
634
+ if (typeof o.preload === 'string') o.preload = [o.preload];
635
+ for (var i = 0, l = o.preload.length; i < l; i++) {
636
+ var pres = f.toLanguages(o.preload[i]);
637
+ for (var y = 0, len = pres.length; y < len; y++) {
638
+ if (lngsToLoad.indexOf(pres[y]) < 0) {
639
+ lngsToLoad.push(pres[y]);
640
+ }
641
+ }
642
+ }
643
+
644
+ // check if we have to load
645
+ var lngNeedLoad = [];
646
+ for (var a = 0, lenA = lngsToLoad.length; a < lenA; a++) {
647
+ var needLoad = false;
648
+ var resSet = resStore[lngsToLoad[a]];
649
+ if (resSet) {
650
+ for (var b = 0, lenB = namespaces.length; b < lenB; b++) {
651
+ if (!resSet[namespaces[b]]) needLoad = true;
652
+ }
653
+ } else {
654
+ needLoad = true;
655
+ }
656
+
657
+ if (needLoad) lngNeedLoad.push(lngsToLoad[a]);
658
+ }
659
+
660
+ if (lngNeedLoad.length) {
661
+ i18n.sync._fetch(lngNeedLoad, opts, function(err, store) {
662
+ var todo = namespaces.length * lngNeedLoad.length;
663
+
664
+ // load each file individual
665
+ f.each(namespaces, function(nsIndex, nsValue) {
666
+ f.each(lngNeedLoad, function(lngIndex, lngValue) {
667
+ resStore[lngValue] = resStore[lngValue] || {};
668
+ resStore[lngValue][nsValue] = store[lngValue][nsValue];
669
+
670
+ todo--; // wait for all done befor callback
671
+ if (todo === 0 && cb) {
672
+ if (o.useLocalStorage) i18n.sync._storeLocal(resStore);
673
+ cb();
674
+ }
675
+ });
676
+ });
677
+ });
678
+ } else {
679
+ if (cb) cb();
680
+ }
681
+ }
682
+
683
+ function setLng(lng, cb) {
684
+ return init({lng: lng}, cb);
685
+ }
686
+
687
+ function lng() {
688
+ return currentLng;
689
+ }
690
+ function addJqueryFunct() {
691
+ // $.t shortcut
692
+ $.t = $.t || translate;
693
+
694
+ function parse(ele, key, options) {
695
+ if (key.length === 0) return;
696
+
697
+ var attr = 'text';
698
+
699
+ if (key.indexOf('[') === 0) {
700
+ var parts = key.split(']');
701
+ key = parts[1];
702
+ attr = parts[0].substr(1, parts[0].length-1);
703
+ }
704
+
705
+ if (key.indexOf(';') === key.length-1) {
706
+ key = key.substr(0, key.length-2);
707
+ }
708
+
709
+ var optionsToUse;
710
+ if (attr === 'html') {
711
+ optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.html() }, options) : options;
712
+ ele.html($.t(key, optionsToUse));
713
+ }
714
+ else if (attr === 'text') {
715
+ optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.text() }, options) : options;
716
+ ele.text($.t(key, optionsToUse));
717
+ } else {
718
+ optionsToUse = o.defaultValueFromContent ? $.extend({ defaultValue: ele.attr(attr) }, options) : options;
719
+ ele.attr(attr, $.t(key, optionsToUse));
720
+ }
721
+ }
722
+
723
+ function localize(ele, options) {
724
+ var key = ele.attr(o.selectorAttr);
725
+ if (!key) return;
726
+
727
+ var target = ele
728
+ , targetSelector = ele.data("i18n-target");
729
+ if (targetSelector) {
730
+ target = ele.find(targetSelector) || ele;
731
+ }
732
+
733
+ if (!options && o.useDataAttrOptions === true) {
734
+ options = ele.data("i18n-options");
735
+ }
736
+ options = options || {};
737
+
738
+ if (key.indexOf(';') <= key.length-1) {
739
+ var keys = key.split(';');
740
+
741
+ $.each(keys, function(m, k) {
742
+ parse(target, k, options);
743
+ });
744
+
745
+ } else {
746
+ parse(target, k, options);
747
+ }
748
+
749
+ if (o.useDataAttrOptions === true) ele.data("i18n-options", options);
750
+ }
751
+
752
+ // fn
753
+ $.fn.i18n = function (options) {
754
+ return this.each(function() {
755
+ // localize element itself
756
+ localize($(this), options);
757
+
758
+ // localize childs
759
+ var elements = $(this).find('[' + o.selectorAttr + ']');
760
+ elements.each(function() {
761
+ localize($(this), options);
762
+ });
763
+ });
764
+ };
765
+ }
766
+
767
+ function applyReplacement(str, replacementHash, nestedKey, options) {
768
+ options = options || replacementHash; // first call uses replacement hash combined with options
769
+ if (str.indexOf(options.interpolationPrefix || o.interpolationPrefix) < 0) return str;
770
+
771
+ var prefix = options.interpolationPrefix ? f.regexEscape(options.interpolationPrefix) : o.interpolationPrefixEscaped
772
+ , suffix = options.interpolationSuffix ? f.regexEscape(options.interpolationSuffix) : o.interpolationSuffixEscaped;
773
+
774
+ f.each(replacementHash, function(key, value) {
775
+ if (typeof value === 'object' && value !== null) {
776
+ str = applyReplacement(str, value, nestedKey ? nestedKey + o.keyseparator + key : key, options);
777
+ } else {
778
+ str = str.replace(new RegExp([prefix, nestedKey ? nestedKey + o.keyseparator + key : key, suffix].join(''), 'g'), value);
779
+ }
780
+ });
781
+ return str;
782
+ }
783
+
784
+ function applyReuse(translated, options) {
785
+ var comma = ',';
786
+ var options_open = '{';
787
+ var options_close = '}';
788
+
789
+ var opts = f.extend({}, options);
790
+ delete opts.postProcess;
791
+
792
+ while (translated.indexOf(o.reusePrefix) != -1) {
793
+ replacementCounter++;
794
+ if (replacementCounter > o.maxRecursion) { break; } // safety net for too much recursion
795
+ var index_of_opening = translated.indexOf(o.reusePrefix);
796
+ var index_of_end_of_closing = translated.indexOf(o.reuseSuffix, index_of_opening) + o.reuseSuffix.length;
797
+ var token = translated.substring(index_of_opening, index_of_end_of_closing);
798
+ var token_without_symbols = token.replace(o.reusePrefix, '').replace(o.reuseSuffix, '');
799
+
800
+
801
+ if (token_without_symbols.indexOf(comma) != -1) {
802
+ var index_of_token_end_of_closing = token_without_symbols.indexOf(comma);
803
+ if (token_without_symbols.indexOf(options_open, index_of_token_end_of_closing) != -1 && token_without_symbols.indexOf(options_close, index_of_token_end_of_closing) != -1) {
804
+ var index_of_opts_opening = token_without_symbols.indexOf(options_open, index_of_token_end_of_closing);
805
+ var index_of_opts_end_of_closing = token_without_symbols.indexOf(options_close, index_of_opts_opening) + options_close.length;
806
+ try {
807
+ opts = f.extend(opts, JSON.parse(token_without_symbols.substring(index_of_opts_opening, index_of_opts_end_of_closing)));
808
+ token_without_symbols = token_without_symbols.substring(0, index_of_token_end_of_closing);
809
+ } catch (e) {
810
+ }
811
+ }
812
+ }
813
+
814
+ var translated_token = _translate(token_without_symbols, opts);
815
+ translated = translated.replace(token, translated_token);
816
+ }
817
+ return translated;
818
+ }
819
+
820
+ function hasContext(options) {
821
+ return (options.context && typeof options.context == 'string');
822
+ }
823
+
824
+ function needsPlural(options) {
825
+ return (options.count !== undefined && typeof options.count != 'string' && options.count !== 1);
826
+ }
827
+
828
+ function translate(key, options){
829
+ replacementCounter = 0;
830
+ return _translate(key, options);
831
+ }
832
+
833
+ function _translate(key, options){
834
+ options = options || {};
835
+
836
+ if (!resStore) { return notfound; } // no resStore to translate from
837
+
838
+ var optionWithoutCount, translated
839
+ , notfound = options.defaultValue || key
840
+ , lngs = languages;
841
+
842
+ if (options.lng) {
843
+ lngs = f.toLanguages(options.lng);
844
+
845
+ if (!resStore[lngs[0]]) {
846
+ var oldAsync = o.getAsync;
847
+ o.getAsync = false;
848
+
849
+ i18n.sync.load(lngs, o, function(err, store) {
850
+ f.extend(resStore, store);
851
+ o.getAsync = oldAsync;
852
+ });
853
+ }
854
+ }
855
+
856
+ var ns = options.ns || o.ns.defaultNs;
857
+ if (key.indexOf(o.nsseparator) > -1) {
858
+ var parts = key.split(o.nsseparator);
859
+ ns = parts[0];
860
+ key = parts[1];
861
+ }
862
+
863
+ if (hasContext(options)) {
864
+ optionWithoutCount = f.extend({}, options);
865
+ delete optionWithoutCount.context;
866
+ optionWithoutCount.defaultValue = o.contextNotFound;
867
+
868
+ var contextKey = ns + o.nsseparator + key + '_' + options.context;
869
+
870
+ translated = translate(contextKey, optionWithoutCount);
871
+ if (translated != o.contextNotFound) {
872
+ return applyReplacement(translated, { context: options.context }); // apply replacement for context only
873
+ } // else continue translation with original/nonContext key
874
+ }
875
+
876
+ if (needsPlural(options)) {
877
+ optionWithoutCount = f.extend({}, options);
878
+ delete optionWithoutCount.count;
879
+ optionWithoutCount.defaultValue = o.pluralNotFound;
880
+
881
+ var pluralKey = ns + o.nsseparator + key + o.pluralSuffix;
882
+ var pluralExtension = pluralExtensions.get(currentLng, options.count);
883
+ if (pluralExtension >= 0) {
884
+ pluralKey = pluralKey + '_' + pluralExtension;
885
+ } else if (pluralExtension === 1) {
886
+ pluralKey = ns + o.nsseparator + key; // singular
887
+ }
888
+
889
+ translated = translate(pluralKey, optionWithoutCount);
890
+ if (translated != o.pluralNotFound) {
891
+ return applyReplacement(translated, { count: options.count }); // apply replacement for count only
892
+ } // else continue translation with original/singular key
893
+ }
894
+
895
+ var found;
896
+ var keys = key.split(o.keyseparator);
897
+ for (var i = 0, len = lngs.length; i < len; i++ ) {
898
+ if (found) break;
899
+
900
+ var l = lngs[i];
901
+
902
+ var x = 0;
903
+ var value = resStore[l] && resStore[l][ns];
904
+ while (keys[x]) {
905
+ value = value && value[keys[x]];
906
+ x++;
907
+ }
908
+ if (value !== undefined) {
909
+ if (typeof value === 'string') {
910
+ value = applyReplacement(value, options);
911
+ value = applyReuse(value, options);
912
+ } else if (Object.prototype.toString.apply(value) === '[object Array]' && !o.returnObjectTrees && !options.returnObjectTrees) {
913
+ value = value.join('\n');
914
+ value = applyReplacement(value, options);
915
+ value = applyReuse(value, options);
916
+ } else {
917
+ if (!o.returnObjectTrees && !options.returnObjectTrees) {
918
+ value = 'key \'' + ns + ':' + key + ' (' + l + ')\' ' +
919
+ 'returned a object instead of string.';
920
+ f.log(value);
921
+ } else {
922
+ var copy = {}; // apply child translation on a copy
923
+ for (var m in value) {
924
+ // apply translation on childs
925
+ copy[m] = _translate(ns + o.nsseparator + key + o.keyseparator + m, options);
926
+ }
927
+ value = copy;
928
+ }
929
+ }
930
+ found = value;
931
+ }
932
+ }
933
+
934
+ if (found === undefined && o.fallbackToDefaultNS) {
935
+ found = _translate(key, options);
936
+ }
937
+
938
+ if (found === undefined && o.sendMissing) {
939
+ if (options.lng) {
940
+ sync.postMissing(lngs[0], ns, key, notfound, lngs);
941
+ } else {
942
+ sync.postMissing(o.lng, ns, key, notfound, lngs);
943
+ }
944
+ }
945
+
946
+ var postProcessor = options.postProcess || o.postProcess;
947
+ if (found !== undefined && postProcessor) {
948
+ if (postProcessors[postProcessor]) {
949
+ found = postProcessors[postProcessor](found, key, options);
950
+ }
951
+ }
952
+
953
+ if (found === undefined) {
954
+ notfound = applyReplacement(notfound, options);
955
+ notfound = applyReuse(notfound, options);
956
+ }
957
+
958
+ return (found !== undefined) ? found : notfound;
959
+ }
960
+
961
+ function detectLanguage() {
962
+ var detectedLng;
963
+
964
+ // get from qs
965
+ var qsParm = [];
966
+ if (typeof window !== 'undefined') {
967
+ (function() {
968
+ var query = window.location.search.substring(1);
969
+ var parms = query.split('&');
970
+ for (var i=0; i<parms.length; i++) {
971
+ var pos = parms[i].indexOf('=');
972
+ if (pos > 0) {
973
+ var key = parms[i].substring(0,pos);
974
+ var val = parms[i].substring(pos+1);
975
+ qsParm[key] = val;
976
+ }
977
+ }
978
+ })();
979
+ if (qsParm[o.detectLngQS]) {
980
+ detectedLng = qsParm[o.detectLngQS];
981
+ }
982
+ }
983
+
984
+ // get from cookie
985
+ if (!detectedLng && typeof document !== 'undefined' && o.useCookie ) {
986
+ var c = f.cookie.read(o.cookieName);
987
+ if (c) detectedLng = c;
988
+ }
989
+
990
+ // get from navigator
991
+ if (!detectedLng && typeof navigator !== 'undefined') {
992
+ detectedLng = (navigator.language) ? navigator.language : navigator.userLanguage;
993
+ }
994
+
995
+ return detectedLng;
996
+ }
997
+ var sync = {
998
+
999
+ load: function(lngs, options, cb) {
1000
+ if (options.useLocalStorage) {
1001
+ sync._loadLocal(lngs, options, function(err, store) {
1002
+ var missingLngs = [];
1003
+ for (var i = 0, len = lngs.length; i < len; i++) {
1004
+ if (!store[lngs[i]]) missingLngs.push(lngs[i]);
1005
+ }
1006
+
1007
+ if (missingLngs.length > 0) {
1008
+ sync._fetch(missingLngs, options, function(err, fetched) {
1009
+ f.extend(store, fetched);
1010
+ sync._storeLocal(fetched);
1011
+
1012
+ cb(null, store);
1013
+ });
1014
+ } else {
1015
+ cb(null, store);
1016
+ }
1017
+ });
1018
+ } else {
1019
+ sync._fetch(lngs, options, function(err, store){
1020
+ cb(null, store);
1021
+ });
1022
+ }
1023
+ },
1024
+
1025
+ _loadLocal: function(lngs, options, cb) {
1026
+ var store = {}
1027
+ , nowMS = new Date().getTime();
1028
+
1029
+ if(window.localStorage) {
1030
+
1031
+ var todo = lngs.length;
1032
+
1033
+ f.each(lngs, function(key, lng) {
1034
+ var local = window.localStorage.getItem('res_' + lng);
1035
+
1036
+ if (local) {
1037
+ local = JSON.parse(local);
1038
+
1039
+ if (local.i18nStamp && local.i18nStamp + options.localStorageExpirationTime > nowMS) {
1040
+ store[lng] = local;
1041
+ }
1042
+ }
1043
+
1044
+ todo--; // wait for all done befor callback
1045
+ if (todo === 0) cb(null, store);
1046
+ });
1047
+ }
1048
+ },
1049
+
1050
+ _storeLocal: function(store) {
1051
+ if(window.localStorage) {
1052
+ for (var m in store) {
1053
+ store[m].i18nStamp = new Date().getTime();
1054
+ window.localStorage.setItem('res_' + m, JSON.stringify(store[m]));
1055
+ }
1056
+ }
1057
+ return;
1058
+ },
1059
+
1060
+ _fetch: function(lngs, options, cb) {
1061
+ var ns = options.ns
1062
+ , store = {};
1063
+
1064
+ if (!options.dynamicLoad) {
1065
+ var todo = ns.namespaces.length * lngs.length
1066
+ , errors;
1067
+
1068
+ // load each file individual
1069
+ f.each(ns.namespaces, function(nsIndex, nsValue) {
1070
+ f.each(lngs, function(lngIndex, lngValue) {
1071
+
1072
+ // Call this once our translation has returned.
1073
+ var loadComplete = function(err, data) {
1074
+ if (err) {
1075
+ errors = errors || [];
1076
+ errors.push(err);
1077
+ }
1078
+ store[lngValue] = store[lngValue] || {};
1079
+ store[lngValue][nsValue] = data;
1080
+
1081
+ todo--; // wait for all done befor callback
1082
+ if (todo === 0) cb(errors, store);
1083
+ };
1084
+
1085
+ if(typeof options.customLoad == 'function'){
1086
+ // Use the specified custom callback.
1087
+ options.customLoad(lngValue, nsValue, options, loadComplete);
1088
+ } else {
1089
+ //~ // Use our inbuilt sync.
1090
+ sync._fetchOne(lngValue, nsValue, options, loadComplete);
1091
+ }
1092
+ });
1093
+ });
1094
+ } else {
1095
+ var url = applyReplacement(options.resGetPath, { lng: lngs.join('+'), ns: ns.namespaces.join('+') });
1096
+ // load all needed stuff once
1097
+ f.ajax({
1098
+ url: url,
1099
+ success: function(data, status, xhr) {
1100
+ f.log('loaded: ' + url);
1101
+ cb(null, data);
1102
+ },
1103
+ error : function(xhr, status, error) {
1104
+ f.log('failed loading: ' + url);
1105
+ cb('failed loading resource.json error: ' + error);
1106
+ },
1107
+ dataType: "json",
1108
+ async : options.getAsync
1109
+ });
1110
+ }
1111
+ },
1112
+
1113
+ _fetchOne: function(lng, ns, options, done) {
1114
+ var url = applyReplacement(options.resGetPath, { lng: lng, ns: ns });
1115
+ f.ajax({
1116
+ url: url,
1117
+ success: function(data, status, xhr) {
1118
+ f.log('loaded: ' + url);
1119
+ done(null, data);
1120
+ },
1121
+ error : function(xhr, status, error) {
1122
+ f.log('failed loading: ' + url);
1123
+ done(error, {});
1124
+ },
1125
+ dataType: "json",
1126
+ async : options.getAsync
1127
+ });
1128
+ },
1129
+
1130
+ postMissing: function(lng, ns, key, defaultValue, lngs) {
1131
+ var payload = {};
1132
+ payload[key] = defaultValue;
1133
+
1134
+ var urls = [];
1135
+
1136
+ if (o.sendMissingTo === 'fallback') {
1137
+ urls.push({lng: o.fallbackLng, url: applyReplacement(o.resPostPath, { lng: o.fallbackLng, ns: ns })});
1138
+ } else if (o.sendMissingTo === 'current') {
1139
+ urls.push({lng: lng, url: applyReplacement(o.resPostPath, { lng: lng, ns: ns })});
1140
+ } else if (o.sendMissingTo === 'all') {
1141
+ for (var i = 0, l = lngs.length; i < l; i++) {
1142
+ urls.push({lng: lngs[i], url: applyReplacement(o.resPostPath, { lng: lngs[i], ns: ns })});
1143
+ }
1144
+ }
1145
+
1146
+ for (var y = 0, len = urls.length; y < len; y++) {
1147
+ var item = urls[y];
1148
+ f.ajax({
1149
+ url: item.url,
1150
+ type: o.sendType,
1151
+ data: payload,
1152
+ success: function(data, status, xhr) {
1153
+ f.log('posted missing key \'' + key + '\' to: ' + item.url);
1154
+
1155
+ // add key to resStore
1156
+ var keys = key.split('.');
1157
+ var x = 0;
1158
+ var value = resStore[item.lng][ns];
1159
+ while (keys[x]) {
1160
+ if (x === keys.length - 1) {
1161
+ value = value[keys[x]] = defaultValue;
1162
+ } else {
1163
+ value = value[keys[x]] = value[keys[x]] || {};
1164
+ }
1165
+ x++;
1166
+ }
1167
+ },
1168
+ error : function(xhr, status, error) {
1169
+ f.log('failed posting missing key \'' + key + '\' to: ' + item.url);
1170
+ },
1171
+ dataType: "json",
1172
+ async : o.postAsync
1173
+ });
1174
+ }
1175
+ }
1176
+ };
1177
+
1178
+ // definition http://translate.sourceforge.net/wiki/l10n/pluralforms
1179
+ var pluralExtensions = {
1180
+
1181
+ rules: {
1182
+ "ach": {
1183
+ "name": "Acholi",
1184
+ "numbers": [
1185
+ 1,
1186
+ 2
1187
+ ],
1188
+ "plurals": function(n) { return Number(n > 1); }
1189
+ },
1190
+ "af": {
1191
+ "name": "Afrikaans",
1192
+ "numbers": [
1193
+ 1,
1194
+ 2
1195
+ ],
1196
+ "plurals": function(n) { return Number(n != 1); }
1197
+ },
1198
+ "ak": {
1199
+ "name": "Akan",
1200
+ "numbers": [
1201
+ 1,
1202
+ 2
1203
+ ],
1204
+ "plurals": function(n) { return Number(n > 1); }
1205
+ },
1206
+ "am": {
1207
+ "name": "Amharic",
1208
+ "numbers": [
1209
+ 1,
1210
+ 2
1211
+ ],
1212
+ "plurals": function(n) { return Number(n > 1); }
1213
+ },
1214
+ "an": {
1215
+ "name": "Aragonese",
1216
+ "numbers": [
1217
+ 1,
1218
+ 2
1219
+ ],
1220
+ "plurals": function(n) { return Number(n != 1); }
1221
+ },
1222
+ "ar": {
1223
+ "name": "Arabic",
1224
+ "numbers": [
1225
+ 0,
1226
+ 1,
1227
+ 2,
1228
+ 3,
1229
+ 11,
1230
+ 100
1231
+ ],
1232
+ "plurals": function(n) { return Number(n===0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); }
1233
+ },
1234
+ "arn": {
1235
+ "name": "Mapudungun",
1236
+ "numbers": [
1237
+ 1,
1238
+ 2
1239
+ ],
1240
+ "plurals": function(n) { return Number(n > 1); }
1241
+ },
1242
+ "ast": {
1243
+ "name": "Asturian",
1244
+ "numbers": [
1245
+ 1,
1246
+ 2
1247
+ ],
1248
+ "plurals": function(n) { return Number(n != 1); }
1249
+ },
1250
+ "ay": {
1251
+ "name": "Aymar\u00e1",
1252
+ "numbers": [
1253
+ 1
1254
+ ],
1255
+ "plurals": function(n) { return 0; }
1256
+ },
1257
+ "az": {
1258
+ "name": "Azerbaijani",
1259
+ "numbers": [
1260
+ 1,
1261
+ 2
1262
+ ],
1263
+ "plurals": function(n) { return Number(n != 1); }
1264
+ },
1265
+ "be": {
1266
+ "name": "Belarusian",
1267
+ "numbers": [
1268
+ 1,
1269
+ 2,
1270
+ 5
1271
+ ],
1272
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
1273
+ },
1274
+ "bg": {
1275
+ "name": "Bulgarian",
1276
+ "numbers": [
1277
+ 1,
1278
+ 2
1279
+ ],
1280
+ "plurals": function(n) { return Number(n != 1); }
1281
+ },
1282
+ "bn": {
1283
+ "name": "Bengali",
1284
+ "numbers": [
1285
+ 1,
1286
+ 2
1287
+ ],
1288
+ "plurals": function(n) { return Number(n != 1); }
1289
+ },
1290
+ "bo": {
1291
+ "name": "Tibetan",
1292
+ "numbers": [
1293
+ 1
1294
+ ],
1295
+ "plurals": function(n) { return 0; }
1296
+ },
1297
+ "br": {
1298
+ "name": "Breton",
1299
+ "numbers": [
1300
+ 1,
1301
+ 2
1302
+ ],
1303
+ "plurals": function(n) { return Number(n > 1); }
1304
+ },
1305
+ "bs": {
1306
+ "name": "Bosnian",
1307
+ "numbers": [
1308
+ 1,
1309
+ 2,
1310
+ 5
1311
+ ],
1312
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
1313
+ },
1314
+ "ca": {
1315
+ "name": "Catalan",
1316
+ "numbers": [
1317
+ 1,
1318
+ 2
1319
+ ],
1320
+ "plurals": function(n) { return Number(n != 1); }
1321
+ },
1322
+ "cgg": {
1323
+ "name": "Chiga",
1324
+ "numbers": [
1325
+ 1
1326
+ ],
1327
+ "plurals": function(n) { return 0; }
1328
+ },
1329
+ "cs": {
1330
+ "name": "Czech",
1331
+ "numbers": [
1332
+ 1,
1333
+ 2,
1334
+ 5
1335
+ ],
1336
+ "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
1337
+ },
1338
+ "csb": {
1339
+ "name": "Kashubian",
1340
+ "numbers": [
1341
+ 1,
1342
+ 2,
1343
+ 5
1344
+ ],
1345
+ "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
1346
+ },
1347
+ "cy": {
1348
+ "name": "Welsh",
1349
+ "numbers": [
1350
+ 1,
1351
+ 2,
1352
+ 3,
1353
+ 8
1354
+ ],
1355
+ "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3); }
1356
+ },
1357
+ "da": {
1358
+ "name": "Danish",
1359
+ "numbers": [
1360
+ 1,
1361
+ 2
1362
+ ],
1363
+ "plurals": function(n) { return Number(n != 1); }
1364
+ },
1365
+ "de": {
1366
+ "name": "German",
1367
+ "numbers": [
1368
+ 1,
1369
+ 2
1370
+ ],
1371
+ "plurals": function(n) { return Number(n != 1); }
1372
+ },
1373
+ "dz": {
1374
+ "name": "Dzongkha",
1375
+ "numbers": [
1376
+ 1
1377
+ ],
1378
+ "plurals": function(n) { return 0; }
1379
+ },
1380
+ "el": {
1381
+ "name": "Greek",
1382
+ "numbers": [
1383
+ 1,
1384
+ 2
1385
+ ],
1386
+ "plurals": function(n) { return Number(n != 1); }
1387
+ },
1388
+ "en": {
1389
+ "name": "English",
1390
+ "numbers": [
1391
+ 1,
1392
+ 2
1393
+ ],
1394
+ "plurals": function(n) { return Number(n != 1); }
1395
+ },
1396
+ "eo": {
1397
+ "name": "Esperanto",
1398
+ "numbers": [
1399
+ 1,
1400
+ 2
1401
+ ],
1402
+ "plurals": function(n) { return Number(n != 1); }
1403
+ },
1404
+ "es": {
1405
+ "name": "Spanish",
1406
+ "numbers": [
1407
+ 1,
1408
+ 2
1409
+ ],
1410
+ "plurals": function(n) { return Number(n != 1); }
1411
+ },
1412
+ "es_ar": {
1413
+ "name": "Argentinean Spanish",
1414
+ "numbers": [
1415
+ 1,
1416
+ 2
1417
+ ],
1418
+ "plurals": function(n) { return Number(n != 1); }
1419
+ },
1420
+ "et": {
1421
+ "name": "Estonian",
1422
+ "numbers": [
1423
+ 1,
1424
+ 2
1425
+ ],
1426
+ "plurals": function(n) { return Number(n != 1); }
1427
+ },
1428
+ "eu": {
1429
+ "name": "Basque",
1430
+ "numbers": [
1431
+ 1,
1432
+ 2
1433
+ ],
1434
+ "plurals": function(n) { return Number(n != 1); }
1435
+ },
1436
+ "fa": {
1437
+ "name": "Persian",
1438
+ "numbers": [
1439
+ 1
1440
+ ],
1441
+ "plurals": function(n) { return 0; }
1442
+ },
1443
+ "fi": {
1444
+ "name": "Finnish",
1445
+ "numbers": [
1446
+ 1,
1447
+ 2
1448
+ ],
1449
+ "plurals": function(n) { return Number(n != 1); }
1450
+ },
1451
+ "fil": {
1452
+ "name": "Filipino",
1453
+ "numbers": [
1454
+ 1,
1455
+ 2
1456
+ ],
1457
+ "plurals": function(n) { return Number(n > 1); }
1458
+ },
1459
+ "fo": {
1460
+ "name": "Faroese",
1461
+ "numbers": [
1462
+ 1,
1463
+ 2
1464
+ ],
1465
+ "plurals": function(n) { return Number(n != 1); }
1466
+ },
1467
+ "fr": {
1468
+ "name": "French",
1469
+ "numbers": [
1470
+ 1,
1471
+ 2
1472
+ ],
1473
+ "plurals": function(n) { return Number(n > 1); }
1474
+ },
1475
+ "fur": {
1476
+ "name": "Friulian",
1477
+ "numbers": [
1478
+ 1,
1479
+ 2
1480
+ ],
1481
+ "plurals": function(n) { return Number(n != 1); }
1482
+ },
1483
+ "fy": {
1484
+ "name": "Frisian",
1485
+ "numbers": [
1486
+ 1,
1487
+ 2
1488
+ ],
1489
+ "plurals": function(n) { return Number(n != 1); }
1490
+ },
1491
+ "ga": {
1492
+ "name": "Irish",
1493
+ "numbers": [
1494
+ 1,
1495
+ 2,
1496
+ 3,
1497
+ 7,
1498
+ 11
1499
+ ],
1500
+ "plurals": function(n) { return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;}
1501
+ },
1502
+ "gd": {
1503
+ "name": "Scottish Gaelic",
1504
+ "numbers": [
1505
+ 1,
1506
+ 2,
1507
+ 3,
1508
+ 20
1509
+ ],
1510
+ "plurals": function(n) { return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3); }
1511
+ },
1512
+ "gl": {
1513
+ "name": "Galician",
1514
+ "numbers": [
1515
+ 1,
1516
+ 2
1517
+ ],
1518
+ "plurals": function(n) { return Number(n != 1); }
1519
+ },
1520
+ "gu": {
1521
+ "name": "Gujarati",
1522
+ "numbers": [
1523
+ 1,
1524
+ 2
1525
+ ],
1526
+ "plurals": function(n) { return Number(n != 1); }
1527
+ },
1528
+ "gun": {
1529
+ "name": "Gun",
1530
+ "numbers": [
1531
+ 1,
1532
+ 2
1533
+ ],
1534
+ "plurals": function(n) { return Number(n > 1); }
1535
+ },
1536
+ "ha": {
1537
+ "name": "Hausa",
1538
+ "numbers": [
1539
+ 1,
1540
+ 2
1541
+ ],
1542
+ "plurals": function(n) { return Number(n != 1); }
1543
+ },
1544
+ "he": {
1545
+ "name": "Hebrew",
1546
+ "numbers": [
1547
+ 1,
1548
+ 2
1549
+ ],
1550
+ "plurals": function(n) { return Number(n != 1); }
1551
+ },
1552
+ "hi": {
1553
+ "name": "Hindi",
1554
+ "numbers": [
1555
+ 1,
1556
+ 2
1557
+ ],
1558
+ "plurals": function(n) { return Number(n != 1); }
1559
+ },
1560
+ "hr": {
1561
+ "name": "Croatian",
1562
+ "numbers": [
1563
+ 1,
1564
+ 2,
1565
+ 5
1566
+ ],
1567
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
1568
+ },
1569
+ "hu": {
1570
+ "name": "Hungarian",
1571
+ "numbers": [
1572
+ 1,
1573
+ 2
1574
+ ],
1575
+ "plurals": function(n) { return Number(n != 1); }
1576
+ },
1577
+ "hy": {
1578
+ "name": "Armenian",
1579
+ "numbers": [
1580
+ 1,
1581
+ 2
1582
+ ],
1583
+ "plurals": function(n) { return Number(n != 1); }
1584
+ },
1585
+ "ia": {
1586
+ "name": "Interlingua",
1587
+ "numbers": [
1588
+ 1,
1589
+ 2
1590
+ ],
1591
+ "plurals": function(n) { return Number(n != 1); }
1592
+ },
1593
+ "id": {
1594
+ "name": "Indonesian",
1595
+ "numbers": [
1596
+ 1
1597
+ ],
1598
+ "plurals": function(n) { return 0; }
1599
+ },
1600
+ "is": {
1601
+ "name": "Icelandic",
1602
+ "numbers": [
1603
+ 1,
1604
+ 2
1605
+ ],
1606
+ "plurals": function(n) { return Number(n%10!=1 || n%100==11); }
1607
+ },
1608
+ "it": {
1609
+ "name": "Italian",
1610
+ "numbers": [
1611
+ 1,
1612
+ 2
1613
+ ],
1614
+ "plurals": function(n) { return Number(n != 1); }
1615
+ },
1616
+ "ja": {
1617
+ "name": "Japanese",
1618
+ "numbers": [
1619
+ 1
1620
+ ],
1621
+ "plurals": function(n) { return 0; }
1622
+ },
1623
+ "jbo": {
1624
+ "name": "Lojban",
1625
+ "numbers": [
1626
+ 1
1627
+ ],
1628
+ "plurals": function(n) { return 0; }
1629
+ },
1630
+ "jv": {
1631
+ "name": "Javanese",
1632
+ "numbers": [
1633
+ 0,
1634
+ 1
1635
+ ],
1636
+ "plurals": function(n) { return Number(n !== 0); }
1637
+ },
1638
+ "ka": {
1639
+ "name": "Georgian",
1640
+ "numbers": [
1641
+ 1
1642
+ ],
1643
+ "plurals": function(n) { return 0; }
1644
+ },
1645
+ "kk": {
1646
+ "name": "Kazakh",
1647
+ "numbers": [
1648
+ 1
1649
+ ],
1650
+ "plurals": function(n) { return 0; }
1651
+ },
1652
+ "km": {
1653
+ "name": "Khmer",
1654
+ "numbers": [
1655
+ 1
1656
+ ],
1657
+ "plurals": function(n) { return 0; }
1658
+ },
1659
+ "kn": {
1660
+ "name": "Kannada",
1661
+ "numbers": [
1662
+ 1,
1663
+ 2
1664
+ ],
1665
+ "plurals": function(n) { return Number(n != 1); }
1666
+ },
1667
+ "ko": {
1668
+ "name": "Korean",
1669
+ "numbers": [
1670
+ 1
1671
+ ],
1672
+ "plurals": function(n) { return 0; }
1673
+ },
1674
+ "ku": {
1675
+ "name": "Kurdish",
1676
+ "numbers": [
1677
+ 1,
1678
+ 2
1679
+ ],
1680
+ "plurals": function(n) { return Number(n != 1); }
1681
+ },
1682
+ "kw": {
1683
+ "name": "Cornish",
1684
+ "numbers": [
1685
+ 1,
1686
+ 2,
1687
+ 3,
1688
+ 4
1689
+ ],
1690
+ "plurals": function(n) { return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3); }
1691
+ },
1692
+ "ky": {
1693
+ "name": "Kyrgyz",
1694
+ "numbers": [
1695
+ 1
1696
+ ],
1697
+ "plurals": function(n) { return 0; }
1698
+ },
1699
+ "lb": {
1700
+ "name": "Letzeburgesch",
1701
+ "numbers": [
1702
+ 1,
1703
+ 2
1704
+ ],
1705
+ "plurals": function(n) { return Number(n != 1); }
1706
+ },
1707
+ "ln": {
1708
+ "name": "Lingala",
1709
+ "numbers": [
1710
+ 1,
1711
+ 2
1712
+ ],
1713
+ "plurals": function(n) { return Number(n > 1); }
1714
+ },
1715
+ "lo": {
1716
+ "name": "Lao",
1717
+ "numbers": [
1718
+ 1
1719
+ ],
1720
+ "plurals": function(n) { return 0; }
1721
+ },
1722
+ "lt": {
1723
+ "name": "Lithuanian",
1724
+ "numbers": [
1725
+ 1,
1726
+ 2,
1727
+ 10
1728
+ ],
1729
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2); }
1730
+ },
1731
+ "lv": {
1732
+ "name": "Latvian",
1733
+ "numbers": [
1734
+ 0,
1735
+ 1,
1736
+ 2
1737
+ ],
1738
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2); }
1739
+ },
1740
+ "mai": {
1741
+ "name": "Maithili",
1742
+ "numbers": [
1743
+ 1,
1744
+ 2
1745
+ ],
1746
+ "plurals": function(n) { return Number(n != 1); }
1747
+ },
1748
+ "mfe": {
1749
+ "name": "Mauritian Creole",
1750
+ "numbers": [
1751
+ 1,
1752
+ 2
1753
+ ],
1754
+ "plurals": function(n) { return Number(n > 1); }
1755
+ },
1756
+ "mg": {
1757
+ "name": "Malagasy",
1758
+ "numbers": [
1759
+ 1,
1760
+ 2
1761
+ ],
1762
+ "plurals": function(n) { return Number(n > 1); }
1763
+ },
1764
+ "mi": {
1765
+ "name": "Maori",
1766
+ "numbers": [
1767
+ 1,
1768
+ 2
1769
+ ],
1770
+ "plurals": function(n) { return Number(n > 1); }
1771
+ },
1772
+ "mk": {
1773
+ "name": "Macedonian",
1774
+ "numbers": [
1775
+ 1,
1776
+ 2
1777
+ ],
1778
+ "plurals": function(n) { return Number(n==1 || n%10==1 ? 0 : 1); }
1779
+ },
1780
+ "ml": {
1781
+ "name": "Malayalam",
1782
+ "numbers": [
1783
+ 1,
1784
+ 2
1785
+ ],
1786
+ "plurals": function(n) { return Number(n != 1); }
1787
+ },
1788
+ "mn": {
1789
+ "name": "Mongolian",
1790
+ "numbers": [
1791
+ 1,
1792
+ 2
1793
+ ],
1794
+ "plurals": function(n) { return Number(n != 1); }
1795
+ },
1796
+ "mnk": {
1797
+ "name": "Mandinka",
1798
+ "numbers": [
1799
+ 0,
1800
+ 1,
1801
+ 2
1802
+ ],
1803
+ "plurals": function(n) { return Number(0 ? 0 : n==1 ? 1 : 2); }
1804
+ },
1805
+ "mr": {
1806
+ "name": "Marathi",
1807
+ "numbers": [
1808
+ 1,
1809
+ 2
1810
+ ],
1811
+ "plurals": function(n) { return Number(n != 1); }
1812
+ },
1813
+ "ms": {
1814
+ "name": "Malay",
1815
+ "numbers": [
1816
+ 1
1817
+ ],
1818
+ "plurals": function(n) { return 0; }
1819
+ },
1820
+ "mt": {
1821
+ "name": "Maltese",
1822
+ "numbers": [
1823
+ 1,
1824
+ 2,
1825
+ 11,
1826
+ 20
1827
+ ],
1828
+ "plurals": function(n) { return Number(n==1 ? 0 : n===0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3); }
1829
+ },
1830
+ "nah": {
1831
+ "name": "Nahuatl",
1832
+ "numbers": [
1833
+ 1,
1834
+ 2
1835
+ ],
1836
+ "plurals": function(n) { return Number(n != 1); }
1837
+ },
1838
+ "nap": {
1839
+ "name": "Neapolitan",
1840
+ "numbers": [
1841
+ 1,
1842
+ 2
1843
+ ],
1844
+ "plurals": function(n) { return Number(n != 1); }
1845
+ },
1846
+ "nb": {
1847
+ "name": "Norwegian Bokmal",
1848
+ "numbers": [
1849
+ 1,
1850
+ 2
1851
+ ],
1852
+ "plurals": function(n) { return Number(n != 1); }
1853
+ },
1854
+ "ne": {
1855
+ "name": "Nepali",
1856
+ "numbers": [
1857
+ 1,
1858
+ 2
1859
+ ],
1860
+ "plurals": function(n) { return Number(n != 1); }
1861
+ },
1862
+ "nl": {
1863
+ "name": "Dutch",
1864
+ "numbers": [
1865
+ 1,
1866
+ 2
1867
+ ],
1868
+ "plurals": function(n) { return Number(n != 1); }
1869
+ },
1870
+ "nn": {
1871
+ "name": "Norwegian Nynorsk",
1872
+ "numbers": [
1873
+ 1,
1874
+ 2
1875
+ ],
1876
+ "plurals": function(n) { return Number(n != 1); }
1877
+ },
1878
+ "no": {
1879
+ "name": "Norwegian",
1880
+ "numbers": [
1881
+ 1,
1882
+ 2
1883
+ ],
1884
+ "plurals": function(n) { return Number(n != 1); }
1885
+ },
1886
+ "nso": {
1887
+ "name": "Northern Sotho",
1888
+ "numbers": [
1889
+ 1,
1890
+ 2
1891
+ ],
1892
+ "plurals": function(n) { return Number(n != 1); }
1893
+ },
1894
+ "oc": {
1895
+ "name": "Occitan",
1896
+ "numbers": [
1897
+ 1,
1898
+ 2
1899
+ ],
1900
+ "plurals": function(n) { return Number(n > 1); }
1901
+ },
1902
+ "or": {
1903
+ "name": "Oriya",
1904
+ "numbers": [
1905
+ 2,
1906
+ 1
1907
+ ],
1908
+ "plurals": function(n) { return Number(n != 1); }
1909
+ },
1910
+ "pa": {
1911
+ "name": "Punjabi",
1912
+ "numbers": [
1913
+ 1,
1914
+ 2
1915
+ ],
1916
+ "plurals": function(n) { return Number(n != 1); }
1917
+ },
1918
+ "pap": {
1919
+ "name": "Papiamento",
1920
+ "numbers": [
1921
+ 1,
1922
+ 2
1923
+ ],
1924
+ "plurals": function(n) { return Number(n != 1); }
1925
+ },
1926
+ "pl": {
1927
+ "name": "Polish",
1928
+ "numbers": [
1929
+ 1,
1930
+ 2,
1931
+ 5
1932
+ ],
1933
+ "plurals": function(n) { return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
1934
+ },
1935
+ "pms": {
1936
+ "name": "Piemontese",
1937
+ "numbers": [
1938
+ 1,
1939
+ 2
1940
+ ],
1941
+ "plurals": function(n) { return Number(n != 1); }
1942
+ },
1943
+ "ps": {
1944
+ "name": "Pashto",
1945
+ "numbers": [
1946
+ 1,
1947
+ 2
1948
+ ],
1949
+ "plurals": function(n) { return Number(n != 1); }
1950
+ },
1951
+ "pt": {
1952
+ "name": "Portuguese",
1953
+ "numbers": [
1954
+ 1,
1955
+ 2
1956
+ ],
1957
+ "plurals": function(n) { return Number(n != 1); }
1958
+ },
1959
+ "pt_br": {
1960
+ "name": "Brazilian Portuguese",
1961
+ "numbers": [
1962
+ 1,
1963
+ 2
1964
+ ],
1965
+ "plurals": function(n) { return Number(n != 1); }
1966
+ },
1967
+ "rm": {
1968
+ "name": "Romansh",
1969
+ "numbers": [
1970
+ 1,
1971
+ 2
1972
+ ],
1973
+ "plurals": function(n) { return Number(n != 1); }
1974
+ },
1975
+ "ro": {
1976
+ "name": "Romanian",
1977
+ "numbers": [
1978
+ 1,
1979
+ 2,
1980
+ 20
1981
+ ],
1982
+ "plurals": function(n) { return Number(n==1 ? 0 : (n===0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2); }
1983
+ },
1984
+ "ru": {
1985
+ "name": "Russian",
1986
+ "numbers": [
1987
+ 1,
1988
+ 2,
1989
+ 5
1990
+ ],
1991
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
1992
+ },
1993
+ "sah": {
1994
+ "name": "Yakut",
1995
+ "numbers": [
1996
+ 1
1997
+ ],
1998
+ "plurals": function(n) { return 0; }
1999
+ },
2000
+ "sco": {
2001
+ "name": "Scots",
2002
+ "numbers": [
2003
+ 1,
2004
+ 2
2005
+ ],
2006
+ "plurals": function(n) { return Number(n != 1); }
2007
+ },
2008
+ "se": {
2009
+ "name": "Northern Sami",
2010
+ "numbers": [
2011
+ 1,
2012
+ 2
2013
+ ],
2014
+ "plurals": function(n) { return Number(n != 1); }
2015
+ },
2016
+ "si": {
2017
+ "name": "Sinhala",
2018
+ "numbers": [
2019
+ 1,
2020
+ 2
2021
+ ],
2022
+ "plurals": function(n) { return Number(n != 1); }
2023
+ },
2024
+ "sk": {
2025
+ "name": "Slovak",
2026
+ "numbers": [
2027
+ 1,
2028
+ 2,
2029
+ 5
2030
+ ],
2031
+ "plurals": function(n) { return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2); }
2032
+ },
2033
+ "sl": {
2034
+ "name": "Slovenian",
2035
+ "numbers": [
2036
+ 5,
2037
+ 1,
2038
+ 2,
2039
+ 3
2040
+ ],
2041
+ "plurals": function(n) { return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); }
2042
+ },
2043
+ "so": {
2044
+ "name": "Somali",
2045
+ "numbers": [
2046
+ 1,
2047
+ 2
2048
+ ],
2049
+ "plurals": function(n) { return Number(n != 1); }
2050
+ },
2051
+ "son": {
2052
+ "name": "Songhay",
2053
+ "numbers": [
2054
+ 1,
2055
+ 2
2056
+ ],
2057
+ "plurals": function(n) { return Number(n != 1); }
2058
+ },
2059
+ "sq": {
2060
+ "name": "Albanian",
2061
+ "numbers": [
2062
+ 1,
2063
+ 2
2064
+ ],
2065
+ "plurals": function(n) { return Number(n != 1); }
2066
+ },
2067
+ "sr": {
2068
+ "name": "Serbian",
2069
+ "numbers": [
2070
+ 1,
2071
+ 2,
2072
+ 5
2073
+ ],
2074
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
2075
+ },
2076
+ "su": {
2077
+ "name": "Sundanese",
2078
+ "numbers": [
2079
+ 1
2080
+ ],
2081
+ "plurals": function(n) { return 0; }
2082
+ },
2083
+ "sv": {
2084
+ "name": "Swedish",
2085
+ "numbers": [
2086
+ 1,
2087
+ 2
2088
+ ],
2089
+ "plurals": function(n) { return Number(n != 1); }
2090
+ },
2091
+ "sw": {
2092
+ "name": "Swahili",
2093
+ "numbers": [
2094
+ 1,
2095
+ 2
2096
+ ],
2097
+ "plurals": function(n) { return Number(n != 1); }
2098
+ },
2099
+ "ta": {
2100
+ "name": "Tamil",
2101
+ "numbers": [
2102
+ 1,
2103
+ 2
2104
+ ],
2105
+ "plurals": function(n) { return Number(n != 1); }
2106
+ },
2107
+ "te": {
2108
+ "name": "Telugu",
2109
+ "numbers": [
2110
+ 1,
2111
+ 2
2112
+ ],
2113
+ "plurals": function(n) { return Number(n != 1); }
2114
+ },
2115
+ "tg": {
2116
+ "name": "Tajik",
2117
+ "numbers": [
2118
+ 1,
2119
+ 2
2120
+ ],
2121
+ "plurals": function(n) { return Number(n > 1); }
2122
+ },
2123
+ "th": {
2124
+ "name": "Thai",
2125
+ "numbers": [
2126
+ 1
2127
+ ],
2128
+ "plurals": function(n) { return 0; }
2129
+ },
2130
+ "ti": {
2131
+ "name": "Tigrinya",
2132
+ "numbers": [
2133
+ 1,
2134
+ 2
2135
+ ],
2136
+ "plurals": function(n) { return Number(n > 1); }
2137
+ },
2138
+ "tk": {
2139
+ "name": "Turkmen",
2140
+ "numbers": [
2141
+ 1,
2142
+ 2
2143
+ ],
2144
+ "plurals": function(n) { return Number(n != 1); }
2145
+ },
2146
+ "tr": {
2147
+ "name": "Turkish",
2148
+ "numbers": [
2149
+ 1,
2150
+ 2
2151
+ ],
2152
+ "plurals": function(n) { return Number(n > 1); }
2153
+ },
2154
+ "tt": {
2155
+ "name": "Tatar",
2156
+ "numbers": [
2157
+ 1
2158
+ ],
2159
+ "plurals": function(n) { return 0; }
2160
+ },
2161
+ "ug": {
2162
+ "name": "Uyghur",
2163
+ "numbers": [
2164
+ 1
2165
+ ],
2166
+ "plurals": function(n) { return 0; }
2167
+ },
2168
+ "uk": {
2169
+ "name": "Ukrainian",
2170
+ "numbers": [
2171
+ 1,
2172
+ 2,
2173
+ 5
2174
+ ],
2175
+ "plurals": function(n) { return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); }
2176
+ },
2177
+ "ur": {
2178
+ "name": "Urdu",
2179
+ "numbers": [
2180
+ 1,
2181
+ 2
2182
+ ],
2183
+ "plurals": function(n) { return Number(n != 1); }
2184
+ },
2185
+ "uz": {
2186
+ "name": "Uzbek",
2187
+ "numbers": [
2188
+ 1,
2189
+ 2
2190
+ ],
2191
+ "plurals": function(n) { return Number(n > 1); }
2192
+ },
2193
+ "vi": {
2194
+ "name": "Vietnamese",
2195
+ "numbers": [
2196
+ 1
2197
+ ],
2198
+ "plurals": function(n) { return 0; }
2199
+ },
2200
+ "wa": {
2201
+ "name": "Walloon",
2202
+ "numbers": [
2203
+ 1,
2204
+ 2
2205
+ ],
2206
+ "plurals": function(n) { return Number(n > 1); }
2207
+ },
2208
+ "wo": {
2209
+ "name": "Wolof",
2210
+ "numbers": [
2211
+ 1
2212
+ ],
2213
+ "plurals": function(n) { return 0; }
2214
+ },
2215
+ "yo": {
2216
+ "name": "Yoruba",
2217
+ "numbers": [
2218
+ 1,
2219
+ 2
2220
+ ],
2221
+ "plurals": function(n) { return Number(n != 1); }
2222
+ },
2223
+ "zh": {
2224
+ "name": "Chinese",
2225
+ "numbers": [
2226
+ 1
2227
+ ],
2228
+ "plurals": function(n) { return 0; }
2229
+ }
2230
+ },
2231
+
2232
+ // for demonstration only sl and ar is added but you can add your own pluralExtensions
2233
+ addRule: function(lng, obj) {
2234
+ pluralExtensions.rules[lng] = obj;
2235
+ },
2236
+
2237
+ setCurrentLng: function(lng) {
2238
+ if (!pluralExtensions.currentRule || pluralExtensions.currentRule.lng !== lng) {
2239
+ var parts = lng.split('-');
2240
+
2241
+ pluralExtensions.currentRule = {
2242
+ lng: lng,
2243
+ rule: pluralExtensions.rules[parts[0]]
2244
+ };
2245
+ }
2246
+ },
2247
+
2248
+ get: function(lng, count) {
2249
+ var parts = lng.split('-');
2250
+
2251
+ function getResult(l, c) {
2252
+ var ext;
2253
+ if (pluralExtensions.currentRule && pluralExtensions.currentRule.lng === lng) {
2254
+ ext = pluralExtensions.currentRule.rule;
2255
+ } else {
2256
+ ext = pluralExtensions.rules[l];
2257
+ }
2258
+ if (ext) {
2259
+ var i = ext.plurals(c);
2260
+ var number = ext.numbers[i];
2261
+ if (ext.numbers.length === 2 && ext.numbers[0] === 1) {
2262
+ if (number === 2) {
2263
+ number = -1; // regular plural
2264
+ } else if (number === 1) {
2265
+ number = 1; // singular
2266
+ }
2267
+ }//console.log(count + '-' + number);
2268
+ return number;
2269
+ } else {
2270
+ return c === 1 ? '1' : '-1';
2271
+ }
2272
+ }
2273
+
2274
+ return getResult(parts[0], count);
2275
+ }
2276
+
2277
+ };
2278
+ var postProcessors = {};
2279
+ var addPostProcessor = function(name, fc) {
2280
+ postProcessors[name] = fc;
2281
+ };
2282
+ // sprintf support
2283
+ var sprintf = (function() {
2284
+ function get_type(variable) {
2285
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
2286
+ }
2287
+ function str_repeat(input, multiplier) {
2288
+ for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
2289
+ return output.join('');
2290
+ }
2291
+
2292
+ var str_format = function() {
2293
+ if (!str_format.cache.hasOwnProperty(arguments[0])) {
2294
+ str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
2295
+ }
2296
+ return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
2297
+ };
2298
+
2299
+ str_format.format = function(parse_tree, argv) {
2300
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
2301
+ for (i = 0; i < tree_length; i++) {
2302
+ node_type = get_type(parse_tree[i]);
2303
+ if (node_type === 'string') {
2304
+ output.push(parse_tree[i]);
2305
+ }
2306
+ else if (node_type === 'array') {
2307
+ match = parse_tree[i]; // convenience purposes only
2308
+ if (match[2]) { // keyword argument
2309
+ arg = argv[cursor];
2310
+ for (k = 0; k < match[2].length; k++) {
2311
+ if (!arg.hasOwnProperty(match[2][k])) {
2312
+ throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
2313
+ }
2314
+ arg = arg[match[2][k]];
2315
+ }
2316
+ }
2317
+ else if (match[1]) { // positional argument (explicit)
2318
+ arg = argv[match[1]];
2319
+ }
2320
+ else { // positional argument (implicit)
2321
+ arg = argv[cursor++];
2322
+ }
2323
+
2324
+ if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
2325
+ throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
2326
+ }
2327
+ switch (match[8]) {
2328
+ case 'b': arg = arg.toString(2); break;
2329
+ case 'c': arg = String.fromCharCode(arg); break;
2330
+ case 'd': arg = parseInt(arg, 10); break;
2331
+ case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
2332
+ case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
2333
+ case 'o': arg = arg.toString(8); break;
2334
+ case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
2335
+ case 'u': arg = Math.abs(arg); break;
2336
+ case 'x': arg = arg.toString(16); break;
2337
+ case 'X': arg = arg.toString(16).toUpperCase(); break;
2338
+ }
2339
+ arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
2340
+ pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
2341
+ pad_length = match[6] - String(arg).length;
2342
+ pad = match[6] ? str_repeat(pad_character, pad_length) : '';
2343
+ output.push(match[5] ? arg + pad : pad + arg);
2344
+ }
2345
+ }
2346
+ return output.join('');
2347
+ };
2348
+
2349
+ str_format.cache = {};
2350
+
2351
+ str_format.parse = function(fmt) {
2352
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
2353
+ while (_fmt) {
2354
+ if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
2355
+ parse_tree.push(match[0]);
2356
+ }
2357
+ else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
2358
+ parse_tree.push('%');
2359
+ }
2360
+ else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
2361
+ if (match[2]) {
2362
+ arg_names |= 1;
2363
+ var field_list = [], replacement_field = match[2], field_match = [];
2364
+ if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
2365
+ field_list.push(field_match[1]);
2366
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
2367
+ if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
2368
+ field_list.push(field_match[1]);
2369
+ }
2370
+ else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
2371
+ field_list.push(field_match[1]);
2372
+ }
2373
+ else {
2374
+ throw('[sprintf] huh?');
2375
+ }
2376
+ }
2377
+ }
2378
+ else {
2379
+ throw('[sprintf] huh?');
2380
+ }
2381
+ match[2] = field_list;
2382
+ }
2383
+ else {
2384
+ arg_names |= 2;
2385
+ }
2386
+ if (arg_names === 3) {
2387
+ throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
2388
+ }
2389
+ parse_tree.push(match);
2390
+ }
2391
+ else {
2392
+ throw('[sprintf] huh?');
2393
+ }
2394
+ _fmt = _fmt.substring(match[0].length);
2395
+ }
2396
+ return parse_tree;
2397
+ };
2398
+
2399
+ return str_format;
2400
+ })();
2401
+
2402
+ var vsprintf = function(fmt, argv) {
2403
+ argv.unshift(fmt);
2404
+ return sprintf.apply(null, argv);
2405
+ };
2406
+
2407
+ addPostProcessor("sprintf", function(val, key, opts) {
2408
+ if (!opts.sprintf) return val;
2409
+
2410
+ if (Object.prototype.toString.apply(opts.sprintf) === '[object Array]') {
2411
+ return vsprintf(val, opts.sprintf);
2412
+ } else if (typeof opts.sprintf === 'object') {
2413
+ return sprintf(val, opts.sprintf);
2414
+ }
2415
+
2416
+ return val;
2417
+ });
2418
+ // public api interface
2419
+ i18n.init = init;
2420
+ i18n.setLng = setLng;
2421
+ i18n.preload = preload;
2422
+ i18n.addResourceBundle = addResourceBundle;
2423
+ i18n.loadNamespace = loadNamespace;
2424
+ i18n.loadNamespaces = loadNamespaces;
2425
+ i18n.setDefaultNamespace = setDefaultNamespace;
2426
+ i18n.t = translate;
2427
+ i18n.translate = translate;
2428
+ i18n.detectLanguage = f.detectLanguage;
2429
+ i18n.pluralExtensions = pluralExtensions;
2430
+ i18n.sync = sync;
2431
+ i18n.functions = f;
2432
+ i18n.lng = lng;
2433
+ i18n.addPostProcessor = addPostProcessor;
2434
+ i18n.options = o;
2435
+
2436
+ })();