i18nliner 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|