refinerycms-i18n 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
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;