lesli 5.0.7 → 5.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/lesli_manifest.js +10 -9
  3. data/app/assets/icons/lesli/engine-lesli.svg +1 -0
  4. data/app/assets/icons/lesli/locale-en.svg +1 -0
  5. data/app/assets/icons/lesli/locale-es.svg +1 -0
  6. data/app/assets/javascripts/lesli/users/sessions.js +1 -722
  7. data/app/assets/stylesheets/lesli/templates/application.css +1846 -0
  8. data/app/assets/stylesheets/lesli/templates/public.css +1 -0
  9. data/app/assets/stylesheets/lesli/users/sessions.css +1 -0
  10. data/app/helpers/lesli/navigation_helper.rb +9 -1
  11. data/app/lib/lesli/system.rb +2 -1
  12. data/app/models/concerns/account_initializer.rb +3 -3
  13. data/app/views/lesli/layouts/application-public.html.erb +1 -1
  14. data/app/views/lesli/partials/_application-data.html.erb +3 -1
  15. data/app/views/lesli/partials/_application-lesli-header.html.erb +7 -8
  16. data/app/views/lesli/partials/_application-lesli-icons.html.erb +1 -1
  17. data/app/views/lesli/partials/_application-lesli-javascript.html.erb +1 -1
  18. data/app/views/lesli/partials/_application-public-javascript.html.erb +1 -4
  19. data/app/views/lesli/wrappers/_application-devise.html.erb +2 -2
  20. data/config/initializers/lesli.rb +1 -1
  21. data/db/seeds.rb +2 -4
  22. data/lib/assets/javascripts/lesli/i18n.js +1037 -1037
  23. data/lib/lesli/engine.rb +7 -6
  24. data/lib/lesli/version.rb +2 -2
  25. data/lib/{sass/lesli → scss}/bulma/loader.scss +3 -1
  26. data/{app/assets/stylesheets/lesli/users → lib/scss/devise}/sessions.scss +3 -1
  27. data/lib/{sass/lesli → scss}/fonts/families.scss +3 -3
  28. data/lib/{sass/lesli → scss}/fonts/mdicons.scss +6 -8
  29. data/lib/{sass/lesli → scss}/fonts/remixicons.scss +2 -2
  30. data/lib/{sass/lesli → scss}/layouts/application-header.scss +4 -0
  31. data/lib/{sass/lesli → scss}/layouts/application-sidebar.scss +1 -1
  32. data/lib/{sass/lesli → scss}/pages/devise.scss +5 -1
  33. data/lib/{sass/lesli → scss}/settings/variables.scss +1 -1
  34. data/lib/{sass/lesli → scss}/templates/application.scss +46 -49
  35. data/lib/{sass/lesli → scss}/templates/component.scss +1 -1
  36. data/lib/{sass/lesli → scss}/templates/dashboards.scss +1 -1
  37. data/lib/{sass/lesli → scss}/templates/public.scss +6 -6
  38. data/lib/tasks/lesli/db.rake +42 -24
  39. data/lib/tasks/lesli/docs.rake +47 -0
  40. data/lib/vue/application.js +16 -15
  41. data/lib/vue/devise/sessions.js +1 -1
  42. data/lib/vue/layouts/application-announcements.vue +1 -1
  43. data/lib/vue/layouts/application-engines.vue +1 -1
  44. data/lib/vue/layouts/application-header.vue +9 -9
  45. data/lib/vue/layouts/application-search.vue +1 -1
  46. data/lib/vue/panels/panel-announcements.vue +2 -2
  47. data/lib/vue/panels/panel-files.vue +2 -2
  48. data/lib/vue/panels/panel-notifications.vue +1 -1
  49. data/lib/vue/panels/panel-profile.vue +2 -2
  50. data/lib/vue/panels/panel-tasks.vue +2 -2
  51. data/lib/vue/panels/panel-tickets.vue +2 -2
  52. data/lib/vue/public.js +3 -2
  53. data/lib/vue/{shared → refactor/shared}/cloudobjects/action.vue +1 -1
  54. data/lib/vue/{shared → refactor/shared}/cloudobjects/discussion/content.vue +2 -2
  55. data/lib/vue/{shared → refactor/shared}/cloudobjects/discussion/element.vue +2 -2
  56. data/lib/vue/{shared → refactor/shared}/cloudobjects/discussion/filters.vue +1 -1
  57. data/lib/vue/{shared → refactor/shared}/cloudobjects/discussion/new.vue +1 -1
  58. data/lib/vue/{shared → refactor/shared}/cloudobjects/discussion.vue +4 -4
  59. data/lib/vue/{shared → refactor/shared}/cloudobjects/file/grid.vue +2 -2
  60. data/lib/vue/{shared → refactor/shared}/cloudobjects/file/list.vue +2 -2
  61. data/lib/vue/{shared → refactor/shared}/cloudobjects/file.vue +4 -4
  62. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/form.vue +1 -1
  63. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/chatroom-form.vue +1 -1
  64. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/cloud-object-clone-form.vue +1 -1
  65. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/cloud-object-file-form.vue +1 -1
  66. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/email-form.vue +1 -1
  67. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/notification-form.vue +1 -1
  68. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/send-cloud-object-file.vue +1 -1
  69. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/forms/task-form.vue +1 -1
  70. data/lib/vue/{shared → refactor/shared}/workflows2/apps/actions/index.vue +1 -1
  71. data/lib/vue/{shared → refactor/shared}/workflows2/apps/checks/form.vue +1 -1
  72. data/lib/vue/{shared → refactor/shared}/workflows2/apps/checks/index.vue +1 -1
  73. data/lib/vue/{shared → refactor/shared}/workflows2/apps/index.vue +1 -1
  74. data/lib/vue/{shared → refactor/shared}/workflows2/apps/new.vue +1 -1
  75. data/lib/vue/{shared → refactor/shared}/workflows2/apps/show.vue +1 -1
  76. data/lib/vue/{shared → refactor/shared}/workflows2/components/associations.vue +1 -1
  77. data/lib/vue/{shared → refactor/shared}/workflows2/components/chart.vue +1 -1
  78. data/lib/vue/{shared → refactor/shared}/workflows2/components/workflow-form.vue +1 -1
  79. data/lib/vue/{shared → refactor/shared}/workflows2/components/workflow-status-dropdown.vue +1 -1
  80. data/lib/vue/refactor/stores/translations.json +60 -0
  81. data/lib/vue/shared/dashboards/apps/edit.vue +1 -1
  82. data/lib/vue/shared/dashboards/apps/index.vue +1 -1
  83. data/lib/vue/shared/dashboards/apps/new.vue +1 -1
  84. data/lib/vue/shared/dashboards/apps/show.vue +4 -2
  85. data/lib/vue/shared/dashboards/components/form.vue +1 -1
  86. data/lib/webpack/base.js +133 -132
  87. data/lib/webpack/core.js +9 -3
  88. data/lib/webpack/engines.js +7 -2
  89. data/lib/webpack/version.js +4 -1
  90. data/readme.md +7 -5
  91. metadata +135 -180
  92. data/app/assets/icons/lesli/cloud-development.svg +0 -1
  93. data/app/assets/icons/lesli/cloud-letter.svg +0 -1
  94. data/app/assets/icons/lesli/icon-module.svg +0 -1
  95. data/app/assets/icons/lesli/logo-android.svg +0 -1
  96. data/app/assets/icons/lesli/logo-bulma.svg +0 -1
  97. data/app/assets/icons/lesli/logo-ios.svg +0 -1
  98. data/app/assets/icons/lesli/logo-linux.svg +0 -1
  99. data/app/assets/icons/lesli/logo-mac.svg +0 -1
  100. data/app/assets/icons/lesli/logo-module.svg +0 -1
  101. data/app/assets/icons/lesli/logo-postgresql.svg +0 -1
  102. data/app/assets/icons/lesli/logo-rails.svg +0 -1
  103. data/app/assets/icons/lesli/logo-ruby.svg +0 -1
  104. data/app/assets/icons/lesli/logo-sass.svg +0 -1
  105. data/app/assets/icons/lesli/logo-vue.svg +0 -1
  106. data/app/assets/icons/lesli/logo-web.svg +0 -1
  107. data/app/assets/icons/lesli/logo-windows.svg +0 -1
  108. data/app/assets/images/lesli/brand/app-logo2.svg +0 -52
  109. data/app/assets/javascripts/lesli/users/confirmations.js +0 -32
  110. data/app/assets/javascripts/lesli/users/passwords.js +0 -742
  111. data/app/assets/javascripts/lesli/users/registrations.js +0 -742
  112. data/lib/assets/javascripts/lesli/translations.js +0 -15
  113. data/lib/sass/lesli/elements/form.scss +0 -43
  114. data/lib/sass/lesli/elements/header.scss +0 -44
  115. data/lib/sass/lesli/elements/panel.scss +0 -65
  116. data/lib/sass/lesli/elements/tab.scss +0 -48
  117. /data/app/assets/fonts/lesli/Domine/{Domine-VariableFont_wght.ttf → Domine-Variable.ttf} +0 -0
  118. /data/app/assets/icons/lesli/{cloud-admin.svg → engine-admin.svg} +0 -0
  119. /data/app/assets/icons/lesli/{cloud-audit.svg → engine-audit.svg} +0 -0
  120. /data/app/assets/icons/lesli/{cloud-babel.svg → engine-babel.svg} +0 -0
  121. /data/app/assets/icons/lesli/{cloud-bell.svg → engine-bell.svg} +0 -0
  122. /data/app/assets/icons/lesli/{cloud-books.svg → engine-books.svg} +0 -0
  123. /data/app/assets/icons/lesli/{cloud-lesli.svg → engine-dashboard.svg} +0 -0
  124. /data/app/assets/icons/lesli/{cloud-dispatcher.svg → engine-dispatcher.svg} +0 -0
  125. /data/app/assets/icons/lesli/{cloud-driver.svg → engine-driver.svg} +0 -0
  126. /data/app/assets/icons/lesli/{cloud-focus.svg → engine-focus.svg} +0 -0
  127. /data/app/assets/icons/lesli/{cloud-guard.svg → engine-guard.svg} +0 -0
  128. /data/app/assets/icons/lesli/{cloud-kb.svg → engine-kb.svg} +0 -0
  129. /data/app/assets/icons/lesli/{cloud-leaf.svg → engine-leaf.svg} +0 -0
  130. /data/app/assets/icons/lesli/{cloud-word.svg → engine-letter.svg} +0 -0
  131. /data/app/assets/icons/lesli/{cloud-mailer.svg → engine-mailer.svg} +0 -0
  132. /data/app/assets/icons/lesli/{cloud-one.svg → engine-one.svg} +0 -0
  133. /data/app/assets/icons/lesli/{cloud-profile.svg → engine-profile.svg} +0 -0
  134. /data/app/assets/icons/lesli/{cloud-proposal.svg → engine-proposal.svg} +0 -0
  135. /data/app/assets/icons/lesli/{cloud-scraper.svg → engine-scraper.svg} +0 -0
  136. /data/app/assets/icons/lesli/{cloud-shared.svg → engine-shared.svg} +0 -0
  137. /data/app/assets/icons/lesli/{cloud-storage.svg → engine-storage.svg} +0 -0
  138. /data/app/assets/icons/lesli/{cloud-support.svg → engine-support.svg} +0 -0
  139. /data/app/assets/icons/lesli/{cloud-talk.svg → engine-talk.svg} +0 -0
  140. /data/app/assets/icons/lesli/{cloud-team.svg → engine-team.svg} +0 -0
  141. /data/app/assets/icons/lesli/{cloud-time.svg → engine-time.svg} +0 -0
  142. /data/lib/{sass/lesli → scss}/cloud-objects/discussion.scss +0 -0
  143. /data/lib/{sass/lesli → scss}/cloud-objects/file.scss +0 -0
  144. /data/lib/{sass/lesli → scss}/components/editor-richtext.scss +0 -0
  145. /data/lib/{sass/lesli → scss}/components/keypad.scss +0 -0
  146. /data/{app/assets/stylesheets/lesli/users → lib/scss/devise}/confirmations.scss +0 -0
  147. /data/{app/assets/stylesheets/lesli/users → lib/scss/devise}/oauth.scss +0 -0
  148. /data/{app/assets/stylesheets/lesli/users → lib/scss/devise}/passwords.scss +0 -0
  149. /data/{app/assets/stylesheets/lesli/users → lib/scss/devise}/registrations.scss +0 -0
  150. /data/lib/{sass/lesli → scss}/elements/autocomplete.scss +0 -0
  151. /data/lib/{sass/lesli → scss}/elements/avatar.scss +0 -0
  152. /data/lib/{sass/lesli → scss}/elements/calendar.scss +0 -0
  153. /data/lib/{sass/lesli → scss}/elements/card.scss +0 -0
  154. /data/lib/{sass/lesli → scss}/elements/empty.scss +0 -0
  155. /data/lib/{sass/lesli → scss}/elements/file-uploader.scss +0 -0
  156. /data/lib/{sass/lesli → scss}/elements/gallery.scss +0 -0
  157. /data/lib/{sass/lesli → scss}/elements/input-tag.scss +0 -0
  158. /data/lib/{sass/lesli → scss}/elements/loading.scss +0 -0
  159. /data/lib/{sass/lesli → scss}/elements/map.scss +0 -0
  160. /data/lib/{sass/lesli → scss}/elements/msg.scss +0 -0
  161. /data/lib/{sass/lesli → scss}/elements/navbar.scss +0 -0
  162. /data/lib/{sass/lesli → scss}/elements/table.scss +0 -0
  163. /data/lib/{sass/lesli → scss}/elements/toggle.scss +0 -0
  164. /data/lib/{sass/lesli → scss}/elements/toolbar.scss +0 -0
  165. /data/lib/{sass/lesli → scss}/helpers/display.scss +0 -0
  166. /data/lib/{sass/lesli → scss}/helpers/tooltip.scss +0 -0
  167. /data/lib/{sass/lesli → scss}/layouts/application-announcements.scss +0 -0
  168. /data/lib/{sass/lesli → scss}/layouts/application-component.scss +0 -0
  169. /data/lib/{sass/lesli → scss}/layouts/application-container.scss +0 -0
  170. /data/lib/{sass/lesli → scss}/layouts/application-engines.scss +0 -0
  171. /data/lib/{sass/lesli → scss}/layouts/application-footer.scss +0 -0
  172. /data/lib/{sass/lesli → scss}/layouts/application-navbar.scss +0 -0
  173. /data/lib/{sass/lesli → scss}/layouts/application-search.scss +0 -0
  174. /data/lib/{sass/lesli → scss}/overrides/sweetalert.scss +0 -0
  175. /data/lib/{sass/lesli → scss}/pages/devise-simple.scss +0 -0
  176. /data/lib/{sass/lesli → scss}/pages/invites.scss +0 -0
  177. /data/lib/{sass/lesli → scss}/panels/panel-announcements.scss +0 -0
  178. /data/lib/{sass/lesli → scss}/panels/panel-notification.scss +0 -0
  179. /data/lib/{sass/lesli → scss}/panels/panel-profile.scss +0 -0
  180. /data/lib/{sass/lesli → scss}/panels/panel-tasks.scss +0 -0
  181. /data/lib/{sass/lesli → scss}/panels/panel-ticket.scss +0 -0
  182. /data/lib/{sass/lesli → scss}/shared/workflow-status-progress.scss +0 -0
  183. /data/lib/{sass/lesli → scss}/templates/empty.scss +0 -0
  184. /data/lib/{sass/lesli → scss}/templates/pdf.scss +0 -0
  185. /data/lib/vue/{stores → refactor/stores}/cloudobjects/action.js +0 -0
  186. /data/lib/vue/{stores → refactor/stores}/cloudobjects/discussion.js +0 -0
  187. /data/lib/vue/{stores → refactor/stores}/cloudobjects/file.js +0 -0
  188. /data/lib/vue/{stores → refactor/stores}/entities/announcements.js +0 -0
  189. /data/lib/vue/{stores → refactor/stores}/entities/dashboard.js +0 -0
  190. /data/lib/vue/{stores → refactor/stores}/entities/workflow.js +0 -0
  191. /data/lib/vue/{stores → refactor/stores}/panels/announcements.js +0 -0
  192. /data/lib/vue/{stores → refactor/stores}/panels/notification.js +0 -0
  193. /data/lib/vue/{stores → refactor/stores}/panels/task.js +0 -0
  194. /data/lib/vue/{stores → refactor/stores}/panels/tickets.js +0 -0
  195. /data/lib/vue/{stores → refactor/stores}/role.js +0 -0
  196. /data/lib/vue/{stores → refactor/stores}/users.js +0 -0
  197. /data/lib/vue/{stores → shared}/services/firebase.js +0 -0
  198. /data/lib/vue/{stores → shared}/services/translator.js +0 -0
  199. /data/lib/vue/{stores → shared/stores}/account.js +0 -0
  200. /data/lib/vue/{stores/search.js → shared/stores/command.js} +0 -0
  201. /data/lib/vue/{stores → shared/stores}/layout.js +0 -0
@@ -16,1080 +16,1080 @@
16
16
  // https://github.com/umdjs/umd#regular-module
17
17
  // `returnExports.js` version
18
18
  ;(function (root, factory) {
19
- if (typeof define === 'function' && define.amd) {
20
- // AMD. Register as an anonymous module.
21
- define("i18n", function(){ return factory(root);});
22
- } else if (typeof module === 'object' && module.exports) {
23
- // Node. Does not work with strict CommonJS, but
24
- // only CommonJS-like environments that support module.exports,
25
- // like Node.
26
- module.exports = factory(root);
27
- } else {
28
- // Browser globals (root is window)
29
- root.I18n = factory(root);
30
- }
31
- }(this, function(global) {
32
- "use strict";
33
-
34
- // Use previously defined object if exists in current scope
35
- var I18n = global && global.I18n || {};
36
-
37
- // Just cache the Array#slice function.
38
- var slice = Array.prototype.slice;
39
-
40
- // Apply number padding.
41
- var padding = function(number) {
42
- return ("0" + number.toString()).substr(-2);
43
- };
44
-
45
- // Improved toFixed number rounding function with support for unprecise floating points
46
- // JavaScript's standard toFixed function does not round certain numbers correctly (for example 0.105 with precision 2).
47
- var toFixed = function(number, precision) {
48
- return decimalAdjust('round', number, -precision).toFixed(precision);
49
- };
50
-
51
- // Is a given variable an object?
52
- // Borrowed from Underscore.js
53
- var isObject = function(obj) {
54
- var type = typeof obj;
55
- return type === 'function' || type === 'object'
56
- };
57
-
58
- var isFunction = function(func) {
59
- var type = typeof func;
60
- return type === 'function'
61
- };
62
-
63
- // Check if value is different than undefined and null;
64
- var isSet = function(value) {
65
- return typeof(value) !== 'undefined' && value !== null;
66
- };
67
-
68
- // Is a given value an array?
69
- // Borrowed from Underscore.js
70
- var isArray = function(val) {
71
- if (Array.isArray) {
72
- return Array.isArray(val);
73
- }
74
- return Object.prototype.toString.call(val) === '[object Array]';
75
- };
76
-
77
- var isString = function(val) {
78
- return typeof val === 'string' || Object.prototype.toString.call(val) === '[object String]';
79
- };
80
-
81
- var isNumber = function(val) {
82
- return typeof val === 'number' || Object.prototype.toString.call(val) === '[object Number]';
83
- };
84
-
85
- var isBoolean = function(val) {
86
- return val === true || val === false;
87
- };
88
-
89
- var isNull = function(val) {
90
- return val === null;
91
- };
92
-
93
- var decimalAdjust = function(type, value, exp) {
94
- // If the exp is undefined or zero...
95
- if (typeof exp === 'undefined' || +exp === 0) {
96
- return Math[type](value);
97
- }
98
- value = +value;
99
- exp = +exp;
100
- // If the value is not a number or the exp is not an integer...
101
- if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
102
- return NaN;
103
- }
104
- // Shift
105
- value = value.toString().split('e');
106
- value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
107
- // Shift back
108
- value = value.toString().split('e');
109
- return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
110
- };
111
-
112
- var lazyEvaluate = function(message, scope) {
113
- if (isFunction(message)) {
114
- return message(scope);
19
+ if (typeof define === 'function' && define.amd) {
20
+ // AMD. Register as an anonymous module.
21
+ define("i18n", function(){ return factory(root);});
22
+ } else if (typeof module === 'object' && module.exports) {
23
+ // Node. Does not work with strict CommonJS, but
24
+ // only CommonJS-like environments that support module.exports,
25
+ // like Node.
26
+ module.exports = factory(root);
115
27
  } else {
116
- return message;
28
+ // Browser globals (root is window)
29
+ root.I18n = factory(root);
117
30
  }
118
- };
119
-
120
- var merge = function (dest, obj) {
121
- var key, value;
122
- for (key in obj) if (obj.hasOwnProperty(key)) {
123
- value = obj[key];
124
- if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value) || isNull(value)) {
125
- dest[key] = value;
31
+ }(this, function(global) {
32
+ "use strict";
33
+
34
+ // Use previously defined object if exists in current scope
35
+ var I18n = global && global.I18n || {};
36
+
37
+ // Just cache the Array#slice function.
38
+ var slice = Array.prototype.slice;
39
+
40
+ // Apply number padding.
41
+ var padding = function(number) {
42
+ return ("0" + number.toString()).substr(-2);
43
+ };
44
+
45
+ // Improved toFixed number rounding function with support for unprecise floating points
46
+ // JavaScript's standard toFixed function does not round certain numbers correctly (for example 0.105 with precision 2).
47
+ var toFixed = function(number, precision) {
48
+ return decimalAdjust('round', number, -precision).toFixed(precision);
49
+ };
50
+
51
+ // Is a given variable an object?
52
+ // Borrowed from Underscore.js
53
+ var isObject = function(obj) {
54
+ var type = typeof obj;
55
+ return type === 'function' || type === 'object'
56
+ };
57
+
58
+ var isFunction = function(func) {
59
+ var type = typeof func;
60
+ return type === 'function'
61
+ };
62
+
63
+ // Check if value is different than undefined and null;
64
+ var isSet = function(value) {
65
+ return typeof(value) !== 'undefined' && value !== null;
66
+ };
67
+
68
+ // Is a given value an array?
69
+ // Borrowed from Underscore.js
70
+ var isArray = function(val) {
71
+ if (Array.isArray) {
72
+ return Array.isArray(val);
73
+ }
74
+ return Object.prototype.toString.call(val) === '[object Array]';
75
+ };
76
+
77
+ var isString = function(val) {
78
+ return typeof val === 'string' || Object.prototype.toString.call(val) === '[object String]';
79
+ };
80
+
81
+ var isNumber = function(val) {
82
+ return typeof val === 'number' || Object.prototype.toString.call(val) === '[object Number]';
83
+ };
84
+
85
+ var isBoolean = function(val) {
86
+ return val === true || val === false;
87
+ };
88
+
89
+ var isNull = function(val) {
90
+ return val === null;
91
+ };
92
+
93
+ var decimalAdjust = function(type, value, exp) {
94
+ // If the exp is undefined or zero...
95
+ if (typeof exp === 'undefined' || +exp === 0) {
96
+ return Math[type](value);
97
+ }
98
+ value = +value;
99
+ exp = +exp;
100
+ // If the value is not a number or the exp is not an integer...
101
+ if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
102
+ return NaN;
103
+ }
104
+ // Shift
105
+ value = value.toString().split('e');
106
+ value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
107
+ // Shift back
108
+ value = value.toString().split('e');
109
+ return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
110
+ };
111
+
112
+ var lazyEvaluate = function(message, scope) {
113
+ if (isFunction(message)) {
114
+ return message(scope);
126
115
  } else {
127
- if (dest[key] == null) dest[key] = {};
128
- merge(dest[key], value);
116
+ return message;
129
117
  }
130
- }
131
- return dest;
132
- };
133
-
134
- // Set default days/months translations.
135
- var DATE = {
136
- day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
137
- , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
138
- , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
139
- , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
140
- , meridian: ["AM", "PM"]
141
- };
142
-
143
- // Set default number format.
144
- var NUMBER_FORMAT = {
145
- precision: 3
146
- , separator: "."
147
- , delimiter: ","
148
- , strip_insignificant_zeros: false
149
- };
150
-
151
- // Set default currency format.
152
- var CURRENCY_FORMAT = {
153
- unit: "$"
154
- , precision: 2
155
- , format: "%u%n"
156
- , sign_first: true
157
- , delimiter: ","
158
- , separator: "."
159
- };
160
-
161
- // Set default percentage format.
162
- var PERCENTAGE_FORMAT = {
163
- unit: "%"
164
- , precision: 3
165
- , format: "%n%u"
166
- , separator: "."
167
- , delimiter: ""
168
- };
169
-
170
- // Set default size units.
171
- var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"];
172
-
173
- // Other default options
174
- var DEFAULT_OPTIONS = {
118
+ };
119
+
120
+ var merge = function (dest, obj) {
121
+ var key, value;
122
+ for (key in obj) if (obj.hasOwnProperty(key)) {
123
+ value = obj[key];
124
+ if (isString(value) || isNumber(value) || isBoolean(value) || isArray(value) || isNull(value)) {
125
+ dest[key] = value;
126
+ } else {
127
+ if (dest[key] == null) dest[key] = {};
128
+ merge(dest[key], value);
129
+ }
130
+ }
131
+ return dest;
132
+ };
133
+
134
+ // Set default days/months translations.
135
+ var DATE = {
136
+ day_names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
137
+ , abbr_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
138
+ , month_names: [null, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
139
+ , abbr_month_names: [null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
140
+ , meridian: ["AM", "PM"]
141
+ };
142
+
143
+ // Set default number format.
144
+ var NUMBER_FORMAT = {
145
+ precision: 3
146
+ , separator: "."
147
+ , delimiter: ","
148
+ , strip_insignificant_zeros: false
149
+ };
150
+
151
+ // Set default currency format.
152
+ var CURRENCY_FORMAT = {
153
+ unit: "$"
154
+ , precision: 2
155
+ , format: "%u%n"
156
+ , sign_first: true
157
+ , delimiter: ","
158
+ , separator: "."
159
+ };
160
+
161
+ // Set default percentage format.
162
+ var PERCENTAGE_FORMAT = {
163
+ unit: "%"
164
+ , precision: 3
165
+ , format: "%n%u"
166
+ , separator: "."
167
+ , delimiter: ""
168
+ };
169
+
170
+ // Set default size units.
171
+ var SIZE_UNITS = [null, "kb", "mb", "gb", "tb"];
172
+
173
+ // Other default options
174
+ var DEFAULT_OPTIONS = {
175
+ // Set default locale. This locale will be used when fallback is enabled and
176
+ // the translation doesn't exist in a particular locale.
177
+ defaultLocale: "en"
178
+ // Set the current locale to `en`.
179
+ , locale: "en"
180
+ // Set the translation key separator.
181
+ , defaultSeparator: "."
182
+ // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
183
+ , placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm
184
+ // Set if engine should fallback to the default locale when a translation
185
+ // is missing.
186
+ , fallbacks: false
187
+ // Set the default translation object.
188
+ , translations: {}
189
+ // Set missing translation behavior. 'message' will display a message
190
+ // that the translation is missing, 'guess' will try to guess the string
191
+ , missingBehaviour: 'message'
192
+ // if you use missingBehaviour with 'message', but want to know that the
193
+ // string is actually missing for testing purposes, you can prefix the
194
+ // guessed string by setting the value here. By default, no prefix!
195
+ , missingTranslationPrefix: ''
196
+ };
197
+
175
198
  // Set default locale. This locale will be used when fallback is enabled and
176
199
  // the translation doesn't exist in a particular locale.
177
- defaultLocale: "en"
178
- // Set the current locale to `en`.
179
- , locale: "en"
180
- // Set the translation key separator.
181
- , defaultSeparator: "."
182
- // Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
183
- , placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm
184
- // Set if engine should fallback to the default locale when a translation
185
- // is missing.
186
- , fallbacks: false
187
- // Set the default translation object.
188
- , translations: {}
189
- // Set missing translation behavior. 'message' will display a message
190
- // that the translation is missing, 'guess' will try to guess the string
191
- , missingBehaviour: 'message'
192
- // if you use missingBehaviour with 'message', but want to know that the
193
- // string is actually missing for testing purposes, you can prefix the
194
- // guessed string by setting the value here. By default, no prefix!
195
- , missingTranslationPrefix: ''
196
- };
197
-
198
- // Set default locale. This locale will be used when fallback is enabled and
199
- // the translation doesn't exist in a particular locale.
200
- I18n.reset = function() {
201
- var key;
202
- for (key in DEFAULT_OPTIONS) {
203
- this[key] = DEFAULT_OPTIONS[key];
204
- }
205
- };
206
-
207
- // Much like `reset`, but only assign options if not already assigned
208
- I18n.initializeOptions = function() {
209
- var key;
210
- for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) {
211
- this[key] = DEFAULT_OPTIONS[key];
212
- }
213
- };
214
- I18n.initializeOptions();
215
-
216
- // Return a list of all locales that must be tried before returning the
217
- // missing translation message. By default, this will consider the inline option,
218
- // current locale and fallback locale.
219
- //
220
- // I18n.locales.get("de-DE");
221
- // // ["de-DE", "de", "en"]
222
- //
223
- // You can define custom rules for any locale. Just make sure you return a array
224
- // containing all locales.
225
- //
226
- // // Default the Wookie locale to English.
227
- // I18n.locales["wk"] = function(locale) {
228
- // return ["en"];
229
- // };
230
- //
231
- I18n.locales = {};
232
-
233
- // Retrieve locales based on inline locale, current locale or default to
234
- // I18n's detection.
235
- I18n.locales.get = function(locale) {
236
- var result = this[locale] || this[I18n.locale] || this["default"];
237
-
238
- if (isFunction(result)) {
239
- result = result(locale);
240
- }
241
-
242
- if (isArray(result) === false) {
243
- result = [result];
244
- }
245
-
246
- return result;
247
- };
248
-
249
- // The default locale list.
250
- I18n.locales["default"] = function(locale) {
251
- var locales = []
252
- , list = []
253
- ;
254
-
255
- // Handle the inline locale option that can be provided to
256
- // the `I18n.t` options.
257
- if (locale) {
258
- locales.push(locale);
259
- }
260
-
261
- // Add the current locale to the list.
262
- if (!locale && I18n.locale) {
263
- locales.push(I18n.locale);
264
- }
265
-
266
- // Add the default locale if fallback strategy is enabled.
267
- if (I18n.fallbacks && I18n.defaultLocale) {
268
- locales.push(I18n.defaultLocale);
269
- }
270
-
271
- // Locale code format 1:
272
- // According to RFC4646 (https://www.ietf.org/rfc/rfc4646.txt)
273
- // language codes for Traditional Chinese should be `zh-Hant`
274
- //
275
- // But due to backward compatibility
276
- // We use older version of IETF language tag
277
- // @see https://www.w3.org/TR/html401/struct/dirlang.html
278
- // @see https://en.wikipedia.org/wiki/IETF_language_tag
279
- //
280
- // Format: `language-code = primary-code ( "-" subcode )*`
200
+ I18n.reset = function() {
201
+ var key;
202
+ for (key in DEFAULT_OPTIONS) {
203
+ this[key] = DEFAULT_OPTIONS[key];
204
+ }
205
+ };
206
+
207
+ // Much like `reset`, but only assign options if not already assigned
208
+ I18n.initializeOptions = function() {
209
+ var key;
210
+ for (key in DEFAULT_OPTIONS) if (!isSet(this[key])) {
211
+ this[key] = DEFAULT_OPTIONS[key];
212
+ }
213
+ };
214
+ I18n.initializeOptions();
215
+
216
+ // Return a list of all locales that must be tried before returning the
217
+ // missing translation message. By default, this will consider the inline option,
218
+ // current locale and fallback locale.
281
219
  //
282
- // primary-code uses ISO639-1
283
- // @see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
284
- // @see https://www.iso.org/iso/home/standards/language_codes.htm
220
+ // I18n.locales.get("de-DE");
221
+ // // ["de-DE", "de", "en"]
285
222
  //
286
- // subcode uses ISO 3166-1 alpha-2
287
- // @see https://en.wikipedia.org/wiki/ISO_3166
288
- // @see https://www.iso.org/iso/country_codes.htm
223
+ // You can define custom rules for any locale. Just make sure you return a array
224
+ // containing all locales.
289
225
  //
290
- // @note
291
- // subcode can be in upper case or lower case
292
- // defining it in upper case is a convention only
293
-
294
-
295
- // Locale code format 2:
296
- // Format: `code = primary-code ( "-" region-code )*`
297
- // primary-code uses ISO 639-1
298
- // script-code uses ISO 15924
299
- // region-code uses ISO 3166-1 alpha-2
300
- // Example: zh-Hant-TW, en-HK, zh-Hant-CN
226
+ // // Default the Wookie locale to English.
227
+ // I18n.locales["wk"] = function(locale) {
228
+ // return ["en"];
229
+ // };
301
230
  //
302
- // It is similar to RFC4646 (or actually the same),
303
- // but seems to be limited to language, script, region
304
-
305
- // Compute each locale with its country code.
306
- // So this will return an array containing
307
- // `de-DE` and `de`
308
- // or
309
- // `zh-hans-tw`, `zh-hans`, `zh`
310
- // locales.
311
- locales.forEach(function(locale) {
312
- var localeParts = locale.split("-");
313
- var firstFallback = null;
314
- var secondFallback = null;
315
- if (localeParts.length === 3) {
316
- firstFallback = [
317
- localeParts[0],
318
- localeParts[1]
319
- ].join("-");
320
- secondFallback = localeParts[0];
231
+ I18n.locales = {};
232
+
233
+ // Retrieve locales based on inline locale, current locale or default to
234
+ // I18n's detection.
235
+ I18n.locales.get = function(locale) {
236
+ var result = this[locale] || this[I18n.locale] || this["default"];
237
+
238
+ if (isFunction(result)) {
239
+ result = result(locale);
321
240
  }
322
- else if (localeParts.length === 2) {
323
- firstFallback = localeParts[0];
241
+
242
+ if (isArray(result) === false) {
243
+ result = [result];
324
244
  }
325
-
326
- if (list.indexOf(locale) === -1) {
327
- list.push(locale);
245
+
246
+ return result;
247
+ };
248
+
249
+ // The default locale list.
250
+ I18n.locales["default"] = function(locale) {
251
+ var locales = []
252
+ , list = []
253
+ ;
254
+
255
+ // Handle the inline locale option that can be provided to
256
+ // the `I18n.t` options.
257
+ if (locale) {
258
+ locales.push(locale);
328
259
  }
329
-
330
- if (! I18n.fallbacks) {
331
- return;
260
+
261
+ // Add the current locale to the list.
262
+ if (!locale && I18n.locale) {
263
+ locales.push(I18n.locale);
332
264
  }
333
-
334
- [
335
- firstFallback,
336
- secondFallback
337
- ].forEach(function(nullableFallbackLocale) {
338
- // We don't want null values
339
- if (typeof nullableFallbackLocale === "undefined") { return; }
340
- if (nullableFallbackLocale === null) { return; }
341
- // We don't want duplicate values
342
- //
343
- // Comparing with `locale` first is faster than
344
- // checking whether value's presence in the list
345
- if (nullableFallbackLocale === locale) { return; }
346
- if (list.indexOf(nullableFallbackLocale) !== -1) { return; }
347
-
348
- list.push(nullableFallbackLocale);
265
+
266
+ // Add the default locale if fallback strategy is enabled.
267
+ if (I18n.fallbacks && I18n.defaultLocale) {
268
+ locales.push(I18n.defaultLocale);
269
+ }
270
+
271
+ // Locale code format 1:
272
+ // According to RFC4646 (https://www.ietf.org/rfc/rfc4646.txt)
273
+ // language codes for Traditional Chinese should be `zh-Hant`
274
+ //
275
+ // But due to backward compatibility
276
+ // We use older version of IETF language tag
277
+ // @see https://www.w3.org/TR/html401/struct/dirlang.html
278
+ // @see https://en.wikipedia.org/wiki/IETF_language_tag
279
+ //
280
+ // Format: `language-code = primary-code ( "-" subcode )*`
281
+ //
282
+ // primary-code uses ISO639-1
283
+ // @see https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
284
+ // @see https://www.iso.org/iso/home/standards/language_codes.htm
285
+ //
286
+ // subcode uses ISO 3166-1 alpha-2
287
+ // @see https://en.wikipedia.org/wiki/ISO_3166
288
+ // @see https://www.iso.org/iso/country_codes.htm
289
+ //
290
+ // @note
291
+ // subcode can be in upper case or lower case
292
+ // defining it in upper case is a convention only
293
+
294
+
295
+ // Locale code format 2:
296
+ // Format: `code = primary-code ( "-" region-code )*`
297
+ // primary-code uses ISO 639-1
298
+ // script-code uses ISO 15924
299
+ // region-code uses ISO 3166-1 alpha-2
300
+ // Example: zh-Hant-TW, en-HK, zh-Hant-CN
301
+ //
302
+ // It is similar to RFC4646 (or actually the same),
303
+ // but seems to be limited to language, script, region
304
+
305
+ // Compute each locale with its country code.
306
+ // So this will return an array containing
307
+ // `de-DE` and `de`
308
+ // or
309
+ // `zh-hans-tw`, `zh-hans`, `zh`
310
+ // locales.
311
+ locales.forEach(function(locale) {
312
+ var localeParts = locale.split("-");
313
+ var firstFallback = null;
314
+ var secondFallback = null;
315
+ if (localeParts.length === 3) {
316
+ firstFallback = [
317
+ localeParts[0],
318
+ localeParts[1]
319
+ ].join("-");
320
+ secondFallback = localeParts[0];
321
+ }
322
+ else if (localeParts.length === 2) {
323
+ firstFallback = localeParts[0];
324
+ }
325
+
326
+ if (list.indexOf(locale) === -1) {
327
+ list.push(locale);
328
+ }
329
+
330
+ if (! I18n.fallbacks) {
331
+ return;
332
+ }
333
+
334
+ [
335
+ firstFallback,
336
+ secondFallback
337
+ ].forEach(function(nullableFallbackLocale) {
338
+ // We don't want null values
339
+ if (typeof nullableFallbackLocale === "undefined") { return; }
340
+ if (nullableFallbackLocale === null) { return; }
341
+ // We don't want duplicate values
342
+ //
343
+ // Comparing with `locale` first is faster than
344
+ // checking whether value's presence in the list
345
+ if (nullableFallbackLocale === locale) { return; }
346
+ if (list.indexOf(nullableFallbackLocale) !== -1) { return; }
347
+
348
+ list.push(nullableFallbackLocale);
349
+ });
349
350
  });
350
- });
351
-
352
- // No locales set? English it is.
353
- if (!locales.length) {
354
- locales.push("en");
355
- }
356
-
357
- return list;
358
- };
359
-
360
- // Hold pluralization rules.
361
- I18n.pluralization = {};
362
-
363
- // Return the pluralizer for a specific locale.
364
- // If no specify locale is found, then I18n's default will be used.
365
- I18n.pluralization.get = function(locale) {
366
- return this[locale] || this[I18n.locale] || this["default"];
367
- };
368
-
369
- // The default pluralizer rule.
370
- // It detects the `zero`, `one`, and `other` scopes.
371
- I18n.pluralization["default"] = function(count) {
372
- switch (count) {
373
- case 0: return ["zero", "other"];
374
- case 1: return ["one"];
375
- default: return ["other"];
376
- }
377
- };
378
-
379
- // Return current locale. If no locale has been set, then
380
- // the current locale will be the default locale.
381
- I18n.currentLocale = function() {
382
- return this.locale || this.defaultLocale;
383
- };
384
-
385
- // Check if value is different than undefined and null;
386
- I18n.isSet = isSet;
387
-
388
- // Find and process the translation using the provided scope and options.
389
- // This is used internally by some functions and should not be used as an
390
- // public API.
391
- I18n.lookup = function(scope, options) {
392
- options = options || {};
393
-
394
- var locales = this.locales.get(options.locale).slice()
395
- , locale
396
- , scopes
397
- , fullScope
398
- , translations
399
- ;
400
-
401
- fullScope = this.getFullScope(scope, options);
402
-
403
- while (locales.length) {
404
- locale = locales.shift();
405
- scopes = fullScope.split(options.separator || this.defaultSeparator);
406
- translations = this.translations[locale];
407
-
408
- if (!translations) {
409
- continue;
351
+
352
+ // No locales set? English it is.
353
+ if (!locales.length) {
354
+ locales.push("en");
410
355
  }
411
- while (scopes.length) {
412
- translations = translations[scopes.shift()];
413
-
414
- if (translations === undefined || translations === null) {
415
- break;
356
+
357
+ return list;
358
+ };
359
+
360
+ // Hold pluralization rules.
361
+ I18n.pluralization = {};
362
+
363
+ // Return the pluralizer for a specific locale.
364
+ // If no specify locale is found, then I18n's default will be used.
365
+ I18n.pluralization.get = function(locale) {
366
+ return this[locale] || this[I18n.locale] || this["default"];
367
+ };
368
+
369
+ // The default pluralizer rule.
370
+ // It detects the `zero`, `one`, and `other` scopes.
371
+ I18n.pluralization["default"] = function(count) {
372
+ switch (count) {
373
+ case 0: return ["zero", "other"];
374
+ case 1: return ["one"];
375
+ default: return ["other"];
376
+ }
377
+ };
378
+
379
+ // Return current locale. If no locale has been set, then
380
+ // the current locale will be the default locale.
381
+ I18n.currentLocale = function() {
382
+ return this.locale || this.defaultLocale;
383
+ };
384
+
385
+ // Check if value is different than undefined and null;
386
+ I18n.isSet = isSet;
387
+
388
+ // Find and process the translation using the provided scope and options.
389
+ // This is used internally by some functions and should not be used as an
390
+ // public API.
391
+ I18n.lookup = function(scope, options) {
392
+ options = options || {};
393
+
394
+ var locales = this.locales.get(options.locale).slice()
395
+ , locale
396
+ , scopes
397
+ , fullScope
398
+ , translations
399
+ ;
400
+
401
+ fullScope = this.getFullScope(scope, options);
402
+
403
+ while (locales.length) {
404
+ locale = locales.shift();
405
+ scopes = fullScope.split(options.separator || this.defaultSeparator);
406
+ translations = this.translations[locale];
407
+
408
+ if (!translations) {
409
+ continue;
410
+ }
411
+ while (scopes.length) {
412
+ translations = translations[scopes.shift()];
413
+
414
+ if (translations === undefined || translations === null) {
415
+ break;
416
+ }
417
+ }
418
+
419
+ if (translations !== undefined && translations !== null) {
420
+ return translations;
416
421
  }
417
422
  }
418
-
419
- if (translations !== undefined && translations !== null) {
420
- return translations;
423
+
424
+ if (isSet(options.defaultValue)) {
425
+ return lazyEvaluate(options.defaultValue, scope);
421
426
  }
422
- }
423
-
424
- if (isSet(options.defaultValue)) {
425
- return lazyEvaluate(options.defaultValue, scope);
426
- }
427
- };
428
-
429
- // lookup pluralization rule key into translations
430
- I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) {
431
- var pluralizer = this.pluralization.get(locale)
432
- , pluralizerKeys = pluralizer(count)
433
- , pluralizerKey
434
- , message;
435
-
436
- if (translations && isObject(translations)) {
437
- while (pluralizerKeys.length) {
438
- pluralizerKey = pluralizerKeys.shift();
439
- if (isSet(translations[pluralizerKey])) {
440
- message = translations[pluralizerKey];
427
+ };
428
+
429
+ // lookup pluralization rule key into translations
430
+ I18n.pluralizationLookupWithoutFallback = function(count, locale, translations) {
431
+ var pluralizer = this.pluralization.get(locale)
432
+ , pluralizerKeys = pluralizer(count)
433
+ , pluralizerKey
434
+ , message;
435
+
436
+ if (translations && isObject(translations)) {
437
+ while (pluralizerKeys.length) {
438
+ pluralizerKey = pluralizerKeys.shift();
439
+ if (isSet(translations[pluralizerKey])) {
440
+ message = translations[pluralizerKey];
441
+ break;
442
+ }
443
+ }
444
+ }
445
+
446
+ return message;
447
+ };
448
+
449
+ // Lookup dedicated to pluralization
450
+ I18n.pluralizationLookup = function(count, scope, options) {
451
+ options = options || {};
452
+ var locales = this.locales.get(options.locale).slice()
453
+ , locale
454
+ , scopes
455
+ , translations
456
+ , message
457
+ ;
458
+ scope = this.getFullScope(scope, options);
459
+
460
+ while (locales.length) {
461
+ locale = locales.shift();
462
+ scopes = scope.split(options.separator || this.defaultSeparator);
463
+ translations = this.translations[locale];
464
+
465
+ if (!translations) {
466
+ continue;
467
+ }
468
+
469
+ while (scopes.length) {
470
+ translations = translations[scopes.shift()];
471
+ if (!isObject(translations)) {
472
+ break;
473
+ }
474
+ if (scopes.length === 0) {
475
+ message = this.pluralizationLookupWithoutFallback(count, locale, translations);
476
+ }
477
+ }
478
+ if (typeof message !== "undefined" && message !== null) {
441
479
  break;
442
480
  }
443
481
  }
444
- }
445
-
446
- return message;
447
- };
448
-
449
- // Lookup dedicated to pluralization
450
- I18n.pluralizationLookup = function(count, scope, options) {
451
- options = options || {};
452
- var locales = this.locales.get(options.locale).slice()
453
- , locale
454
- , scopes
455
- , translations
456
- , message
457
- ;
458
- scope = this.getFullScope(scope, options);
459
-
460
- while (locales.length) {
461
- locale = locales.shift();
462
- scopes = scope.split(options.separator || this.defaultSeparator);
463
- translations = this.translations[locale];
464
-
465
- if (!translations) {
466
- continue;
482
+
483
+ if (typeof message === "undefined" || message === null) {
484
+ if (isSet(options.defaultValue)) {
485
+ if (isObject(options.defaultValue)) {
486
+ message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue);
487
+ } else {
488
+ message = options.defaultValue;
489
+ }
490
+ translations = options.defaultValue;
491
+ }
467
492
  }
468
-
469
- while (scopes.length) {
470
- translations = translations[scopes.shift()];
471
- if (!isObject(translations)) {
472
- break;
493
+
494
+ return { message: message, translations: translations };
495
+ };
496
+
497
+ // Rails changed the way the meridian is stored.
498
+ // It started with `date.meridian` returning an array,
499
+ // then it switched to `time.am` and `time.pm`.
500
+ // This function abstracts this difference and returns
501
+ // the correct meridian or the default value when none is provided.
502
+ I18n.meridian = function() {
503
+ var time = this.lookup("time");
504
+ var date = this.lookup("date");
505
+
506
+ if (time && time.am && time.pm) {
507
+ return [time.am, time.pm];
508
+ } else if (date && date.meridian) {
509
+ return date.meridian;
510
+ } else {
511
+ return DATE.meridian;
512
+ }
513
+ };
514
+
515
+ // Merge serveral hash options, checking if value is set before
516
+ // overwriting any value. The precedence is from left to right.
517
+ //
518
+ // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
519
+ // #=> {name: "John Doe", role: "user"}
520
+ //
521
+ I18n.prepareOptions = function() {
522
+ var args = slice.call(arguments)
523
+ , options = {}
524
+ , subject
525
+ ;
526
+
527
+ while (args.length) {
528
+ subject = args.shift();
529
+
530
+ if (typeof(subject) != "object") {
531
+ continue;
473
532
  }
474
- if (scopes.length === 0) {
475
- message = this.pluralizationLookupWithoutFallback(count, locale, translations);
533
+
534
+ for (var attr in subject) {
535
+ if (!subject.hasOwnProperty(attr)) {
536
+ continue;
537
+ }
538
+
539
+ if (isSet(options[attr])) {
540
+ continue;
541
+ }
542
+
543
+ options[attr] = subject[attr];
476
544
  }
477
545
  }
478
- if (typeof message !== "undefined" && message !== null) {
479
- break;
546
+
547
+ return options;
548
+ };
549
+
550
+ // Generate a list of translation options for default fallbacks.
551
+ // `defaultValue` is also deleted from options as it is returned as part of
552
+ // the translationOptions array.
553
+ I18n.createTranslationOptions = function(scope, options) {
554
+ var translationOptions = [{scope: scope}];
555
+
556
+ // Defaults should be an array of hashes containing either
557
+ // fallback scopes or messages
558
+ if (isSet(options.defaults)) {
559
+ translationOptions = translationOptions.concat(options.defaults);
480
560
  }
481
- }
482
-
483
- if (typeof message === "undefined" || message === null) {
561
+
562
+ // Maintain support for defaultValue. Since it is always a message
563
+ // insert it in to the translation options as such.
484
564
  if (isSet(options.defaultValue)) {
485
- if (isObject(options.defaultValue)) {
486
- message = this.pluralizationLookupWithoutFallback(count, options.locale, options.defaultValue);
565
+ translationOptions.push({ message: options.defaultValue });
566
+ }
567
+
568
+ return translationOptions;
569
+ };
570
+
571
+ // Translate the given scope with the provided options.
572
+ I18n.translate = function(scope, options) {
573
+ options = options || {};
574
+
575
+ var translationOptions = this.createTranslationOptions(scope, options);
576
+
577
+ var translation;
578
+ var usedScope = scope;
579
+
580
+ var optionsWithoutDefault = this.prepareOptions(options)
581
+ delete optionsWithoutDefault.defaultValue
582
+
583
+ // Iterate through the translation options until a translation
584
+ // or message is found.
585
+ var translationFound =
586
+ translationOptions.some(function(translationOption) {
587
+ if (isSet(translationOption.scope)) {
588
+ usedScope = translationOption.scope;
589
+ translation = this.lookup(usedScope, optionsWithoutDefault);
590
+ } else if (isSet(translationOption.message)) {
591
+ translation = lazyEvaluate(translationOption.message, scope);
592
+ }
593
+
594
+ if (translation !== undefined && translation !== null) {
595
+ return true;
596
+ }
597
+ }, this);
598
+
599
+ if (!translationFound) {
600
+ return this.missingTranslation(scope, options);
601
+ }
602
+
603
+ if (typeof(translation) === "string") {
604
+ translation = this.interpolate(translation, options);
605
+ } else if (isArray(translation)) {
606
+ translation = translation.map(function(t) {
607
+ return (typeof(t) === "string" ? this.interpolate(t, options) : t);
608
+ }, this);
609
+ } else if (isObject(translation) && isSet(options.count)) {
610
+ translation = this.pluralize(options.count, usedScope, options);
611
+ }
612
+
613
+ return translation;
614
+ };
615
+
616
+ // This function interpolates the all variables in the given message.
617
+ I18n.interpolate = function(message, options) {
618
+ if (message == null) {
619
+ return message;
620
+ }
621
+
622
+ options = options || {};
623
+ var matches = message.match(this.placeholder)
624
+ , placeholder
625
+ , value
626
+ , name
627
+ , regex
628
+ ;
629
+
630
+ if (!matches) {
631
+ return message;
632
+ }
633
+
634
+ while (matches.length) {
635
+ placeholder = matches.shift();
636
+ name = placeholder.replace(this.placeholder, "$1");
637
+
638
+ if (isSet(options[name])) {
639
+ value = options[name].toString().replace(/\$/gm, "_#$#_");
640
+ } else if (name in options) {
641
+ value = this.nullPlaceholder(placeholder, message, options);
487
642
  } else {
488
- message = options.defaultValue;
643
+ value = this.missingPlaceholder(placeholder, message, options);
489
644
  }
490
- translations = options.defaultValue;
645
+
646
+ regex = new RegExp(placeholder.replace(/{/gm, "\\{").replace(/}/gm, "\\}"));
647
+ message = message.replace(regex, value);
491
648
  }
492
- }
493
-
494
- return { message: message, translations: translations };
495
- };
496
-
497
- // Rails changed the way the meridian is stored.
498
- // It started with `date.meridian` returning an array,
499
- // then it switched to `time.am` and `time.pm`.
500
- // This function abstracts this difference and returns
501
- // the correct meridian or the default value when none is provided.
502
- I18n.meridian = function() {
503
- var time = this.lookup("time");
504
- var date = this.lookup("date");
505
-
506
- if (time && time.am && time.pm) {
507
- return [time.am, time.pm];
508
- } else if (date && date.meridian) {
509
- return date.meridian;
510
- } else {
511
- return DATE.meridian;
512
- }
513
- };
514
-
515
- // Merge serveral hash options, checking if value is set before
516
- // overwriting any value. The precedence is from left to right.
517
- //
518
- // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
519
- // #=> {name: "John Doe", role: "user"}
520
- //
521
- I18n.prepareOptions = function() {
522
- var args = slice.call(arguments)
523
- , options = {}
524
- , subject
525
- ;
526
-
527
- while (args.length) {
528
- subject = args.shift();
529
-
530
- if (typeof(subject) != "object") {
531
- continue;
649
+
650
+ return message.replace(/_#\$#_/g, "$");
651
+ };
652
+
653
+ // Pluralize the given scope using the `count` value.
654
+ // The pluralized translation may have other placeholders,
655
+ // which will be retrieved from `options`.
656
+ I18n.pluralize = function(count, scope, options) {
657
+ options = this.prepareOptions({count: String(count)}, options)
658
+ var pluralizer, result;
659
+
660
+ result = this.pluralizationLookup(count, scope, options);
661
+ if (typeof result.translations === "undefined" || result.translations == null) {
662
+ return this.missingTranslation(scope, options);
532
663
  }
533
-
534
- for (var attr in subject) {
535
- if (!subject.hasOwnProperty(attr)) {
536
- continue;
537
- }
538
-
539
- if (isSet(options[attr])) {
540
- continue;
541
- }
542
-
543
- options[attr] = subject[attr];
664
+
665
+ if (typeof result.message !== "undefined" && result.message != null) {
666
+ return this.interpolate(result.message, options);
544
667
  }
545
- }
546
-
547
- return options;
548
- };
549
-
550
- // Generate a list of translation options for default fallbacks.
551
- // `defaultValue` is also deleted from options as it is returned as part of
552
- // the translationOptions array.
553
- I18n.createTranslationOptions = function(scope, options) {
554
- var translationOptions = [{scope: scope}];
555
-
556
- // Defaults should be an array of hashes containing either
557
- // fallback scopes or messages
558
- if (isSet(options.defaults)) {
559
- translationOptions = translationOptions.concat(options.defaults);
560
- }
561
-
562
- // Maintain support for defaultValue. Since it is always a message
563
- // insert it in to the translation options as such.
564
- if (isSet(options.defaultValue)) {
565
- translationOptions.push({ message: options.defaultValue });
566
- }
567
-
568
- return translationOptions;
569
- };
570
-
571
- // Translate the given scope with the provided options.
572
- I18n.translate = function(scope, options) {
573
- options = options || {};
574
-
575
- var translationOptions = this.createTranslationOptions(scope, options);
576
-
577
- var translation;
578
- var usedScope = scope;
579
-
580
- var optionsWithoutDefault = this.prepareOptions(options)
581
- delete optionsWithoutDefault.defaultValue
582
-
583
- // Iterate through the translation options until a translation
584
- // or message is found.
585
- var translationFound =
586
- translationOptions.some(function(translationOption) {
587
- if (isSet(translationOption.scope)) {
588
- usedScope = translationOption.scope;
589
- translation = this.lookup(usedScope, optionsWithoutDefault);
590
- } else if (isSet(translationOption.message)) {
591
- translation = lazyEvaluate(translationOption.message, scope);
592
- }
593
-
594
- if (translation !== undefined && translation !== null) {
595
- return true;
596
- }
597
- }, this);
598
-
599
- if (!translationFound) {
600
- return this.missingTranslation(scope, options);
601
- }
602
-
603
- if (typeof(translation) === "string") {
604
- translation = this.interpolate(translation, options);
605
- } else if (isArray(translation)) {
606
- translation = translation.map(function(t) {
607
- return (typeof(t) === "string" ? this.interpolate(t, options) : t);
608
- }, this);
609
- } else if (isObject(translation) && isSet(options.count)) {
610
- translation = this.pluralize(options.count, usedScope, options);
611
- }
612
-
613
- return translation;
614
- };
615
-
616
- // This function interpolates the all variables in the given message.
617
- I18n.interpolate = function(message, options) {
618
- if (message == null) {
619
- return message;
620
- }
621
-
622
- options = options || {};
623
- var matches = message.match(this.placeholder)
624
- , placeholder
625
- , value
626
- , name
627
- , regex
628
- ;
629
-
630
- if (!matches) {
631
- return message;
632
- }
633
-
634
- while (matches.length) {
635
- placeholder = matches.shift();
636
- name = placeholder.replace(this.placeholder, "$1");
637
-
638
- if (isSet(options[name])) {
639
- value = options[name].toString().replace(/\$/gm, "_#$#_");
640
- } else if (name in options) {
641
- value = this.nullPlaceholder(placeholder, message, options);
642
- } else {
643
- value = this.missingPlaceholder(placeholder, message, options);
668
+ else {
669
+ pluralizer = this.pluralization.get(options.locale);
670
+ return this.missingTranslation(scope + '.' + pluralizer(count)[0], options);
644
671
  }
645
-
646
- regex = new RegExp(placeholder.replace(/{/gm, "\\{").replace(/}/gm, "\\}"));
647
- message = message.replace(regex, value);
648
- }
649
-
650
- return message.replace(/_#\$#_/g, "$");
651
- };
652
-
653
- // Pluralize the given scope using the `count` value.
654
- // The pluralized translation may have other placeholders,
655
- // which will be retrieved from `options`.
656
- I18n.pluralize = function(count, scope, options) {
657
- options = this.prepareOptions({count: String(count)}, options)
658
- var pluralizer, result;
659
-
660
- result = this.pluralizationLookup(count, scope, options);
661
- if (typeof result.translations === "undefined" || result.translations == null) {
662
- return this.missingTranslation(scope, options);
663
- }
664
-
665
- if (typeof result.message !== "undefined" && result.message != null) {
666
- return this.interpolate(result.message, options);
667
- }
668
- else {
669
- pluralizer = this.pluralization.get(options.locale);
670
- return this.missingTranslation(scope + '.' + pluralizer(count)[0], options);
671
- }
672
- };
673
-
674
- // Return a missing translation message for the given parameters.
675
- I18n.missingTranslation = function(scope, options) {
676
- //guess intended string
677
- if(this.missingBehaviour === 'guess'){
678
- //get only the last portion of the scope
679
- var s = scope.split('.').slice(-1)[0];
680
- //replace underscore with space && camelcase with space and lowercase letter
681
- return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') +
682
- s.replace(/_/g,' ').replace(/([a-z])([A-Z])/g,
683
- function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} );
684
- }
685
-
686
- var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale();
687
- var fullScope = this.getFullScope(scope, options);
688
- var fullScopeWithLocale = [localeForTranslation, fullScope].join(options.separator || this.defaultSeparator);
689
-
690
- return '[missing "' + fullScopeWithLocale + '" translation]';
691
- };
692
-
693
- // Return a missing placeholder message for given parameters
694
- I18n.missingPlaceholder = function(placeholder, message, options) {
695
- return "[missing " + placeholder + " value]";
696
- };
697
-
698
- I18n.nullPlaceholder = function() {
699
- return I18n.missingPlaceholder.apply(I18n, arguments);
700
- };
701
-
702
- // Format number using localization rules.
703
- // The options will be retrieved from the `number.format` scope.
704
- // If this isn't present, then the following options will be used:
705
- //
706
- // - `precision`: `3`
707
- // - `separator`: `"."`
708
- // - `delimiter`: `","`
709
- // - `strip_insignificant_zeros`: `false`
710
- //
711
- // You can also override these options by providing the `options` argument.
712
- //
713
- I18n.toNumber = function(number, options) {
714
- options = this.prepareOptions(
715
- options
716
- , this.lookup("number.format")
717
- , NUMBER_FORMAT
718
- );
719
-
720
- var negative = number < 0
721
- , string = toFixed(Math.abs(number), options.precision).toString()
722
- , parts = string.split(".")
723
- , precision
724
- , buffer = []
725
- , formattedNumber
726
- , format = options.format || "%n"
727
- , sign = negative ? "-" : ""
728
- ;
729
-
730
- number = parts[0];
731
- precision = parts[1];
732
-
733
- while (number.length > 0) {
734
- buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
735
- number = number.substr(0, number.length -3);
736
- }
737
-
738
- formattedNumber = buffer.join(options.delimiter);
739
-
740
- if (options.strip_insignificant_zeros && precision) {
741
- precision = precision.replace(/0+$/, "");
742
- }
743
-
744
- if (options.precision > 0 && precision) {
745
- formattedNumber += options.separator + precision;
746
- }
747
-
748
- if (options.sign_first) {
749
- format = "%s" + format;
750
- }
751
- else {
752
- format = format.replace("%n", "%s%n");
753
- }
754
-
755
- formattedNumber = format
756
- .replace("%u", options.unit)
757
- .replace("%n", formattedNumber)
758
- .replace("%s", sign)
759
- ;
760
-
761
- return formattedNumber;
762
- };
763
-
764
- // Format currency with localization rules.
765
- // The options will be retrieved from the `number.currency.format` and
766
- // `number.format` scopes, in that order.
767
- //
768
- // Any missing option will be retrieved from the `I18n.toNumber` defaults and
769
- // the following options:
770
- //
771
- // - `unit`: `"$"`
772
- // - `precision`: `2`
773
- // - `format`: `"%u%n"`
774
- // - `delimiter`: `","`
775
- // - `separator`: `"."`
776
- //
777
- // You can also override these options by providing the `options` argument.
778
- //
779
- I18n.toCurrency = function(number, options) {
780
- options = this.prepareOptions(
781
- options
782
- , this.lookup("number.currency.format", options)
783
- , this.lookup("number.format", options)
784
- , CURRENCY_FORMAT
785
- );
786
-
787
- return this.toNumber(number, options);
788
- };
789
-
790
- // Localize several values.
791
- // You can provide the following scopes: `currency`, `number`, or `percentage`.
792
- // If you provide a scope that matches the `/^(date|time)/` regular expression
793
- // then the `value` will be converted by using the `I18n.toTime` function.
794
- //
795
- // It will default to the value's `toString` function.
796
- //
797
- I18n.localize = function(scope, value, options) {
798
- options || (options = {});
799
-
800
- switch (scope) {
801
- case "currency":
802
- return this.toCurrency(value, options);
803
- case "number":
804
- scope = this.lookup("number.format", options);
805
- return this.toNumber(value, scope);
806
- case "percentage":
807
- return this.toPercentage(value, options);
808
- default:
809
- var localizedValue;
810
-
811
- if (scope.match(/^(date|time)/)) {
812
- localizedValue = this.toTime(scope, value, options);
672
+ };
673
+
674
+ // Return a missing translation message for the given parameters.
675
+ I18n.missingTranslation = function(scope, options) {
676
+ //guess intended string
677
+ if(this.missingBehaviour === 'guess'){
678
+ //get only the last portion of the scope
679
+ var s = scope.split('.').slice(-1)[0];
680
+ //replace underscore with space && camelcase with space and lowercase letter
681
+ return (this.missingTranslationPrefix.length > 0 ? this.missingTranslationPrefix : '') +
682
+ s.replace(/_/g,' ').replace(/([a-z])([A-Z])/g,
683
+ function(match, p1, p2) {return p1 + ' ' + p2.toLowerCase()} );
684
+ }
685
+
686
+ var localeForTranslation = (options != null && options.locale != null) ? options.locale : this.currentLocale();
687
+ var fullScope = this.getFullScope(scope, options);
688
+ var fullScopeWithLocale = [localeForTranslation, fullScope].join(options.separator || this.defaultSeparator);
689
+
690
+ return '[missing "' + fullScopeWithLocale + '" translation]';
691
+ };
692
+
693
+ // Return a missing placeholder message for given parameters
694
+ I18n.missingPlaceholder = function(placeholder, message, options) {
695
+ return "[missing " + placeholder + " value]";
696
+ };
697
+
698
+ I18n.nullPlaceholder = function() {
699
+ return I18n.missingPlaceholder.apply(I18n, arguments);
700
+ };
701
+
702
+ // Format number using localization rules.
703
+ // The options will be retrieved from the `number.format` scope.
704
+ // If this isn't present, then the following options will be used:
705
+ //
706
+ // - `precision`: `3`
707
+ // - `separator`: `"."`
708
+ // - `delimiter`: `","`
709
+ // - `strip_insignificant_zeros`: `false`
710
+ //
711
+ // You can also override these options by providing the `options` argument.
712
+ //
713
+ I18n.toNumber = function(number, options) {
714
+ options = this.prepareOptions(
715
+ options
716
+ , this.lookup("number.format")
717
+ , NUMBER_FORMAT
718
+ );
719
+
720
+ var negative = number < 0
721
+ , string = toFixed(Math.abs(number), options.precision).toString()
722
+ , parts = string.split(".")
723
+ , precision
724
+ , buffer = []
725
+ , formattedNumber
726
+ , format = options.format || "%n"
727
+ , sign = negative ? "-" : ""
728
+ ;
729
+
730
+ number = parts[0];
731
+ precision = parts[1];
732
+
733
+ while (number.length > 0) {
734
+ buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
735
+ number = number.substr(0, number.length -3);
736
+ }
737
+
738
+ formattedNumber = buffer.join(options.delimiter);
739
+
740
+ if (options.strip_insignificant_zeros && precision) {
741
+ precision = precision.replace(/0+$/, "");
742
+ }
743
+
744
+ if (options.precision > 0 && precision) {
745
+ formattedNumber += options.separator + precision;
746
+ }
747
+
748
+ if (options.sign_first) {
749
+ format = "%s" + format;
750
+ }
751
+ else {
752
+ format = format.replace("%n", "%s%n");
753
+ }
754
+
755
+ formattedNumber = format
756
+ .replace("%u", options.unit)
757
+ .replace("%n", formattedNumber)
758
+ .replace("%s", sign)
759
+ ;
760
+
761
+ return formattedNumber;
762
+ };
763
+
764
+ // Format currency with localization rules.
765
+ // The options will be retrieved from the `number.currency.format` and
766
+ // `number.format` scopes, in that order.
767
+ //
768
+ // Any missing option will be retrieved from the `I18n.toNumber` defaults and
769
+ // the following options:
770
+ //
771
+ // - `unit`: `"$"`
772
+ // - `precision`: `2`
773
+ // - `format`: `"%u%n"`
774
+ // - `delimiter`: `","`
775
+ // - `separator`: `"."`
776
+ //
777
+ // You can also override these options by providing the `options` argument.
778
+ //
779
+ I18n.toCurrency = function(number, options) {
780
+ options = this.prepareOptions(
781
+ options
782
+ , this.lookup("number.currency.format", options)
783
+ , this.lookup("number.format", options)
784
+ , CURRENCY_FORMAT
785
+ );
786
+
787
+ return this.toNumber(number, options);
788
+ };
789
+
790
+ // Localize several values.
791
+ // You can provide the following scopes: `currency`, `number`, or `percentage`.
792
+ // If you provide a scope that matches the `/^(date|time)/` regular expression
793
+ // then the `value` will be converted by using the `I18n.toTime` function.
794
+ //
795
+ // It will default to the value's `toString` function.
796
+ //
797
+ I18n.localize = function(scope, value, options) {
798
+ options || (options = {});
799
+
800
+ switch (scope) {
801
+ case "currency":
802
+ return this.toCurrency(value, options);
803
+ case "number":
804
+ scope = this.lookup("number.format", options);
805
+ return this.toNumber(value, scope);
806
+ case "percentage":
807
+ return this.toPercentage(value, options);
808
+ default:
809
+ var localizedValue;
810
+
811
+ if (scope.match(/^(date|time)/)) {
812
+ localizedValue = this.toTime(scope, value, options);
813
+ } else {
814
+ localizedValue = value.toString();
815
+ }
816
+
817
+ return this.interpolate(localizedValue, options);
818
+ }
819
+ };
820
+
821
+ // Parse a given `date` string into a JavaScript Date object.
822
+ // This function is time zone aware.
823
+ //
824
+ // The following string formats are recognized:
825
+ //
826
+ // yyyy-mm-dd
827
+ // yyyy-mm-dd[ T]hh:mm::ss
828
+ // yyyy-mm-dd[ T]hh:mm::ss
829
+ // yyyy-mm-dd[ T]hh:mm::ssZ
830
+ // yyyy-mm-dd[ T]hh:mm::ss+0000
831
+ // yyyy-mm-dd[ T]hh:mm::ss+00:00
832
+ // yyyy-mm-dd[ T]hh:mm::ss.123Z
833
+ //
834
+ I18n.parseDate = function(date) {
835
+ var matches, convertedDate, fraction;
836
+ // A date input of `null` or `undefined` will be returned as-is
837
+ if (date == null) {
838
+ return date;
839
+ }
840
+ // we have a date, so just return it.
841
+ if (typeof(date) === "object") {
842
+ return date;
843
+ }
844
+
845
+ matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/);
846
+
847
+ if (matches) {
848
+ for (var i = 1; i <= 6; i++) {
849
+ matches[i] = parseInt(matches[i], 10) || 0;
850
+ }
851
+
852
+ // month starts on 0
853
+ matches[2] -= 1;
854
+
855
+ fraction = matches[7] ? 1000 * ("0" + matches[7]) : null;
856
+
857
+ if (matches[8]) {
858
+ convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction));
813
859
  } else {
814
- localizedValue = value.toString();
860
+ convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction);
815
861
  }
816
-
817
- return this.interpolate(localizedValue, options);
818
- }
819
- };
820
-
821
- // Parse a given `date` string into a JavaScript Date object.
822
- // This function is time zone aware.
823
- //
824
- // The following string formats are recognized:
825
- //
826
- // yyyy-mm-dd
827
- // yyyy-mm-dd[ T]hh:mm::ss
828
- // yyyy-mm-dd[ T]hh:mm::ss
829
- // yyyy-mm-dd[ T]hh:mm::ssZ
830
- // yyyy-mm-dd[ T]hh:mm::ss+0000
831
- // yyyy-mm-dd[ T]hh:mm::ss+00:00
832
- // yyyy-mm-dd[ T]hh:mm::ss.123Z
833
- //
834
- I18n.parseDate = function(date) {
835
- var matches, convertedDate, fraction;
836
- // A date input of `null` or `undefined` will be returned as-is
837
- if (date == null) {
838
- return date;
839
- }
840
- // we have a date, so just return it.
841
- if (typeof(date) === "object") {
842
- return date;
843
- }
844
-
845
- matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/);
846
-
847
- if (matches) {
848
- for (var i = 1; i <= 6; i++) {
849
- matches[i] = parseInt(matches[i], 10) || 0;
850
- }
851
-
852
- // month starts on 0
853
- matches[2] -= 1;
854
-
855
- fraction = matches[7] ? 1000 * ("0" + matches[7]) : null;
856
-
857
- if (matches[8]) {
858
- convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction));
862
+ } else if (typeof(date) == "number") {
863
+ // UNIX timestamp
864
+ convertedDate = new Date();
865
+ convertedDate.setTime(date);
866
+ } else if (date.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)) {
867
+ // This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by
868
+ // webkit/firefox, but not by IE, so we must parse it manually.
869
+ convertedDate = new Date();
870
+ convertedDate.setTime(Date.parse([
871
+ RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5
872
+ ].join(" ")));
873
+ } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
874
+ // a valid javascript format with timezone info
875
+ convertedDate = new Date();
876
+ convertedDate.setTime(Date.parse(date));
859
877
  } else {
860
- convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction);
878
+ // an arbitrary javascript string
879
+ convertedDate = new Date();
880
+ convertedDate.setTime(Date.parse(date));
861
881
  }
862
- } else if (typeof(date) == "number") {
863
- // UNIX timestamp
864
- convertedDate = new Date();
865
- convertedDate.setTime(date);
866
- } else if (date.match(/([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/)) {
867
- // This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by
868
- // webkit/firefox, but not by IE, so we must parse it manually.
869
- convertedDate = new Date();
870
- convertedDate.setTime(Date.parse([
871
- RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5
872
- ].join(" ")));
873
- } else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
874
- // a valid javascript format with timezone info
875
- convertedDate = new Date();
876
- convertedDate.setTime(Date.parse(date));
877
- } else {
878
- // an arbitrary javascript string
879
- convertedDate = new Date();
880
- convertedDate.setTime(Date.parse(date));
881
- }
882
-
883
- return convertedDate;
884
- };
885
-
886
- // Formats time according to the directives in the given format string.
887
- // The directives begins with a percent (%) character. Any text not listed as a
888
- // directive will be passed through to the output string.
889
- //
890
- // The accepted formats are:
891
- //
892
- // %a - The abbreviated weekday name (Sun)
893
- // %A - The full weekday name (Sunday)
894
- // %b - The abbreviated month name (Jan)
895
- // %B - The full month name (January)
896
- // %c - The preferred local date and time representation
897
- // %d - Day of the month (01..31)
898
- // %-d - Day of the month (1..31)
899
- // %H - Hour of the day, 24-hour clock (00..23)
900
- // %-H/%k - Hour of the day, 24-hour clock (0..23)
901
- // %I - Hour of the day, 12-hour clock (01..12)
902
- // %-I/%l - Hour of the day, 12-hour clock (1..12)
903
- // %m - Month of the year (01..12)
904
- // %-m - Month of the year (1..12)
905
- // %M - Minute of the hour (00..59)
906
- // %-M - Minute of the hour (0..59)
907
- // %p - Meridian indicator (AM or PM)
908
- // %P - Meridian indicator (am or pm)
909
- // %S - Second of the minute (00..60)
910
- // %-S - Second of the minute (0..60)
911
- // %w - Day of the week (Sunday is 0, 0..6)
912
- // %y - Year without a century (00..99)
913
- // %-y - Year without a century (0..99)
914
- // %Y - Year with century
915
- // %z/%Z - Timezone offset (+0545)
916
- //
917
- I18n.strftime = function(date, format, options) {
918
- var options = this.lookup("date", options)
919
- , meridianOptions = I18n.meridian()
920
- ;
921
-
922
- if (!options) {
923
- options = {};
924
- }
925
-
926
- options = this.prepareOptions(options, DATE);
927
-
928
- if (isNaN(date.getTime())) {
929
- throw new Error('I18n.strftime() requires a valid date object, but received an invalid date.');
930
- }
931
-
932
- var weekDay = date.getDay()
933
- , day = date.getDate()
934
- , year = date.getFullYear()
935
- , month = date.getMonth() + 1
936
- , hour = date.getHours()
937
- , hour12 = hour
938
- , meridian = hour > 11 ? 1 : 0
939
- , secs = date.getSeconds()
940
- , mins = date.getMinutes()
941
- , offset = date.getTimezoneOffset()
942
- , absOffsetHours = Math.floor(Math.abs(offset / 60))
943
- , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
944
- , timezoneoffset = (offset > 0 ? "-" : "+") +
945
- (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) +
946
- (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
947
- ;
948
-
949
- if (hour12 > 12) {
950
- hour12 = hour12 - 12;
951
- } else if (hour12 === 0) {
952
- hour12 = 12;
953
- }
954
-
955
- format = format.replace("%a", options.abbr_day_names[weekDay]);
956
- format = format.replace("%A", options.day_names[weekDay]);
957
- format = format.replace("%b", options.abbr_month_names[month]);
958
- format = format.replace("%B", options.month_names[month]);
959
- format = format.replace("%d", padding(day));
960
- format = format.replace("%e", day);
961
- format = format.replace("%-d", day);
962
- format = format.replace("%H", padding(hour));
963
- format = format.replace("%-H", hour);
964
- format = format.replace("%k", hour);
965
- format = format.replace("%I", padding(hour12));
966
- format = format.replace("%-I", hour12);
967
- format = format.replace("%l", hour12);
968
- format = format.replace("%m", padding(month));
969
- format = format.replace("%-m", month);
970
- format = format.replace("%M", padding(mins));
971
- format = format.replace("%-M", mins);
972
- format = format.replace("%p", meridianOptions[meridian]);
973
- format = format.replace("%P", meridianOptions[meridian].toLowerCase());
974
- format = format.replace("%S", padding(secs));
975
- format = format.replace("%-S", secs);
976
- format = format.replace("%w", weekDay);
977
- format = format.replace("%y", padding(year));
978
- format = format.replace("%-y", padding(year).replace(/^0+/, ""));
979
- format = format.replace("%Y", year);
980
- format = format.replace("%z", timezoneoffset);
981
- format = format.replace("%Z", timezoneoffset);
982
-
983
- return format;
984
- };
985
-
986
- // Convert the given dateString into a formatted date.
987
- I18n.toTime = function(scope, dateString, options) {
988
- var date = this.parseDate(dateString)
989
- , format = this.lookup(scope, options)
990
- ;
991
-
992
- // A date input of `null` or `undefined` will be returned as-is
993
- if (date == null) {
994
- return date;
995
- }
996
-
997
- var date_string = date.toString()
998
- if (date_string.match(/invalid/i)) {
999
- return date_string;
1000
- }
1001
-
1002
- if (!format) {
1003
- return date_string;
1004
- }
1005
-
1006
- return this.strftime(date, format, options);
1007
- };
1008
-
1009
- // Convert a number into a formatted percentage value.
1010
- I18n.toPercentage = function(number, options) {
1011
- options = this.prepareOptions(
1012
- options
1013
- , this.lookup("number.percentage.format", options)
1014
- , this.lookup("number.format", options)
1015
- , PERCENTAGE_FORMAT
1016
- );
1017
-
1018
- return this.toNumber(number, options);
1019
- };
1020
-
1021
- // Convert a number into a readable size representation.
1022
- I18n.toHumanSize = function(number, options) {
1023
- var kb = 1024
1024
- , size = number
1025
- , iterations = 0
1026
- , unit
1027
- , precision
1028
- , fullScope
1029
- ;
1030
-
1031
- while (size >= kb && iterations < 4) {
1032
- size = size / kb;
1033
- iterations += 1;
1034
- }
1035
-
1036
- if (iterations === 0) {
1037
- fullScope = this.getFullScope("number.human.storage_units.units.byte", options);
1038
- unit = this.t(fullScope, {count: size});
1039
- precision = 0;
1040
- } else {
1041
- fullScope = this.getFullScope("number.human.storage_units.units." + SIZE_UNITS[iterations], options);
1042
- unit = this.t(fullScope);
1043
- precision = (size - Math.floor(size) === 0) ? 0 : 1;
1044
- }
1045
-
1046
- options = this.prepareOptions(
1047
- options
1048
- , {unit: unit, precision: precision, format: "%n%u", delimiter: ""}
1049
- );
1050
-
1051
- return this.toNumber(size, options);
1052
- };
1053
-
1054
- I18n.getFullScope = function(scope, options) {
1055
- options = options || {};
1056
-
1057
- // Deal with the scope as an array.
1058
- if (isArray(scope)) {
1059
- scope = scope.join(options.separator || this.defaultSeparator);
1060
- }
1061
-
1062
- // Deal with the scope option provided through the second argument.
882
+
883
+ return convertedDate;
884
+ };
885
+
886
+ // Formats time according to the directives in the given format string.
887
+ // The directives begins with a percent (%) character. Any text not listed as a
888
+ // directive will be passed through to the output string.
1063
889
  //
1064
- // I18n.t('hello', {scope: 'greetings'});
890
+ // The accepted formats are:
1065
891
  //
1066
- if (options.scope) {
1067
- scope = [options.scope, scope].join(options.separator || this.defaultSeparator);
1068
- }
1069
-
1070
- return scope;
1071
- };
1072
- /**
1073
- * Merge obj1 with obj2 (shallow merge), without modifying inputs
1074
- * @param {Object} obj1
1075
- * @param {Object} obj2
1076
- * @returns {Object} Merged values of obj1 and obj2
1077
- *
1078
- * In order to support ES3, `Object.prototype.hasOwnProperty.call` is used
1079
- * Idea is from:
1080
- * https://stackoverflow.com/questions/8157700/object-has-no-hasownproperty-method-i-e-its-undefined-ie8
1081
- */
1082
- I18n.extend = function ( obj1, obj2 ) {
1083
- if (typeof(obj1) === "undefined" && typeof(obj2) === "undefined") {
1084
- return {};
1085
- }
1086
- return merge(obj1, obj2);
1087
- };
1088
-
1089
- // Set aliases, so we can save some typing.
1090
- I18n.t = I18n.translate.bind(I18n);
1091
- I18n.l = I18n.localize.bind(I18n);
1092
- I18n.p = I18n.pluralize.bind(I18n);
1093
-
1094
- return I18n;
1095
- }));
892
+ // %a - The abbreviated weekday name (Sun)
893
+ // %A - The full weekday name (Sunday)
894
+ // %b - The abbreviated month name (Jan)
895
+ // %B - The full month name (January)
896
+ // %c - The preferred local date and time representation
897
+ // %d - Day of the month (01..31)
898
+ // %-d - Day of the month (1..31)
899
+ // %H - Hour of the day, 24-hour clock (00..23)
900
+ // %-H/%k - Hour of the day, 24-hour clock (0..23)
901
+ // %I - Hour of the day, 12-hour clock (01..12)
902
+ // %-I/%l - Hour of the day, 12-hour clock (1..12)
903
+ // %m - Month of the year (01..12)
904
+ // %-m - Month of the year (1..12)
905
+ // %M - Minute of the hour (00..59)
906
+ // %-M - Minute of the hour (0..59)
907
+ // %p - Meridian indicator (AM or PM)
908
+ // %P - Meridian indicator (am or pm)
909
+ // %S - Second of the minute (00..60)
910
+ // %-S - Second of the minute (0..60)
911
+ // %w - Day of the week (Sunday is 0, 0..6)
912
+ // %y - Year without a century (00..99)
913
+ // %-y - Year without a century (0..99)
914
+ // %Y - Year with century
915
+ // %z/%Z - Timezone offset (+0545)
916
+ //
917
+ I18n.strftime = function(date, format, options) {
918
+ var options = this.lookup("date", options)
919
+ , meridianOptions = I18n.meridian()
920
+ ;
921
+
922
+ if (!options) {
923
+ options = {};
924
+ }
925
+
926
+ options = this.prepareOptions(options, DATE);
927
+
928
+ if (isNaN(date.getTime())) {
929
+ throw new Error('I18n.strftime() requires a valid date object, but received an invalid date.');
930
+ }
931
+
932
+ var weekDay = date.getDay()
933
+ , day = date.getDate()
934
+ , year = date.getFullYear()
935
+ , month = date.getMonth() + 1
936
+ , hour = date.getHours()
937
+ , hour12 = hour
938
+ , meridian = hour > 11 ? 1 : 0
939
+ , secs = date.getSeconds()
940
+ , mins = date.getMinutes()
941
+ , offset = date.getTimezoneOffset()
942
+ , absOffsetHours = Math.floor(Math.abs(offset / 60))
943
+ , absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60)
944
+ , timezoneoffset = (offset > 0 ? "-" : "+") +
945
+ (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) +
946
+ (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes)
947
+ ;
948
+
949
+ if (hour12 > 12) {
950
+ hour12 = hour12 - 12;
951
+ } else if (hour12 === 0) {
952
+ hour12 = 12;
953
+ }
954
+
955
+ format = format.replace("%a", options.abbr_day_names[weekDay]);
956
+ format = format.replace("%A", options.day_names[weekDay]);
957
+ format = format.replace("%b", options.abbr_month_names[month]);
958
+ format = format.replace("%B", options.month_names[month]);
959
+ format = format.replace("%d", padding(day));
960
+ format = format.replace("%e", day);
961
+ format = format.replace("%-d", day);
962
+ format = format.replace("%H", padding(hour));
963
+ format = format.replace("%-H", hour);
964
+ format = format.replace("%k", hour);
965
+ format = format.replace("%I", padding(hour12));
966
+ format = format.replace("%-I", hour12);
967
+ format = format.replace("%l", hour12);
968
+ format = format.replace("%m", padding(month));
969
+ format = format.replace("%-m", month);
970
+ format = format.replace("%M", padding(mins));
971
+ format = format.replace("%-M", mins);
972
+ format = format.replace("%p", meridianOptions[meridian]);
973
+ format = format.replace("%P", meridianOptions[meridian].toLowerCase());
974
+ format = format.replace("%S", padding(secs));
975
+ format = format.replace("%-S", secs);
976
+ format = format.replace("%w", weekDay);
977
+ format = format.replace("%y", padding(year));
978
+ format = format.replace("%-y", padding(year).replace(/^0+/, ""));
979
+ format = format.replace("%Y", year);
980
+ format = format.replace("%z", timezoneoffset);
981
+ format = format.replace("%Z", timezoneoffset);
982
+
983
+ return format;
984
+ };
985
+
986
+ // Convert the given dateString into a formatted date.
987
+ I18n.toTime = function(scope, dateString, options) {
988
+ var date = this.parseDate(dateString)
989
+ , format = this.lookup(scope, options)
990
+ ;
991
+
992
+ // A date input of `null` or `undefined` will be returned as-is
993
+ if (date == null) {
994
+ return date;
995
+ }
996
+
997
+ var date_string = date.toString()
998
+ if (date_string.match(/invalid/i)) {
999
+ return date_string;
1000
+ }
1001
+
1002
+ if (!format) {
1003
+ return date_string;
1004
+ }
1005
+
1006
+ return this.strftime(date, format, options);
1007
+ };
1008
+
1009
+ // Convert a number into a formatted percentage value.
1010
+ I18n.toPercentage = function(number, options) {
1011
+ options = this.prepareOptions(
1012
+ options
1013
+ , this.lookup("number.percentage.format", options)
1014
+ , this.lookup("number.format", options)
1015
+ , PERCENTAGE_FORMAT
1016
+ );
1017
+
1018
+ return this.toNumber(number, options);
1019
+ };
1020
+
1021
+ // Convert a number into a readable size representation.
1022
+ I18n.toHumanSize = function(number, options) {
1023
+ var kb = 1024
1024
+ , size = number
1025
+ , iterations = 0
1026
+ , unit
1027
+ , precision
1028
+ , fullScope
1029
+ ;
1030
+
1031
+ while (size >= kb && iterations < 4) {
1032
+ size = size / kb;
1033
+ iterations += 1;
1034
+ }
1035
+
1036
+ if (iterations === 0) {
1037
+ fullScope = this.getFullScope("number.human.storage_units.units.byte", options);
1038
+ unit = this.t(fullScope, {count: size});
1039
+ precision = 0;
1040
+ } else {
1041
+ fullScope = this.getFullScope("number.human.storage_units.units." + SIZE_UNITS[iterations], options);
1042
+ unit = this.t(fullScope);
1043
+ precision = (size - Math.floor(size) === 0) ? 0 : 1;
1044
+ }
1045
+
1046
+ options = this.prepareOptions(
1047
+ options
1048
+ , {unit: unit, precision: precision, format: "%n%u", delimiter: ""}
1049
+ );
1050
+
1051
+ return this.toNumber(size, options);
1052
+ };
1053
+
1054
+ I18n.getFullScope = function(scope, options) {
1055
+ options = options || {};
1056
+
1057
+ // Deal with the scope as an array.
1058
+ if (isArray(scope)) {
1059
+ scope = scope.join(options.separator || this.defaultSeparator);
1060
+ }
1061
+
1062
+ // Deal with the scope option provided through the second argument.
1063
+ //
1064
+ // I18n.t('hello', {scope: 'greetings'});
1065
+ //
1066
+ if (options.scope) {
1067
+ scope = [options.scope, scope].join(options.separator || this.defaultSeparator);
1068
+ }
1069
+
1070
+ return scope;
1071
+ };
1072
+ /**
1073
+ * Merge obj1 with obj2 (shallow merge), without modifying inputs
1074
+ * @param {Object} obj1
1075
+ * @param {Object} obj2
1076
+ * @returns {Object} Merged values of obj1 and obj2
1077
+ *
1078
+ * In order to support ES3, `Object.prototype.hasOwnProperty.call` is used
1079
+ * Idea is from:
1080
+ * https://stackoverflow.com/questions/8157700/object-has-no-hasownproperty-method-i-e-its-undefined-ie8
1081
+ */
1082
+ I18n.extend = function ( obj1, obj2 ) {
1083
+ if (typeof(obj1) === "undefined" && typeof(obj2) === "undefined") {
1084
+ return {};
1085
+ }
1086
+ return merge(obj1, obj2);
1087
+ };
1088
+
1089
+ // Set aliases, so we can save some typing.
1090
+ I18n.t = I18n.translate.bind(I18n);
1091
+ I18n.l = I18n.localize.bind(I18n);
1092
+ I18n.p = I18n.pluralize.bind(I18n);
1093
+
1094
+ return I18n;
1095
+ }));