refinerycms-i18n 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/REFINERY_README +8 -0
  2. data/Rakefile +34 -0
  3. data/config/locales/ar.yml +121 -0
  4. data/config/locales/bg.yml +198 -0
  5. data/config/locales/bn-IN.yml +180 -0
  6. data/config/locales/bs.yml +114 -0
  7. data/config/locales/ca-ES.yml +155 -0
  8. data/config/locales/cz.rb +166 -0
  9. data/config/locales/da.yml +167 -0
  10. data/config/locales/de-AT.yml +140 -0
  11. data/config/locales/de.yml +141 -0
  12. data/config/locales/dsb.yml +182 -0
  13. data/config/locales/el.yml +191 -0
  14. data/config/locales/es-AR.yml +168 -0
  15. data/config/locales/es-CO.yml +146 -0
  16. data/config/locales/es-MX.yml +118 -0
  17. data/config/locales/es.yml +186 -0
  18. data/config/locales/et.yml +109 -0
  19. data/config/locales/fa.yml +119 -0
  20. data/config/locales/fi.yml +154 -0
  21. data/config/locales/fr-CH.yml +123 -0
  22. data/config/locales/fr.yml +140 -0
  23. data/config/locales/fun/en-AU.rb +105 -0
  24. data/config/locales/fun/gibberish.rb +109 -0
  25. data/config/locales/fur.yml +141 -0
  26. data/config/locales/gl-ES.yml +193 -0
  27. data/config/locales/he.yml +103 -0
  28. data/config/locales/hr.yml +116 -0
  29. data/config/locales/hsb.yml +190 -0
  30. data/config/locales/hu.yml +144 -0
  31. data/config/locales/id.yml +122 -0
  32. data/config/locales/is.yml +142 -0
  33. data/config/locales/it.yml +146 -0
  34. data/config/locales/ja.yml +139 -0
  35. data/config/locales/ko.yml +153 -0
  36. data/config/locales/lo.yml +201 -0
  37. data/config/locales/lt.yml +141 -0
  38. data/config/locales/lv.yml +132 -0
  39. data/config/locales/mk.yml +115 -0
  40. data/config/locales/nb.yml +103 -0
  41. data/config/locales/nl.yml +173 -0
  42. data/config/locales/nn.yml +96 -0
  43. data/config/locales/pl.yml +127 -0
  44. data/config/locales/pt-BR.yml +148 -0
  45. data/config/locales/pt-PT.yml +220 -0
  46. data/config/locales/rm.yml +134 -0
  47. data/config/locales/ro.yml +152 -0
  48. data/config/locales/ru.yml +210 -0
  49. data/config/locales/sk.yml +139 -0
  50. data/config/locales/sl.yml +190 -0
  51. data/config/locales/sr-Latn.yml +116 -0
  52. data/config/locales/sr.yml +116 -0
  53. data/config/locales/sv-SE.yml +191 -0
  54. data/config/locales/sw.yml +123 -0
  55. data/config/locales/th.rb +222 -0
  56. data/config/locales/tr.yml +139 -0
  57. data/config/locales/uk.yml +237 -0
  58. data/config/locales/vi.yml +198 -0
  59. data/config/locales/zh-CN.yml +131 -0
  60. data/config/locales/zh-TW.yml +132 -0
  61. data/i18n-js-readme.rdoc +249 -0
  62. data/lib/gemspec.rb +29 -0
  63. data/lib/refinery/i18n-filter.rb +38 -0
  64. data/lib/refinery/i18n-js.rb +132 -0
  65. data/lib/refinery/i18n-js.yml +4 -0
  66. data/lib/refinery/i18n.js +339 -0
  67. data/lib/refinery/i18n.rb +146 -0
  68. data/lib/refinery/translate.rb +13 -0
  69. data/lib/tasks/i18n-js_tasks.rake +16 -0
  70. data/lib/tasks/translate.rake +200 -0
  71. data/lib/translate/file.rb +35 -0
  72. data/lib/translate/keys.rb +123 -0
  73. data/lib/translate/log.rb +39 -0
  74. data/lib/translate/storage.rb +20 -0
  75. data/test/i18n-test.html +50 -0
  76. data/test/i18n-test.js +661 -0
  77. data/test/i18n_js_test.rb +168 -0
  78. data/test/jsunittest/jsunittest.js +1017 -0
  79. data/test/jsunittest/unittest.css +54 -0
  80. data/test/resources/custom_path.yml +4 -0
  81. data/test/resources/default.yml +4 -0
  82. data/test/resources/locales.yml +76 -0
  83. data/test/resources/multiple_files.yml +6 -0
  84. data/test/resources/no_scope.yml +3 -0
  85. data/test/resources/simple_scope.yml +4 -0
  86. data/test/test_helper.rb +24 -0
  87. data/translate-readme.md +61 -0
  88. metadata +154 -0
data/lib/gemspec.rb ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ version = '0.9.8'
3
+ raise "Could not get version so gemspec can not be built" if version.nil?
4
+ files = Dir.glob("**/*").flatten.reject do |file|
5
+ file =~ /\.gem(spec)?$/
6
+ end
7
+
8
+ gemspec = <<EOF
9
+ Gem::Specification.new do |s|
10
+ s.name = %q{refinerycms-i18n}
11
+ s.version = %q{#{version}}
12
+ s.description = %q{i18n logic extracted from RefineryCMS, for Refinery CMS.}
13
+ s.date = %q{#{Time.now.strftime('%Y-%m-%d')}}
14
+ s.summary = %q{i18n logic for Refinery CMS.}
15
+ s.email = %q{info@refinerycms.com}
16
+ s.homepage = %q{http://refinerycms.com}
17
+ s.authors = %w(Resolve\\ Digital)
18
+ s.require_paths = %w(lib)
19
+
20
+ s.files = [
21
+ '#{files.join("',\n '")}'
22
+ ]
23
+ #{"s.test_files = [
24
+ '#{Dir.glob("test/**/*.rb").join("',\n '")}'
25
+ ]" if File.directory?("test")}
26
+ end
27
+ EOF
28
+
29
+ File.open(File.expand_path("../../refinerycms-i18n.gemspec", __FILE__), 'w').puts(gemspec)
@@ -0,0 +1,38 @@
1
+ module RoutingFilter
2
+ class RefineryLocales < Filter
3
+
4
+ def around_recognize(path, env, &block)
5
+ if ::Refinery::I18n.enabled?
6
+ if path =~ %r{^/(#{::Refinery::I18n.locales.keys.join('|')})/?}
7
+ path.sub! %r(^/(([a-zA-Z\-_])*)(?=/|$)) do
8
+ ::I18n.locale = $1
9
+ ''
10
+ end
11
+ path.sub!(%r{^$}) { '/' }
12
+ else
13
+ ::I18n.locale = ::Refinery::I18n.default_frontend_locale
14
+ end
15
+ end
16
+
17
+ yield.tap do |params|
18
+ params[:locale] = ::I18n.locale if ::Refinery::I18n.enabled?
19
+ params
20
+ end
21
+ end
22
+
23
+ def around_generate(params, &block)
24
+ locale = params.delete(:locale) || ::I18n.locale
25
+
26
+ yield.tap do |result|
27
+ if ::Refinery::I18n.enabled? and
28
+ locale != ::Refinery::I18n.default_frontend_locale and
29
+ result !~ %r{^/(refinery|wymiframe)}
30
+ result.sub!(%r(^(http.?://[^/]*)?(.*))) { "#{$1}/#{locale}#{$2}" }
31
+ end
32
+
33
+ result
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,132 @@
1
+ module SimplesIdeias
2
+ module I18n
3
+ extend self
4
+
5
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
6
+ MERGER = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 }
7
+
8
+ # Set configuration file path
9
+ CONFIG_FILE = Rails.root.join("config/i18n-js.yml")
10
+
11
+ # Set i18n.js output path
12
+ JAVASCRIPT_FILE = Rails.root.join("public/javascripts/i18n.js")
13
+
14
+ # Export translations to JavaScript, considering settings
15
+ # from configuration file
16
+ def export!
17
+ if config?
18
+ for options in config[:translations]
19
+ options.reverse_merge!(:only => "*")
20
+
21
+ if options[:only] == "*"
22
+ save translations, options[:file]
23
+ else
24
+ result = scoped_translations(options[:only])
25
+ save result, options[:file] unless result.empty?
26
+ end
27
+ end
28
+ else
29
+ save translations, "public/javascripts/translations.js"
30
+ end
31
+ end
32
+
33
+ # Load configuration file for partial exporting and
34
+ # custom output directory
35
+ def config
36
+ HashWithIndifferentAccess.new YAML.load_file(CONFIG_FILE)
37
+ end
38
+
39
+ # Check if configuration file exist
40
+ def config?
41
+ File.file? CONFIG_FILE
42
+ end
43
+
44
+ # Copy configuration and JavaScript library files to
45
+ # <tt>SimplesIdeias::I18n::CONFIG_FILE</tt> and <tt>public/i18n.js</tt>.
46
+ def setup!
47
+ FileUtils.cp File.dirname(__FILE__) + "/i18n.js", JAVASCRIPT_FILE
48
+ FileUtils.cp(File.dirname(__FILE__) + "/i18n-js.yml", CONFIG_FILE) unless config?
49
+ end
50
+
51
+ # Retrieve an updated JavaScript library from Github.
52
+ def update!
53
+ require "open-uri"
54
+ contents = open("http://github.com/fnando/i18n-js/raw/master/lib/i18n.js").read
55
+ File.open(JAVASCRIPT_FILE, "w+") {|f| f << contents}
56
+ end
57
+
58
+ # Convert translations to JSON string and save file.
59
+ def save(translations, file)
60
+ file = Rails.root.join(file)
61
+ FileUtils.mkdir_p File.dirname(file)
62
+
63
+ File.open(file, "w+") do |f|
64
+ f << %(var I18n = I18n || {};\n)
65
+ f << %(I18n.translations = );
66
+ f << sorted_hash(translations).to_json
67
+ f << %(;)
68
+ end
69
+ end
70
+
71
+ def scoped_translations(scopes) # :nodoc:
72
+ result = {}
73
+
74
+ [scopes].flatten.each do |scope|
75
+ deep_merge! result, filter(translations, scope)
76
+ end
77
+
78
+ result
79
+ end
80
+
81
+ # Filter translations according to the specified scope.
82
+ def filter(translations, scopes)
83
+ scopes = scopes.split(".") if scopes.is_a?(String)
84
+ scopes = scopes.clone
85
+ scope = scopes.shift
86
+
87
+ if scope == "*"
88
+ results = {}
89
+ translations.each do |scope, translations|
90
+ tmp = scopes.empty? ? translations : filter(translations, scopes)
91
+ results[scope.to_sym] = tmp unless tmp.nil?
92
+ end
93
+ return results
94
+ elsif translations.has_key?(scope.to_sym)
95
+ return {scope.to_sym => scopes.empty? ? translations[scope.to_sym] : filter(translations[scope.to_sym], scopes)}
96
+ end
97
+ nil
98
+ end
99
+
100
+ # Initialize and return translations
101
+ def translations
102
+ ::I18n.backend.instance_eval do
103
+ init_translations unless initialized?
104
+ translations
105
+ end
106
+ end
107
+
108
+ def deep_merge(target, hash) # :nodoc:
109
+ target.merge(hash, &MERGER)
110
+ end
111
+
112
+ def deep_merge!(target, hash) # :nodoc:
113
+ target.merge!(hash, &MERGER)
114
+ end
115
+
116
+ # Taken from http://seb.box.re/2010/1/15/deep-hash-ordering-with-ruby-1-8/
117
+ def sorted_hash(object, deep = false) # :nodoc:
118
+ if object.is_a?(Hash)
119
+ res = returning(ActiveSupport::OrderedHash.new) do |map|
120
+ object.each {|k, v| map[k] = deep ? sorted_hash(v, deep) : v }
121
+ end
122
+ return res.class[res.sort {|a, b| a[0].to_s <=> b[0].to_s } ]
123
+ elsif deep && object.is_a?(Array)
124
+ array = Array.new
125
+ object.each_with_index {|v, i| array[i] = sorted_hash(v, deep) }
126
+ return array
127
+ else
128
+ return object
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,4 @@
1
+ # Find more details about this configuration file at http://github.com/fnando/i18n-js
2
+ translations:
3
+ - file: "public/javascripts/translations.js"
4
+ only: "*"
@@ -0,0 +1,339 @@
1
+ // Instantiate the object
2
+ var I18n = I18n || {};
3
+
4
+ // Set default locale to english
5
+ I18n.defaultLocale = "en";
6
+
7
+ // Set current locale to null
8
+ I18n.locale = null;
9
+
10
+ I18n.lookup = function(scope, options) {
11
+ var translations = this.prepareOptions(I18n.translations);
12
+ var messages = translations[I18n.currentLocale()];
13
+ options = this.prepareOptions(options);
14
+
15
+ if (!messages) {
16
+ return;
17
+ }
18
+
19
+ if (typeof(scope) == "object") {
20
+ scope = scope.join(".");
21
+ }
22
+
23
+ if (options.scope) {
24
+ scope = options.scope.toString() + "." + scope;
25
+ }
26
+
27
+ scope = scope.split(".");
28
+
29
+ while (scope.length > 0) {
30
+ var currentScope = scope.shift();
31
+ messages = messages[currentScope];
32
+
33
+ if (!messages) {
34
+ break;
35
+ }
36
+ }
37
+
38
+ if (!messages && options.defaultValue != null && options.defaultValue != undefined) {
39
+ messages = options.defaultValue;
40
+ }
41
+
42
+ return messages;
43
+ };
44
+
45
+ // Merge serveral hash options, checking if value is set before
46
+ // overwriting any value. The precedence is from left to right.
47
+ //
48
+ // I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
49
+ // #=> {name: "John Doe", role: "user"}
50
+ //
51
+ I18n.prepareOptions = function() {
52
+ var options = {};
53
+ var opts;
54
+ var count = arguments.length;
55
+
56
+ for (var i = 0; i < count; i++) {
57
+ opts = arguments[i];
58
+
59
+ if (!opts) {
60
+ continue;
61
+ }
62
+
63
+ for (var key in opts) {
64
+ if (options[key] == undefined || options[key] == null) {
65
+ options[key] = opts[key];
66
+ }
67
+ }
68
+ }
69
+
70
+ return options;
71
+ };
72
+
73
+ I18n.interpolate = function(message, options) {
74
+ options = this.prepareOptions(options);
75
+ var regex = /\{\{(.*?)\}\}/gm;
76
+
77
+ var matches = message.match(regex);
78
+
79
+ if (!matches) {
80
+ return message;
81
+ }
82
+
83
+ var placeholder, value, name;
84
+
85
+ for (var i = 0; placeholder = matches[i]; i++) {
86
+ name = placeholder.replace(/\{\{(.*?)\}\}/gm, "$1");
87
+
88
+ value = options[name];
89
+
90
+ if (options[name] == null || options[name] == undefined) {
91
+ value = "[missing " + placeholder + " value]";
92
+ }
93
+
94
+ regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
95
+
96
+ message = message.replace(regex, value);
97
+ }
98
+
99
+ return message;
100
+ };
101
+
102
+ I18n.translate = function(scope, options) {
103
+ options = this.prepareOptions(options);
104
+ var translation = this.lookup(scope, options);
105
+
106
+ try {
107
+ if (typeof(translation) == "object") {
108
+ if (typeof(options.count) == "number") {
109
+ return this.pluralize(options.count, scope, options);
110
+ } else {
111
+ return translation;
112
+ }
113
+ } else {
114
+ return this.interpolate(translation, options);
115
+ }
116
+ } catch(err) {
117
+ return this.missingTranslation(scope);
118
+ }
119
+ };
120
+
121
+ I18n.localize = function(scope, value) {
122
+ switch (scope) {
123
+ case "currency":
124
+ return this.toCurrency(value);
125
+ case "number":
126
+ scope = this.lookup("number.format");
127
+ return this.toNumber(value, scope);
128
+ case "percentage":
129
+ return this.toPercentage(value);
130
+ default:
131
+ if (scope.match(/^(date|time)/)) {
132
+ return this.toTime(scope, value);
133
+ } else {
134
+ return value.toString();
135
+ }
136
+ }
137
+ };
138
+
139
+ I18n.parseDate = function(d) {
140
+ var matches, date;
141
+
142
+ if (matches = d.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ |T](\d{2}):(\d{2}):(\d{2}))?(Z)?/)) {
143
+ // date/time strings: yyyy-mm-dd hh:mm:ss or yyyy-mm-dd or yyyy-mm-ddThh:mm:ssZ
144
+ for (var i = 1; i <= 6; i++) {
145
+ matches[i] = parseInt(matches[i], 10) || 0;
146
+ }
147
+
148
+ // month starts on 0
149
+ matches[2] -= 1;
150
+
151
+ if (matches[7]) {
152
+ date = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
153
+ } else {
154
+ date = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
155
+ }
156
+ } else if (typeof(d) == "number") {
157
+ // UNIX timestamp
158
+ date = new Date();
159
+ date.setTime(d);
160
+ } else {
161
+ // an arbitrary javascript string
162
+ date = new Date();
163
+ date.setTime(Date.parse(d));
164
+ }
165
+
166
+ return date;
167
+ };
168
+
169
+ I18n.toTime = function(scope, d) {
170
+ var date = this.parseDate(d);
171
+ var format = this.lookup(scope);
172
+
173
+ if (date.toString().match(/invalid/i)) {
174
+ return date.toString();
175
+ }
176
+
177
+ if (!format) {
178
+ return date.toString();
179
+ }
180
+
181
+ return this.strftime(date, format);
182
+ };
183
+
184
+ I18n.strftime = function(date, format) {
185
+ var options = this.lookup("date");
186
+
187
+ if (!options) {
188
+ return date.toString();
189
+ }
190
+
191
+ var weekDay = date.getDay();
192
+ var day = date.getDate();
193
+ var year = date.getFullYear();
194
+ var month = date.getMonth() + 1;
195
+ var hour = date.getHours();
196
+ var hour12 = hour;
197
+ var meridian = hour > 12? "PM" : "AM";
198
+ var secs = date.getSeconds();
199
+ var mins = date.getMinutes();
200
+ var offset = date.getTimezoneOffset();
201
+ var absOffsetHours = Math.floor(Math.abs(offset / 60));
202
+ var absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60);
203
+ var timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes);
204
+
205
+ if (hour12 > 12) {
206
+ hour12 = hour12 - 12;
207
+ }
208
+
209
+ var padding = function(n) {
210
+ var s = "0" + n.toString();
211
+ return s.substr(s.length - 2);
212
+ }
213
+
214
+ var f = format;
215
+ f = f.replace("%a", options["abbr_day_names"][weekDay]);
216
+ f = f.replace("%A", options["day_names"][weekDay]);
217
+ f = f.replace("%b", options["abbr_month_names"][month]);
218
+ f = f.replace("%B", options["month_names"][month]);
219
+ f = f.replace("%d", padding(day));
220
+ f = f.replace("%-d", day);
221
+ f = f.replace("%H", padding(hour));
222
+ f = f.replace("%-H", hour);
223
+ f = f.replace("%I", padding(hour12));
224
+ f = f.replace("%-I", hour12);
225
+ f = f.replace("%m", padding(month));
226
+ f = f.replace("%-m", month);
227
+ f = f.replace("%M", padding(mins));
228
+ f = f.replace("%-M", mins);
229
+ f = f.replace("%p", meridian);
230
+ f = f.replace("%S", padding(secs));
231
+ f = f.replace("%-S", secs);
232
+ f = f.replace("%w", weekDay);
233
+ f = f.replace("%y", padding(year));
234
+ f = f.replace("%-y", padding(year).replace(/^0+/, ""));
235
+ f = f.replace("%Y", year);
236
+ f = f.replace("%z", timezoneoffset);
237
+
238
+ return f;
239
+ };
240
+
241
+ I18n.toNumber = function(number, options) {
242
+ options = this.prepareOptions(
243
+ options,
244
+ this.lookup("number.format"),
245
+ {precision: 3, separator: ".", delimiter: ","}
246
+ );
247
+
248
+ var string = number.toFixed(options["precision"]).toString();
249
+ var parts = string.split(".");
250
+
251
+ number = parts[0];
252
+ var precision = parts[1];
253
+
254
+ var n = [];
255
+
256
+ while (number.length > 0) {
257
+ n.unshift(number.substr(Math.max(0, number.length - 3), 3));
258
+ number = number.substr(0, number.length -3);
259
+ }
260
+
261
+ var formattedNumber = n.join(options["delimiter"]);
262
+
263
+ if (options["precision"] > 0) {
264
+ formattedNumber += options["separator"] + parts[1];
265
+ }
266
+
267
+ return formattedNumber;
268
+ };
269
+
270
+ I18n.toCurrency = function(number, options) {
271
+ options = this.prepareOptions(
272
+ options,
273
+ this.lookup("number.currency.format"),
274
+ this.lookup("number.format"),
275
+ {unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
276
+ );
277
+
278
+ number = this.toNumber(number, options);
279
+ number = options["format"]
280
+ .replace("%u", options["unit"])
281
+ .replace("%n", number);
282
+
283
+ return number;
284
+ };
285
+
286
+ I18n.toPercentage = function(number, options) {
287
+ options = this.prepareOptions(
288
+ options,
289
+ this.lookup("number.percentage.format"),
290
+ this.lookup("number.format"),
291
+ {precision: 3, separator: ".", delimiter: ""}
292
+ );
293
+
294
+ number = this.toNumber(number, options);
295
+ return number + "%";
296
+ };
297
+
298
+ I18n.pluralize = function(count, scope, options) {
299
+ var translation = this.lookup(scope, options);
300
+
301
+ var message;
302
+ options = this.prepareOptions(options);
303
+ options["count"] = count.toString();
304
+
305
+ switch(Math.abs(count)) {
306
+ case 0:
307
+ message = translation["zero"] || translation["none"] || translation["other"] || this.missingTranslation(scope, "zero");
308
+ break;
309
+ case 1:
310
+ message = translation["one"] || this.missingTranslation(scope, "one");;
311
+ break;
312
+ default:
313
+ message = translation["other"] || this.missingTranslation(scope, "other");;
314
+ }
315
+
316
+ return this.interpolate(message, options);
317
+ };
318
+
319
+ I18n.missingTranslation = function() {
320
+ var message = '[missing "' + this.currentLocale();
321
+ var count = arguments.length;
322
+
323
+ for (var i = 0; i < count; i++) {
324
+ message += "." + arguments[i];
325
+ }
326
+
327
+ message += '" translation]';
328
+
329
+ return message;
330
+ };
331
+
332
+ I18n.currentLocale = function() {
333
+ return (I18n.locale || I18n.defaultLocale);
334
+ };
335
+
336
+ // shortcuts
337
+ I18n.t = I18n.translate;
338
+ I18n.l = I18n.localize;
339
+ I18n.p = I18n.pluralize;