i18nliner 0.0.1 → 0.0.2
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.
- data/README.md +151 -104
- data/lib/i18nliner/base.rb +47 -0
- data/lib/i18nliner/call_helpers.rb +39 -13
- data/lib/i18nliner/commands/check.rb +20 -10
- data/lib/i18nliner/commands/dump.rb +17 -0
- data/lib/i18nliner/commands/generic_command.rb +10 -1
- data/lib/i18nliner/errors.rb +11 -1
- data/lib/i18nliner/erubis.rb +12 -0
- data/lib/i18nliner/extensions/controller.rb +25 -0
- data/lib/i18nliner/extensions/core.rb +61 -0
- data/lib/i18nliner/extensions/inferpolation.rb +28 -0
- data/lib/i18nliner/extensions/model.rb +29 -0
- data/lib/i18nliner/extensions/view.rb +28 -0
- data/lib/i18nliner/extractors/ruby_extractor.rb +15 -33
- data/lib/i18nliner/extractors/sexp_helper.rb +38 -0
- data/lib/i18nliner/extractors/translate_call.rb +9 -11
- data/lib/i18nliner/extractors/translation_hash.rb +6 -6
- data/lib/i18nliner/pre_processors/erb_pre_processor.rb +334 -0
- data/lib/i18nliner/processors/abstract_processor.rb +26 -5
- data/lib/i18nliner/processors/erb_processor.rb +19 -3
- data/lib/i18nliner/processors/ruby_processor.rb +16 -5
- data/lib/i18nliner/railtie.rb +27 -0
- data/lib/i18nliner/scope.rb +4 -0
- data/lib/i18nliner.rb +5 -29
- data/lib/tasks/i18nliner.rake +10 -4
- data/spec/commands/check_spec.rb +22 -0
- data/spec/commands/dump_spec.rb +27 -0
- data/spec/extensions/core_spec.rb +71 -0
- data/spec/extensions/inferpolation_spec.rb +41 -0
- data/spec/extensions/view_spec.rb +42 -0
- data/spec/extractors/ruby_extractor_spec.rb +4 -5
- data/spec/extractors/translate_call_spec.rb +29 -2
- data/spec/fixtures/app/models/invalid.rb +5 -0
- data/spec/fixtures/app/models/valid.rb +5 -0
- data/spec/pre_processors/erb_pre_processor_spec.rb +194 -0
- data/spec/processors/erb_processor_spec.rb +28 -0
- metadata +88 -5
@@ -0,0 +1,334 @@
|
|
1
|
+
require 'i18nliner/errors'
|
2
|
+
require 'i18nliner/call_helpers'
|
3
|
+
require 'i18nliner/extractors/sexp_helper'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'ruby_parser'
|
6
|
+
require 'ruby2ruby'
|
7
|
+
|
8
|
+
module I18nliner
|
9
|
+
module PreProcessors
|
10
|
+
class ErbPreProcessor
|
11
|
+
|
12
|
+
class Context
|
13
|
+
attr_reader :buffer, :parent
|
14
|
+
|
15
|
+
def initialize(parent = nil)
|
16
|
+
@parent = parent
|
17
|
+
@buffer = ''
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(string)
|
21
|
+
if string =~ ERB_T_BLOCK_EXPRESSION
|
22
|
+
TBlock.new(self, $&)
|
23
|
+
else
|
24
|
+
@buffer << string
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def result
|
30
|
+
@buffer
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Helper
|
35
|
+
include Extractors::SexpHelper
|
36
|
+
|
37
|
+
DEFINITIONS = [
|
38
|
+
{:method => :link_to, :pattern => /link_to/, :arg => 0}
|
39
|
+
]
|
40
|
+
RUBY2RUBY = Ruby2Ruby.new
|
41
|
+
PARSER = RubyParser.new
|
42
|
+
|
43
|
+
def self.match_for(string)
|
44
|
+
DEFINITIONS.each do |info|
|
45
|
+
return Helper.new(info, string) if string =~ info[:pattern]
|
46
|
+
end
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :placeholder, :wrapper
|
51
|
+
attr_accessor :content
|
52
|
+
|
53
|
+
def initialize(info, source)
|
54
|
+
@arg = info[:arg]
|
55
|
+
@method = info[:method]
|
56
|
+
@source = source
|
57
|
+
end
|
58
|
+
|
59
|
+
SEXP_ARG_OFFSET = 3
|
60
|
+
def wrappable?
|
61
|
+
return @wrappable if !@wrappable.nil?
|
62
|
+
begin
|
63
|
+
sexps = PARSER.parse(@source)
|
64
|
+
@wrappable = sexps.sexp_type == :call &&
|
65
|
+
sexps[1].nil? &&
|
66
|
+
sexps[2] == @method &&
|
67
|
+
sexps[@arg + SEXP_ARG_OFFSET]
|
68
|
+
extract_content!(sexps) if @wrappable
|
69
|
+
@wrappable
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def extract_content!(sexps)
|
74
|
+
sexp = sexps[@arg + SEXP_ARG_OFFSET]
|
75
|
+
if stringish?(sexp)
|
76
|
+
@content = string_from(sexp)
|
77
|
+
else
|
78
|
+
@placeholder = RUBY2RUBY.process(sexp)
|
79
|
+
end
|
80
|
+
sexps[@arg + SEXP_ARG_OFFSET] = Sexp.new(:str, "\\1")
|
81
|
+
@wrapper = RUBY2RUBY.process(sexps)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class TBlock < Context
|
86
|
+
include CallHelpers
|
87
|
+
|
88
|
+
def initialize(parent, content)
|
89
|
+
super(parent)
|
90
|
+
@lines = content.count("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
def <<(string)
|
94
|
+
case string
|
95
|
+
when ERB_BLOCK_EXPRESSION
|
96
|
+
if string =~ ERB_T_BLOCK_EXPRESSION
|
97
|
+
TBlock.new(self, $&)
|
98
|
+
else
|
99
|
+
raise TBlockNestingError.new("can't nest block expressions inside a t block")
|
100
|
+
end
|
101
|
+
when ERB_STATEMENT
|
102
|
+
if string =~ ERB_END_STATEMENT
|
103
|
+
@parent << result
|
104
|
+
else
|
105
|
+
raise TBlockNestingError.new("can't nest statements inside a t block")
|
106
|
+
end
|
107
|
+
else
|
108
|
+
# expressions and the like are handled a bit later
|
109
|
+
# TODO: perhaps a tad more efficient to capture/transform them
|
110
|
+
# here?
|
111
|
+
@buffer << string
|
112
|
+
self
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def result
|
117
|
+
@lines += @buffer.count("\n")
|
118
|
+
key, default, options, wrappers = normalize_call
|
119
|
+
result = "<%= t :#{key}, #{default}"
|
120
|
+
result << ", " << options if options
|
121
|
+
result << ", " << wrappers if wrappers
|
122
|
+
result << (@lines > 0 ? "\n" * @lines : " ")
|
123
|
+
result << "%>"
|
124
|
+
end
|
125
|
+
|
126
|
+
# get a unique and reasonable looking key for a given erb
|
127
|
+
# expression
|
128
|
+
def infer_interpolation_key(string, others)
|
129
|
+
key = string.downcase
|
130
|
+
key.sub!(/\.html_safe\z/, '')
|
131
|
+
key.gsub!(/[^a-z0-9]/, ' ')
|
132
|
+
key.strip!
|
133
|
+
key.gsub!(/ +/, '_')
|
134
|
+
key.slice!(20)
|
135
|
+
i = 0
|
136
|
+
base_key = key
|
137
|
+
while others.key?(key) && others[key] != string
|
138
|
+
key = "#{base_key}_#{i}"
|
139
|
+
i += 1
|
140
|
+
end
|
141
|
+
key
|
142
|
+
end
|
143
|
+
|
144
|
+
def extract_wrappers!(source, wrappers, placeholder_map)
|
145
|
+
source = extract_html_wrappers!(source, wrappers, placeholder_map)
|
146
|
+
source = extract_helper_wrappers!(source, wrappers, placeholder_map)
|
147
|
+
source
|
148
|
+
end
|
149
|
+
|
150
|
+
def find_or_add_wrapper(wrapper, wrappers)
|
151
|
+
unless pos = wrappers.index(wrapper)
|
152
|
+
pos = wrappers.size
|
153
|
+
wrappers << wrapper
|
154
|
+
end
|
155
|
+
pos
|
156
|
+
end
|
157
|
+
|
158
|
+
# incidentally this converts entities to their corresponding values
|
159
|
+
def extract_html_wrappers!(source, wrappers, placeholder_map)
|
160
|
+
default = ''
|
161
|
+
nodes = Nokogiri::HTML.fragment(source).children
|
162
|
+
nodes.each do |node|
|
163
|
+
if node.is_a?(Nokogiri::XML::Text)
|
164
|
+
default << node.content
|
165
|
+
elsif text = extract_text(node)
|
166
|
+
wrapper = node.to_s.sub(text, "\\\\1")
|
167
|
+
wrapper = prepare_wrapper(wrapper, placeholder_map)
|
168
|
+
pos = find_or_add_wrapper(wrapper, wrappers)
|
169
|
+
default << wrap(text, pos + 1)
|
170
|
+
else # no wrapped text (e.g. <input>)
|
171
|
+
key = "__I18NLINER_#{placeholder_map.size}__"
|
172
|
+
placeholder_map[key] = node.to_s.inspect << ".html_safe"
|
173
|
+
default << key
|
174
|
+
end
|
175
|
+
end
|
176
|
+
default
|
177
|
+
end
|
178
|
+
|
179
|
+
def extract_helper_wrappers!(source, wrappers, placeholder_map)
|
180
|
+
source.gsub(TEMP_PLACEHOLDER) do |string|
|
181
|
+
if (helper = Helper.match_for(placeholder_map[string])) && helper.wrappable?
|
182
|
+
placeholder_map.delete(string)
|
183
|
+
if helper.placeholder # e.g. link_to(name) -> *%{name}*
|
184
|
+
helper.content = "__I18NLINER_#{placeholder_map.size}__"
|
185
|
+
placeholder_map[helper.content] = helper.placeholder
|
186
|
+
end
|
187
|
+
pos = find_or_add_wrapper(helper.wrapper, wrappers)
|
188
|
+
wrap(helper.content, pos + 1)
|
189
|
+
else
|
190
|
+
string
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def prepare_wrapper(content, placeholder_map)
|
196
|
+
content = content.inspect
|
197
|
+
content.gsub!(TEMP_PLACEHOLDER) do |key|
|
198
|
+
"\#{#{placeholder_map[key]}}"
|
199
|
+
end
|
200
|
+
content
|
201
|
+
end
|
202
|
+
|
203
|
+
def extract_temp_placeholders!
|
204
|
+
extract_placeholders!(@buffer, ERB_EXPRESSION, false) do |str, map|
|
205
|
+
["__I18NLINER_#{map.size}__", str]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def extract_placeholders!(buffer = @buffer, pattern = ERB_EXPRESSION, wrap_placeholder = true)
|
210
|
+
map = {}
|
211
|
+
buffer.gsub!(pattern) do |str|
|
212
|
+
key, str = yield($~[:content], map)
|
213
|
+
map[key] = str
|
214
|
+
wrap_placeholder ? "%{#{key}}" : key
|
215
|
+
end
|
216
|
+
map
|
217
|
+
end
|
218
|
+
|
219
|
+
TEMP_PLACEHOLDER = /(?<content>__I18NLINER_\d+__)/
|
220
|
+
def normalize_call
|
221
|
+
wrappers = []
|
222
|
+
|
223
|
+
temp_map = extract_temp_placeholders!
|
224
|
+
default = extract_wrappers!(@buffer, wrappers, temp_map)
|
225
|
+
options = extract_placeholders!(default, TEMP_PLACEHOLDER) do |str, map|
|
226
|
+
[infer_interpolation_key(temp_map[str], map), temp_map[str]]
|
227
|
+
end
|
228
|
+
|
229
|
+
default.strip!
|
230
|
+
default.gsub!(/\s+/, ' ')
|
231
|
+
|
232
|
+
key = infer_key(default)
|
233
|
+
default = default.inspect
|
234
|
+
options = options_to_ruby(options)
|
235
|
+
wrappers = wrappers_to_ruby(wrappers)
|
236
|
+
[key, default, options, wrappers]
|
237
|
+
end
|
238
|
+
|
239
|
+
def options_to_ruby(options)
|
240
|
+
return if options.size == 0
|
241
|
+
options.map do |key, value|
|
242
|
+
":" << key << " => (" << value << ")"
|
243
|
+
end.join(", ")
|
244
|
+
end
|
245
|
+
|
246
|
+
def wrappers_to_ruby(wrappers)
|
247
|
+
return if wrappers.size == 0
|
248
|
+
":wrappers => [" << wrappers.join(", ") << "]"
|
249
|
+
end
|
250
|
+
|
251
|
+
def extract_text(root_node)
|
252
|
+
text = nil
|
253
|
+
nodes = root_node.children.to_a
|
254
|
+
while node = nodes.shift
|
255
|
+
if node.is_a?(Nokogiri::XML::Text) && !node.content.strip.empty?
|
256
|
+
raise UnwrappableContentError.new "multiple text nodes in html markup" if text
|
257
|
+
text = node.content
|
258
|
+
else
|
259
|
+
nodes.concat node.children
|
260
|
+
end
|
261
|
+
end
|
262
|
+
text
|
263
|
+
end
|
264
|
+
|
265
|
+
def wrap(text, index)
|
266
|
+
delimiter = "*" * index
|
267
|
+
"" << delimiter << text << delimiter
|
268
|
+
end
|
269
|
+
|
270
|
+
def infer_wrappers(source)
|
271
|
+
wrappers = []
|
272
|
+
[source, wrappers]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# need to evaluate all expressions and statements, so we can
|
277
|
+
# correctly match the start/end of the `t` block expression
|
278
|
+
# (including nested ones)
|
279
|
+
ERB_EXPRESSION = /<%=\s*(?<content>.*?)\s*%>/
|
280
|
+
ERB_BLOCK_EXPRESSION = /
|
281
|
+
\A
|
282
|
+
<%=
|
283
|
+
.*?
|
284
|
+
(\sdo|\{)
|
285
|
+
(\s*\|[^\|]+\|)?
|
286
|
+
\s*
|
287
|
+
%>
|
288
|
+
\z
|
289
|
+
/xm
|
290
|
+
ERB_T_BLOCK_EXPRESSION = /
|
291
|
+
\A
|
292
|
+
<%=
|
293
|
+
\s*
|
294
|
+
t
|
295
|
+
\s*?
|
296
|
+
(\(\)\s*)?
|
297
|
+
(\sdo|\{)
|
298
|
+
\s*
|
299
|
+
%>
|
300
|
+
\z
|
301
|
+
/xm
|
302
|
+
ERB_STATEMENT = /\A<%[^=]/
|
303
|
+
ERB_END_STATEMENT = /
|
304
|
+
\A
|
305
|
+
<%
|
306
|
+
\s*
|
307
|
+
(end|\})
|
308
|
+
(\W|%>\z)
|
309
|
+
/xm
|
310
|
+
ERB_TOKENIZER = /(<%.*?%>)/m
|
311
|
+
|
312
|
+
def self.process(source)
|
313
|
+
new(source).result
|
314
|
+
end
|
315
|
+
|
316
|
+
def initialize(source)
|
317
|
+
@source = source
|
318
|
+
end
|
319
|
+
|
320
|
+
def result
|
321
|
+
# the basic idea:
|
322
|
+
# 1. whenever we find a t block expr, go till we find the end
|
323
|
+
# 2. if we find another t block expr before the end, goto step 1
|
324
|
+
# 3. capture any inline expressions along the way
|
325
|
+
# 4. if we find *any* other statement or block expr, abort,
|
326
|
+
# since it's a no-go
|
327
|
+
# TODO get line numbers for errors
|
328
|
+
ctx = @source.split(ERB_TOKENIZER).inject(Context.new, :<<)
|
329
|
+
raise MalformedErbError.new('possibly unterminated block expression') if ctx.parent
|
330
|
+
ctx.result
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
@@ -1,10 +1,19 @@
|
|
1
|
+
require 'globby'
|
2
|
+
require 'i18nliner/base'
|
3
|
+
require 'i18nliner/processors'
|
4
|
+
|
1
5
|
module I18nliner
|
2
6
|
module Processors
|
3
7
|
class AbstractProcessor
|
8
|
+
attr_reader :translation_count, :file_count
|
9
|
+
|
4
10
|
def initialize(translations, options = {})
|
5
11
|
@translations = translations
|
12
|
+
@translation_count = 0
|
13
|
+
@file_count = 0
|
6
14
|
@only = options[:only]
|
7
|
-
@checker = options[:checker] ||
|
15
|
+
@checker = options[:checker] || method(:noop_checker)
|
16
|
+
@pattern = options[:pattern] || self.class.default_pattern
|
8
17
|
end
|
9
18
|
|
10
19
|
def noop_checker(file)
|
@@ -13,21 +22,33 @@ module I18nliner
|
|
13
22
|
|
14
23
|
def files
|
15
24
|
@files ||= begin
|
16
|
-
files = Globby.select(@pattern)
|
17
|
-
files = files.select(@only) if @only
|
25
|
+
files = Globby.select(Array(@pattern))
|
26
|
+
files = files.select(Array(@only.dup)) if @only
|
18
27
|
files.reject(I18nliner.ignore)
|
19
28
|
end
|
20
29
|
end
|
21
30
|
|
22
31
|
def check_files
|
23
|
-
|
24
|
-
|
32
|
+
Dir.chdir(I18nliner.base_path) do
|
33
|
+
files.each do |file|
|
34
|
+
@checker.call file, &method(:check_file)
|
35
|
+
end
|
25
36
|
end
|
26
37
|
end
|
27
38
|
|
39
|
+
def check_file(file)
|
40
|
+
@file_count += 1
|
41
|
+
check_contents(source_for(file), scope_for(file))
|
42
|
+
end
|
43
|
+
|
28
44
|
def self.inherited(klass)
|
29
45
|
Processors.register klass
|
30
46
|
end
|
47
|
+
|
48
|
+
def self.default_pattern(*pattern)
|
49
|
+
@pattern ||= []
|
50
|
+
@pattern.concat(pattern)
|
51
|
+
end
|
31
52
|
end
|
32
53
|
end
|
33
54
|
end
|
@@ -1,9 +1,25 @@
|
|
1
|
+
if defined?(::Rails)
|
2
|
+
require 'i18nliner/erubis'
|
3
|
+
else
|
4
|
+
require 'erubis'
|
5
|
+
end
|
6
|
+
require 'i18nliner/processors/ruby_processor'
|
7
|
+
require 'i18nliner/pre_processors/erb_pre_processor'
|
8
|
+
|
1
9
|
module I18nliner
|
2
10
|
module Processors
|
3
11
|
class ErbProcessor < RubyProcessor
|
4
|
-
|
5
|
-
|
6
|
-
|
12
|
+
default_pattern '*.erb'
|
13
|
+
|
14
|
+
if defined?(::Rails) # block expressions and all that jazz
|
15
|
+
def pre_process(source)
|
16
|
+
I18nliner::Erubis.new(source).src
|
17
|
+
end
|
18
|
+
else
|
19
|
+
def pre_process(source)
|
20
|
+
source = PreProcessors::ErbPreProcessor.process(source)
|
21
|
+
Erubis::Eruby.new(source).src
|
22
|
+
end
|
7
23
|
end
|
8
24
|
|
9
25
|
def scope_for(path)
|
@@ -1,11 +1,18 @@
|
|
1
|
+
require 'i18nliner/processors/abstract_processor'
|
2
|
+
require 'i18nliner/extractors/ruby_extractor'
|
3
|
+
require 'i18nliner/scope'
|
4
|
+
|
1
5
|
module I18nliner
|
2
6
|
module Processors
|
3
7
|
class RubyProcessor < AbstractProcessor
|
4
|
-
|
5
|
-
|
6
|
-
|
8
|
+
default_pattern '*.rb'
|
9
|
+
|
10
|
+
def check_contents(source, scope = Scope.new)
|
11
|
+
sexps = RubyParser.new.parse(pre_process(source))
|
12
|
+
extractor = Extractors::RubyExtractor.new(sexps, scope)
|
7
13
|
extractor.each_translation do |key, value|
|
8
|
-
@
|
14
|
+
@translation_count += 1
|
15
|
+
@translations.line = extractor.current_line
|
9
16
|
@translations[key] = value
|
10
17
|
end
|
11
18
|
end
|
@@ -15,7 +22,11 @@ module I18nliner
|
|
15
22
|
end
|
16
23
|
|
17
24
|
def scope_for(path)
|
18
|
-
Scope.
|
25
|
+
Scope.root
|
26
|
+
end
|
27
|
+
|
28
|
+
def pre_process(source)
|
29
|
+
source
|
19
30
|
end
|
20
31
|
end
|
21
32
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'i18nliner/extensions/controller'
|
2
|
+
require 'i18nliner/extensions/view'
|
3
|
+
require 'i18nliner/extensions/model'
|
4
|
+
|
5
|
+
module I18nliner
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
ActiveSupport.on_load :action_controller do
|
8
|
+
ActionController::Base.send :include, I18nliner::Extensions::Controller
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveSupport.on_load :action_view do
|
12
|
+
require 'i18nliner/erubis'
|
13
|
+
ActionView::Template::Handlers::ERB.erb_implementation = I18nliner::Erubis
|
14
|
+
ActionView::Base.send :include, I18nliner::Extensions::View
|
15
|
+
end
|
16
|
+
|
17
|
+
ActiveSupport.on_load :active_record do
|
18
|
+
ActiveRecord::Base.send :include, I18nliner::Extensions::Model
|
19
|
+
end
|
20
|
+
|
21
|
+
rake_tasks do
|
22
|
+
load "tasks/i18nliner.rake"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
data/lib/i18nliner/scope.rb
CHANGED
data/lib/i18nliner.rb
CHANGED
@@ -1,31 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18nliner/base'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
4
|
+
require 'i18nliner/extensions/core'
|
5
|
+
I18n.send :extend, I18nliner::Extensions::Core
|
6
6
|
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.setting(key, value)
|
11
|
-
instance_eval <<-CODE
|
12
|
-
def #{key}(value = nil)
|
13
|
-
if value && block_given?
|
14
|
-
begin
|
15
|
-
value_was = @#{key}
|
16
|
-
@#{key} = value
|
17
|
-
yield
|
18
|
-
ensure
|
19
|
-
@#{key} = value_was
|
20
|
-
end
|
21
|
-
else
|
22
|
-
@#{key} = #{value.inspect} if @#{key}.nil?
|
23
|
-
@#{key}
|
24
|
-
end
|
25
|
-
end
|
26
|
-
CODE
|
27
|
-
end
|
28
|
-
|
29
|
-
setting :inferred_key_format, :underscored_crc32
|
30
|
-
setting :infer_interpolation_values, true
|
31
|
-
end
|
7
|
+
require 'i18nliner/railtie' if defined?(Rails)
|
data/lib/tasks/i18nliner.rake
CHANGED
@@ -1,14 +1,20 @@
|
|
1
1
|
namespace :i18nliner do
|
2
2
|
desc "Verifies all translation calls"
|
3
3
|
task :check => :environment do
|
4
|
-
|
5
|
-
|
4
|
+
require 'i18nliner/commands/check'
|
5
|
+
|
6
|
+
options = {:only => ENV['ONLY']}
|
7
|
+
@command = I18nliner::Commands::Check.run(options)
|
8
|
+
@command.success? or exit 1
|
6
9
|
end
|
7
10
|
|
8
11
|
desc "Generates a new [default_locale].yml file for all translations"
|
9
12
|
task :dump => :check do
|
10
|
-
|
11
|
-
|
13
|
+
require 'i18nliner/commands/dump'
|
14
|
+
|
15
|
+
options = {:translations => @command.translations, :file => ENV['YML_FILE']}
|
16
|
+
@command = I18nliner::Commands::Dump.run(options)
|
17
|
+
@command.success? or exit 1
|
12
18
|
end
|
13
19
|
end
|
14
20
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'i18nliner/commands/check'
|
2
|
+
|
3
|
+
describe I18nliner::Commands::Check do
|
4
|
+
describe ".run" do
|
5
|
+
|
6
|
+
around do |example|
|
7
|
+
I18nliner.base_path "spec/fixtures" do
|
8
|
+
example.run
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should find errors" do
|
13
|
+
checker = I18nliner::Commands::Check.new({:silent => true})
|
14
|
+
checker.check_files
|
15
|
+
checker.translations.values.should == ["welcome, %{name}", "Hello World", "*This* is a test, %{user}"]
|
16
|
+
checker.errors.size.should == 2
|
17
|
+
checker.errors.each do |error|
|
18
|
+
error.should =~ /\Ainvalid signature/
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'i18nliner/commands/dump'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
describe I18nliner::Commands::Dump do
|
6
|
+
describe ".run" do
|
7
|
+
|
8
|
+
around do |example|
|
9
|
+
Dir.mktmpdir do |dir|
|
10
|
+
I18nliner.base_path dir do
|
11
|
+
example.run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should dump translations in utf8" do
|
17
|
+
dumper = I18nliner::Commands::Dump.new({:silent => true, :translations => {'i18n' => "Iñtërnâtiônàlizætiøn"}})
|
18
|
+
dumper.run
|
19
|
+
File.read(dumper.yml_file).gsub(/\s+$/, '').should == <<-YML.strip_heredoc.strip
|
20
|
+
---
|
21
|
+
#{I18n.default_locale}:
|
22
|
+
i18n: Iñtërnâtiônàlizætiøn
|
23
|
+
YML
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'i18nliner/extensions/core'
|
3
|
+
|
4
|
+
describe I18nliner::Extensions::Core do
|
5
|
+
let(:i18n) do
|
6
|
+
Module.new do
|
7
|
+
extend(Module.new do
|
8
|
+
def translate(*args)
|
9
|
+
simple_translate(args[0], args[1])
|
10
|
+
end
|
11
|
+
|
12
|
+
def simple_translate(key, options)
|
13
|
+
string = options.delete(:default)
|
14
|
+
interpolate_hash(string, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def interpolate_hash(string, values)
|
18
|
+
I18n.interpolate_hash(string, values)
|
19
|
+
end
|
20
|
+
end)
|
21
|
+
extend I18nliner::Extensions::Core
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".translate" do
|
26
|
+
it "should should normalize the arguments passed into the original translate" do
|
27
|
+
expect(i18n).to receive(:simple_translate).with("hello_name_84ff273f", :default => "Hello %{name}", :name => "bob")
|
28
|
+
i18n.translate("Hello %{name}", :name => "bob")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should apply wrappers" do
|
32
|
+
result = i18n.translate("Hello *bob*. Click **here**", :wrappers => ['<b>\1</b>', '<a href="/">\1</a>'])
|
33
|
+
result.should == "Hello <b>bob</b>. Click <a href=\"/\">here</a>"
|
34
|
+
result.should be_html_safe
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should html-escape the default when applying wrappers" do
|
38
|
+
i18n.translate("*bacon* > narwhals", :wrappers => ['<b>\1</b>']).
|
39
|
+
should == "<b>bacon</b> > narwhals"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe ".translate!" do
|
44
|
+
it "should behave like translate" do
|
45
|
+
expect(i18n).to receive(:simple_translate).with("hello_name_84ff273f", :default => "Hello %{name}", :name => "bob")
|
46
|
+
i18n.translate!("Hello %{name}", :name => "bob")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe ".interpolate_hash" do
|
51
|
+
it "should not mark the result as html-safe if none of the components are html-safe" do
|
52
|
+
result = i18n.interpolate_hash("hello %{name}", :name => "<script>")
|
53
|
+
result.should == "hello <script>"
|
54
|
+
result.should_not be_html_safe
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should html-escape values if the string is html-safe" do
|
58
|
+
result = i18n.interpolate_hash("some markup: %{markup}".html_safe, :markup => "<html>")
|
59
|
+
result.should == "some markup: <html>"
|
60
|
+
result.should be_html_safe
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should html-escape the string and other values if any value is html-safe" do
|
64
|
+
markup = "<input>"
|
65
|
+
result = i18n.interpolate_hash("type %{input} & you get this: %{output}", :input => markup, :output => markup.html_safe)
|
66
|
+
result.should == "type <input> & you get this: <input>"
|
67
|
+
result.should be_html_safe
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|