gettext_i18n_rails_js 0.0.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ # -*- coding: UTF-8 -*-
2
+ #
3
+ # Copyright (c) 2012-2015 Dropmysite.com <https://dropmyemail.com>
4
+ # Copyright (c) 2015 Webhippie <http://www.webhippie.de>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+
26
+ require "gettext/tools/xgettext"
27
+ require "gettext_i18n_rails/gettext_hooks"
28
+ require_relative "base"
29
+
30
+ module GettextI18nRailsJs
31
+ module Parser
32
+ module Javascript
33
+ include Base
34
+ extend self
35
+
36
+ def target?(file)
37
+ [
38
+ ".js",
39
+ ".coffee"
40
+ ].include? ::File.extname(file)
41
+ end
42
+
43
+ protected
44
+
45
+ def collect_for(value)
46
+ ::File.new(
47
+ value
48
+ ).each_line.each_with_index.collect do |line, idx|
49
+ line.scan(invoke_regex).collect do |function, arguments|
50
+ yield(function, arguments, idx + 1)
51
+ end
52
+ end.inject(:+).compact
53
+ end
54
+
55
+ def invoke_regex
56
+ #
57
+ # * Matches the function call grouping the method used (__, n__, N__)
58
+ # * A parenthesis to start the arguments to the function.
59
+ # * There may be many arguments to the same function call
60
+ # * Then the last, or only argument to the function call.
61
+ # * Function call closing parenthesis
62
+ #
63
+
64
+ /
65
+ (\b[snN]?#{gettext_function})
66
+ \(
67
+ (
68
+ (#{arg_regex},)*
69
+ #{arg_regex}
70
+ )?
71
+ \)
72
+ /x
73
+ end
74
+
75
+ def arg_regex
76
+ #
77
+ # * Some whitespace
78
+ # * A token inside the argument list, like a single quoted string
79
+ # * Double quote string, both support escapes
80
+ # * A number, variable name, or called function lik: 33, foo, Foo.bar()
81
+ # * More whitespace
82
+ #
83
+
84
+ /
85
+ \s*
86
+ (
87
+ '(?:[^'\\]|\\.)*?'|
88
+ "(?:[^"\\]|\\.)*?"|
89
+ [a-zA-Z0-9_\.()]*?
90
+ )
91
+ \s*
92
+ /x
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ GettextI18nRails::GettextHooks.add_parser(
99
+ GettextI18nRailsJs::Parser::Javascript
100
+ )
@@ -1,3 +1,40 @@
1
+ # -*- coding: UTF-8 -*-
2
+ #
3
+ # Copyright (c) 2012-2015 Dropmysite.com <https://dropmyemail.com>
4
+ # Copyright (c) 2015 Webhippie <http://www.webhippie.de>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+
1
26
  module GettextI18nRailsJs
2
- VERSION = "0.0.9"
27
+ class Version
28
+ MAJOR = 1
29
+ MINOR = 0
30
+ PATCH = 0
31
+
32
+ PRE = nil
33
+
34
+ class << self
35
+ def to_s
36
+ [MAJOR, MINOR, PATCH, PRE].compact.join(".")
37
+ end
38
+ end
39
+ end
3
40
  end
@@ -1,7 +1,153 @@
1
- if RUBY_PLATFORM != 'java'
2
- dir = File.dirname(__FILE__)
3
- require File.join(dir, '../gettext_i18n_rails_js/js_and_coffee_parser')
4
- require File.join(dir, '../gettext_i18n_rails_js/handlebars_parser')
5
- require 'gettext_i18n_rails/tasks'
6
- require File.join(dir, '../gettext_i18n_rails_js/tasks')
1
+ # -*- coding: UTF-8 -*-
2
+ #
3
+ # Copyright (c) 2012-2015 Dropmysite.com <https://dropmyemail.com>
4
+ # Copyright (c) 2015 Webhippie <http://www.webhippie.de>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+
26
+ require "gettext_i18n_rails/tasks"
27
+
28
+ namespace :gettext do
29
+ desc "Convert PO files to JS files"
30
+ task po_to_json: :environment do
31
+ GettextI18nRailsJs::Parser::Javascript
32
+ .gettext_function = config[:javascript_function]
33
+
34
+ GettextI18nRailsJs::Parser::Handlebars
35
+ .gettext_function = config[:handlebars_function]
36
+
37
+ if files_list.empty?
38
+ puts "Couldn't find PO files in #{locale_path}, run 'rake gettext:find'"
39
+ else
40
+ files_list.each do |input|
41
+ # Language is used for filenames, while language code is used as the
42
+ # in-app language code. So for instance, simplified chinese will live
43
+ # in app/assets/locale/zh_CN/app.js but inside the file the language
44
+ # will be referred to as locales['zh-CN']. This is to adapt to the
45
+ # existing gettext_rails convention.
46
+
47
+ language = input.dirname.basename.to_s
48
+ language_code = language.gsub("_", "-")
49
+
50
+ destination = output_path.join(language)
51
+ destination.mkpath
52
+
53
+ json = PoToJson.new(
54
+ input.to_s
55
+ ).generate_for_jed(
56
+ language_code,
57
+ config[:jed_options].symbolize_keys
58
+ )
59
+
60
+ destination.join("app.js").open("w") do |f|
61
+ f.rewind
62
+ f.write(json)
63
+ end
64
+
65
+ puts "Created app.js in #{destination}"
66
+ end
67
+
68
+ puts
69
+ puts "All files created, make sure they are being added to your assets."
70
+ puts "If they are not, you can add them with this line (configurable):"
71
+ puts
72
+ puts "//= require_tree ./locale"
73
+ puts "//= require gettext/all"
74
+ puts
75
+ end
76
+ end
77
+
78
+ def files_list
79
+ Pathname.glob(
80
+ ::File.join(
81
+ locale_path,
82
+ "**",
83
+ "*.po"
84
+ )
85
+ )
86
+ end
87
+
88
+ def output_path
89
+ Rails.root.join(
90
+ config[:output_path]
91
+ )
92
+ end
93
+
94
+ def config
95
+ @config ||= begin
96
+ file = Rails.root.join(
97
+ "config",
98
+ "gettext_i18n_rails_js.yml"
99
+ )
100
+
101
+ defaults = {
102
+ output_path: File.join(
103
+ "app",
104
+ "assets",
105
+ "javascripts",
106
+ "locale"
107
+ ),
108
+ handlebars_function: "__",
109
+ javascript_function: "__",
110
+ jed_options: {
111
+ pretty: false
112
+ }
113
+ }
114
+
115
+ if file.exist?
116
+ yaml = YAML.load_file(file) || {}
117
+
118
+ defaults.deep_merge(
119
+ yaml
120
+ ).with_indifferent_access
121
+ else
122
+ defaults.with_indifferent_access
123
+ end
124
+ end
125
+ end
126
+
127
+ # Required for gettext to filter the files
128
+ def files_to_translate
129
+ folders = [
130
+ "app",
131
+ "lib",
132
+ "config",
133
+ locale_path
134
+ ].join(",")
135
+
136
+ exts = [
137
+ "rb",
138
+ "erb",
139
+ "haml",
140
+ "slim",
141
+ "rhtml",
142
+ "js",
143
+ "coffee",
144
+ "handlebars",
145
+ "hbs",
146
+ "mustache"
147
+ ].join(",")
148
+
149
+ Dir.glob(
150
+ "{#{folders}}/**/*.{#{exts}}"
151
+ )
152
+ end
7
153
  end
@@ -0,0 +1,15 @@
1
+ pepito = ->
2
+ string = "this" + __('json') + 'should be translated'
3
+ alert(n__('item', 'items', 3))
4
+
5
+ example_template( __('Hello {yourname}'), { yourname: 'bob' })
6
+
7
+ object =
8
+ one: N__('new-trans')
9
+ two: s__('namespaced', 'trans')
10
+
11
+ "#{ __("Hello\nBuddy") }"
12
+
13
+ """
14
+ #{ __("Multi-line") }
15
+ """
@@ -0,0 +1,11 @@
1
+ <div class="content-main">
2
+ <div class="content-header">
3
+ <label>{{__ "Locale"}}</label>
4
+ <h1>{{__ "Profile"}}</h1>
5
+ <div class="btn-toolbar pull-right">
6
+ <div class="btn-group">
7
+ <a href="#" {{action "updateUser" target="controller"}} class="btn btn-success">{{__ "Update" "Updates" 2}}</a>
8
+ </div>
9
+ </div>
10
+ </div>
11
+ </div>
@@ -0,0 +1,12 @@
1
+ function pepito() {
2
+ var string = "this" + __('json') + 'should be translated';
3
+ alert(n__('item', 'items', 3));
4
+ }
5
+
6
+ example_template(__('Hello {yourname}'), { yourname: 'bob' });
7
+
8
+ object = {
9
+ one: N__('new-trans'),
10
+ two: s__('namespaced', 'trans')
11
+ three: __("Hello\nBuddy")
12
+ }
@@ -0,0 +1,301 @@
1
+ # -*- coding: UTF-8 -*-
2
+ #
3
+ # Copyright (c) 2012-2015 Dropmysite.com <https://dropmyemail.com>
4
+ # Copyright (c) 2015 Webhippie <http://www.webhippie.de>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+
26
+ require "spec_helper"
27
+
28
+ describe GettextI18nRailsJs::Parser::Handlebars do
29
+ let(:parser) { GettextI18nRailsJs::Parser::Handlebars }
30
+
31
+ describe "#target?" do
32
+ it "targets .handlebars" do
33
+ expect(parser.target?("foo/bar/xxx.handlebars")).to be_truthy
34
+ end
35
+
36
+ it "targets .handlebars.erb" do
37
+ expect(parser.target?("foo/bar/xxx.handlebars.erb")).to be_truthy
38
+ end
39
+ it "targets .hbs" do
40
+ expect(parser.target?("foo/bar/xxx.hbs")).to be_truthy
41
+ end
42
+
43
+ it "targets .hbs.erb" do
44
+ expect(parser.target?("foo/bar/xxx.hbs.erb")).to be_truthy
45
+ end
46
+
47
+ it "targets .mustache" do
48
+ expect(parser.target?("foo/bar/xxx.mustache")).to be_truthy
49
+ end
50
+
51
+ it "targets .mustache.erb" do
52
+ expect(parser.target?("foo/bar/xxx.mustache.erb")).to be_truthy
53
+ end
54
+
55
+ it "does not target cows" do
56
+ expect(parser.target?("foo/bar/xxx.cows")).to be_falsey
57
+ end
58
+ end
59
+
60
+ describe "#parse" do
61
+ it "finds plural messages" do
62
+ content = <<-EOF
63
+ <div>{{n__ "xxxx" "yyyy\" "zzzz" some_count}}</div>
64
+ EOF
65
+
66
+ with_file content do |path|
67
+ expect(parser.parse(path, [])).to(
68
+ eq(
69
+ [
70
+ ["xxxx\000yyyy\000zzzz", "#{path}:1"]
71
+ ]
72
+ )
73
+ )
74
+ end
75
+ end
76
+
77
+ it "finds namespaced messages" do
78
+ content = <<-EOF
79
+ <div>{{__ "xxxx", "yyyy"}}</div>
80
+ EOF
81
+
82
+ with_file content do |path|
83
+ expect(parser.parse(path, [])).to(
84
+ eq(
85
+ [
86
+ ["xxxx\004yyyy", "#{path}:1"]
87
+ ]
88
+ )
89
+ )
90
+ end
91
+ end
92
+
93
+ it "finds simple messages" do
94
+ content = <<-EOF
95
+ <div>{{__ "blah"}}</div>
96
+ EOF
97
+
98
+ with_file content do |path|
99
+ expect(parser.parse(path, [])).to(
100
+ eq(
101
+ [
102
+ ["blah", "#{path}:1"]
103
+ ]
104
+ )
105
+ )
106
+ end
107
+ end
108
+
109
+ # it "finds messages with newlines/tabs" do
110
+ # content = <<-EOF
111
+ # bla = __("xxxx\n\tfoo")
112
+ # EOF
113
+
114
+ # with_file content do |path|
115
+ # expect(parser.parse(path, [])).to(
116
+ # eq(
117
+ # [
118
+ # ["xxxx\n\tfoo", "#{path}:1"]
119
+ # ]
120
+ # )
121
+ # )
122
+ # end
123
+ # end
124
+
125
+ # it "finds messages with newlines/tabs (single quotes)" do
126
+ # content = <<-EOF
127
+ # bla = __('xxxx\n\tfoo')
128
+ # EOF
129
+
130
+ # with_file content do |path|
131
+ # parser.parse(path, []).should == [
132
+ # ["xxxx\n\tfoo", "#{path}:1"]
133
+ # ]
134
+ # end
135
+ # end
136
+
137
+ # it "finds interpolated multi-line messages" do
138
+ # content = <<-EOF
139
+ # """ Parser should grab
140
+ # #{ __("This") } __("known bug")
141
+ # """
142
+ # EOF
143
+
144
+ # with_file content do |path|
145
+ # expect(parser.parse(path, [])).to(
146
+ # eq(
147
+ # [
148
+ # ["This", "#{path}:3"],
149
+ # ["known bug", "#{path}:3"]
150
+ # ]
151
+ # )
152
+ # )
153
+ # end
154
+ # end
155
+
156
+ # it "finds strings that use some templating" do
157
+ # content = <<-EOF
158
+ # __("hello {yourname}")
159
+ # EOF
160
+
161
+ # with_file content do |path|
162
+ # expect(parser.parse(path, [])).to(
163
+ # eq(
164
+ # [
165
+ # ["hello {yourname}", "#{path}:1"]
166
+ # ]
167
+ # )
168
+ # )
169
+ # end
170
+ # end
171
+
172
+ # it "finds strings that use escaped strings" do
173
+ # content = <<-EOF
174
+ # __("hello \"dude\"") + __('how is it \'going\' ')
175
+ # EOF
176
+
177
+ # with_file content do |path|
178
+ # expect(parser.parse(path, [])).to(
179
+ # eq(
180
+ # [
181
+ # ["hello \"dude\"", "#{path}:1"],
182
+ # ["how is it 'going' ", "#{path}:1"]
183
+ # ]
184
+ # )
185
+ # )
186
+ # end
187
+ # end
188
+
189
+ # it "does not capture a false positive" do
190
+ # content = <<-EOF
191
+ # bla = this_should_not_be_registered__("xxxx", "yyyy")
192
+ # EOF
193
+
194
+ # with_file content do |path|
195
+ # expect(parser.parse(path, [])).to(
196
+ # eq(
197
+ # []
198
+ # )
199
+ # )
200
+ # end
201
+ # end
202
+
203
+ it "does not find nonstring messages" do
204
+ content = <<-EOF
205
+ <div>{{__ bar}}</div>
206
+ EOF
207
+
208
+ with_file content do |path|
209
+ expect(parser.parse(path, [])).to(
210
+ eq(
211
+ []
212
+ )
213
+ )
214
+ end
215
+ end
216
+
217
+ it "does not parse internal parentheses" do
218
+ content = <<-EOF
219
+ <div>
220
+ {{__ "text (which is great) and parentheses()"}}
221
+ {{__ "foobar"}}
222
+ </div>
223
+ EOF
224
+
225
+ with_file content do |path|
226
+ expect(parser.parse(path, [])).to(
227
+ eq(
228
+ [
229
+ ["text (which is great) and parentheses()", "#{path}:1"],
230
+ ["foobar", "#{path}:1"]
231
+ ]
232
+ )
233
+ )
234
+ end
235
+ end
236
+
237
+ # it "does not parse internal functions" do
238
+ # content = <<-EOF
239
+ # bla = n__("items (single)", "i (more)", item.count()) + __('foobar')
240
+ # EOF
241
+
242
+ # with_file content do |path|
243
+ # parser.parse(path, []).should == [
244
+ # ["items (single)\000i (more)", "#{path}:1"],
245
+ # ['foobar', "#{path}:1"]
246
+ # ]
247
+ # end
248
+ # end
249
+ end
250
+
251
+ describe "parses handlebars files" do
252
+ let(:example) do
253
+ File.expand_path(
254
+ "../../../fixtures/example.handlebars",
255
+ __FILE__
256
+ )
257
+ end
258
+
259
+ let(:parsed_example) do
260
+ parser.parse(example, [])
261
+ end
262
+
263
+ it "parses all translations" do
264
+ expect(parsed_example.collect(&:first)).to(
265
+ eq(["Locale", "Profile", "Update\004Updates"])
266
+ )
267
+ end
268
+
269
+ it "parses the file paths" do
270
+ parsed_example.collect(&:last).each do |path|
271
+ expect(path).to end_with("fixtures/example.handlebars:1")
272
+ end
273
+ end
274
+
275
+ it "accepts changing the translate method" do
276
+ parser.gettext_function = "gettext"
277
+
278
+ content = <<-EOF
279
+ <div>
280
+ {{gettext \"Hello {yourname}\"}}
281
+ <span>
282
+ {{ngettext \"item\", \"items\", 44}}
283
+ </span>
284
+ </div>
285
+ EOF
286
+
287
+ with_file content do |path|
288
+ expect(parser.parse(path, [])).to(
289
+ eq(
290
+ [
291
+ ["Hello {yourname}", "#{path}:1"],
292
+ ["item\000items", "#{path}:1"]
293
+ ]
294
+ )
295
+ )
296
+ end
297
+
298
+ parser.gettext_function = "__"
299
+ end
300
+ end
301
+ end