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
data/lib/i18nliner/errors.rb
CHANGED
@@ -6,7 +6,8 @@ module I18nliner
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def to_s
|
9
|
-
error = self.class.name.humanize
|
9
|
+
error = self.class.name.underscore.humanize
|
10
|
+
error.gsub!(/\AI18nliner\/| error\z/, '')
|
10
11
|
error = "#{error} on line #{@line}"
|
11
12
|
@detail ?
|
12
13
|
error + " (got #{@detail.inspect})" :
|
@@ -14,6 +15,7 @@ module I18nliner
|
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
18
|
+
# extraction errors
|
17
19
|
class InvalidSignatureError < ExtractionError; end
|
18
20
|
class MissingDefaultError < ExtractionError; end
|
19
21
|
class AmbiguousKeyError < ExtractionError; end
|
@@ -26,4 +28,12 @@ module I18nliner
|
|
26
28
|
class InvalidOptionKeyError < ExtractionError; end
|
27
29
|
class KeyAsScopeError < ExtractionError; end
|
28
30
|
class KeyInUseError < ExtractionError; end
|
31
|
+
|
32
|
+
# pre-processing errors
|
33
|
+
class TBlockNestingError < StandardError; end
|
34
|
+
class MalformedErbError < StandardError; end
|
35
|
+
class UnwrappableContentError < StandardError; end
|
36
|
+
|
37
|
+
# runtime errors
|
38
|
+
class InvalidBlockUsageError < ArgumentError; end
|
29
39
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'action_view/template/handlers/erb'
|
2
|
+
require 'i18nliner/pre_processors/erb_pre_processor'
|
3
|
+
|
4
|
+
module I18nliner
|
5
|
+
class Erubis < ::ActionView::Template::Handlers::Erubis
|
6
|
+
def initialize(source, options = {})
|
7
|
+
source = I18nliner::PreProcessors::ErbPreProcessor.new(source).result
|
8
|
+
super(source, options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'i18nliner/base'
|
2
|
+
require 'i18nliner/call_helpers'
|
3
|
+
require 'i18nliner/extensions/inferpolation'
|
4
|
+
|
5
|
+
module I18nliner
|
6
|
+
module Extensions
|
7
|
+
module Controller
|
8
|
+
include Inferpolation
|
9
|
+
|
10
|
+
PATH = "app/controllers"
|
11
|
+
ALLOW_RELATIVE = Rails.version >= '4'
|
12
|
+
|
13
|
+
def translate(*args)
|
14
|
+
key, options = CallHelpers.infer_arguments(args)
|
15
|
+
options = inferpolate(options) if I18nliner.infer_interpolation_values
|
16
|
+
super(key, options)
|
17
|
+
end
|
18
|
+
alias :t :translate
|
19
|
+
alias :t! :translate
|
20
|
+
alias :translate! :translate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'i18nliner/scope'
|
2
|
+
require 'i18nliner/call_helpers'
|
3
|
+
require 'active_support/core_ext/string/output_safety'
|
4
|
+
|
5
|
+
module I18nliner
|
6
|
+
module Extensions
|
7
|
+
module Core
|
8
|
+
def translate(*args)
|
9
|
+
key, options = *CallHelpers.infer_arguments(args)
|
10
|
+
wrappers = options.delete(:wrappers)
|
11
|
+
result = super(key, options)
|
12
|
+
if wrappers
|
13
|
+
result = apply_wrappers(result, wrappers)
|
14
|
+
end
|
15
|
+
result
|
16
|
+
end
|
17
|
+
alias :t :translate
|
18
|
+
alias :t! :translate
|
19
|
+
alias :translate! :translate
|
20
|
+
|
21
|
+
# can't super this one yet :-/
|
22
|
+
def interpolate_hash_with_html_safety(string, values)
|
23
|
+
if string.html_safe? || values.values.any?(&:html_safe?)
|
24
|
+
string = ERB::Util.h(string) unless string.html_safe?
|
25
|
+
values.each do |key, value|
|
26
|
+
values[key] = ERB::Util.h(value) unless value.html_safe?
|
27
|
+
end
|
28
|
+
interpolate_hash_without_html_safety(string.to_str, values).html_safe
|
29
|
+
else
|
30
|
+
interpolate_hash_without_html_safety(string, values)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.extended(base)
|
35
|
+
base.instance_eval do
|
36
|
+
alias :interpolate_hash_without_html_safety :interpolate_hash
|
37
|
+
alias :interpolate_hash :interpolate_hash_with_html_safety
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def apply_wrappers(string, wrappers)
|
44
|
+
string = string.html_safe? ? string.dup : ERB::Util.h(string)
|
45
|
+
if wrappers.is_a?(Array)
|
46
|
+
wrappers = Hash[wrappers.each_with_index.map{ |w, i| ['*' * (1 + i), w]}]
|
47
|
+
end
|
48
|
+
wrappers.sort_by{ |k, v| -k.length }.each do |k, v|
|
49
|
+
pattern = pattern_for(k)
|
50
|
+
string.gsub!(pattern, v)
|
51
|
+
end
|
52
|
+
string.html_safe
|
53
|
+
end
|
54
|
+
|
55
|
+
def pattern_for(key)
|
56
|
+
escaped_key = Regexp.escape(key)
|
57
|
+
/#{escaped_key}(.*?)#{escaped_key}/
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module I18nliner
|
2
|
+
module Extensions
|
3
|
+
module Inferpolation
|
4
|
+
def inferpolate(options)
|
5
|
+
default = options[:default]
|
6
|
+
return options unless default
|
7
|
+
|
8
|
+
default.gsub!(/%\{((@)?\w+(.\w+)*)\}/).each do
|
9
|
+
match = $~
|
10
|
+
key = $1
|
11
|
+
ivar = $2
|
12
|
+
next match if options[key] || options[key.to_sym]
|
13
|
+
parts = key.split('.')
|
14
|
+
receiver = ivar ? instance_variable_get(parts.shift) : self
|
15
|
+
value = parts.inject(receiver) do |obj, message|
|
16
|
+
obj.respond_to?(message) ? obj.send(message) : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
next match if value.nil?
|
20
|
+
new_key = key.sub(/\@/, '').gsub('.', '_')
|
21
|
+
options[new_key.to_sym] = value
|
22
|
+
"%{#{new_key}}"
|
23
|
+
end
|
24
|
+
options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'i18nliner/base'
|
2
|
+
require 'i18nliner/call_helpers'
|
3
|
+
require 'i18nliner/extensions/inferpolation'
|
4
|
+
|
5
|
+
module I18nliner
|
6
|
+
module Extensions
|
7
|
+
module Model
|
8
|
+
include Inferpolation
|
9
|
+
|
10
|
+
PATH = "app/models"
|
11
|
+
ALLOW_RELATIVE = false
|
12
|
+
|
13
|
+
def translate(*args)
|
14
|
+
key, options = CallHelpers.infer_arguments(args)
|
15
|
+
options = inferpolate(options) if I18nliner.infer_interpolation_values
|
16
|
+
I18n.t(key, options)
|
17
|
+
end
|
18
|
+
alias :t :translate
|
19
|
+
alias :t! :translate
|
20
|
+
alias :translate! :translate
|
21
|
+
|
22
|
+
def localize(*args)
|
23
|
+
I18n.localize(*args)
|
24
|
+
end
|
25
|
+
alias :l :localize
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'i18nliner/base'
|
2
|
+
require 'i18nliner/call_helpers'
|
3
|
+
require 'i18nliner/extensions/inferpolation'
|
4
|
+
|
5
|
+
module I18nliner
|
6
|
+
module Extensions
|
7
|
+
module View
|
8
|
+
include Inferpolation
|
9
|
+
|
10
|
+
PATH = "app/views"
|
11
|
+
ALLOW_RELATIVE = true
|
12
|
+
|
13
|
+
def translate(*args)
|
14
|
+
# if a block gets through to here, it either means:
|
15
|
+
# 1. the user did something weird (e.g. <%= t{ "haha" } %>)
|
16
|
+
# 2. the erb pre processor missed it somehow (bug)
|
17
|
+
raise InvalidBlockUsageError.new("block translate calls need to be output (i.e. `<%=`) and the block body must be of the form `%>your string<%`") if block_given?
|
18
|
+
raise ArgumentError.new("wrong number of arguments (0 for 1..3)") if args.empty? || args.size > 3
|
19
|
+
key, options = CallHelpers.infer_arguments(args)
|
20
|
+
options = inferpolate(options) if I18nliner.infer_interpolation_values
|
21
|
+
super(key, options)
|
22
|
+
end
|
23
|
+
alias :t :translate
|
24
|
+
alias :t! :translate
|
25
|
+
alias :translate! :translate
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,6 +1,13 @@
|
|
1
|
+
require 'sexp_processor'
|
2
|
+
require 'i18nliner/errors'
|
3
|
+
require 'i18nliner/extractors/translate_call'
|
4
|
+
require 'i18nliner/extractors/sexp_helper'
|
5
|
+
|
1
6
|
module I18nliner
|
2
7
|
module Extractors
|
3
|
-
class RubyExtractor < SexpProcessor
|
8
|
+
class RubyExtractor < ::SexpProcessor
|
9
|
+
include SexpHelper
|
10
|
+
|
4
11
|
TRANSLATE_CALLS = [:t, :translate]
|
5
12
|
attr_reader :current_line
|
6
13
|
|
@@ -20,7 +27,7 @@ module I18nliner
|
|
20
27
|
receiver = process(exp.shift)
|
21
28
|
receiver = receiver.last if receiver
|
22
29
|
method = exp.shift
|
23
|
-
|
30
|
+
|
24
31
|
if extractable_call?(receiver, method)
|
25
32
|
@current_line = exp.line
|
26
33
|
|
@@ -33,7 +40,7 @@ module I18nliner
|
|
33
40
|
# one
|
34
41
|
process exp.shift until exp.empty?
|
35
42
|
end
|
36
|
-
|
43
|
+
|
37
44
|
s
|
38
45
|
end
|
39
46
|
|
@@ -44,7 +51,8 @@ module I18nliner
|
|
44
51
|
end
|
45
52
|
|
46
53
|
def process_translate_call(receiver, method, args)
|
47
|
-
|
54
|
+
scope = receiver ? Scope.root : @scope
|
55
|
+
call = TranslateCall.new(@scope, @current_line, method, args)
|
48
56
|
call.translations.each &@block
|
49
57
|
end
|
50
58
|
|
@@ -57,38 +65,14 @@ module I18nliner
|
|
57
65
|
end
|
58
66
|
values
|
59
67
|
end
|
60
|
-
|
68
|
+
|
61
69
|
def evaluate_expression(exp)
|
62
|
-
|
63
|
-
|
64
|
-
return exp.shift
|
65
|
-
end
|
66
|
-
return string_from(exp) if string_concatenation?(exp)
|
70
|
+
return raw(exp) if exp.sexp_type == :lit
|
71
|
+
return string_from(exp) if stringish?(exp)
|
67
72
|
return hash_from(exp) if exp.sexp_type == :hash
|
68
73
|
process(exp)
|
69
74
|
UnsupportedExpression
|
70
75
|
end
|
71
|
-
|
72
|
-
def string_concatenation?(exp)
|
73
|
-
exp.sexp_type == :call &&
|
74
|
-
exp[2] == :+ &&
|
75
|
-
exp.last &&
|
76
|
-
exp.last.sexp_type == :str
|
77
|
-
end
|
78
|
-
|
79
|
-
def string_from(exp)
|
80
|
-
exp.shift
|
81
|
-
lhs = exp.shift
|
82
|
-
exp.shift
|
83
|
-
rhs = exp.shift
|
84
|
-
if lhs.sexp_type == :str
|
85
|
-
lhs.last + rhs.last
|
86
|
-
elsif string_concatenation?(lhs)
|
87
|
-
string_from(lhs) + rhs.last
|
88
|
-
else
|
89
|
-
return UnsupportedExpression
|
90
|
-
end
|
91
|
-
end
|
92
76
|
|
93
77
|
def hash_from(exp)
|
94
78
|
exp.shift
|
@@ -96,7 +80,5 @@ module I18nliner
|
|
96
80
|
Hash[*values]
|
97
81
|
end
|
98
82
|
end
|
99
|
-
|
100
|
-
class UnsupportedExpression; end
|
101
83
|
end
|
102
84
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module I18nliner
|
2
|
+
module Extractors
|
3
|
+
module SexpHelper
|
4
|
+
def stringish?(exp)
|
5
|
+
exp && (exp.sexp_type == :str || string_concatenation?(exp))
|
6
|
+
end
|
7
|
+
|
8
|
+
def string_concatenation?(exp)
|
9
|
+
exp.sexp_type == :call &&
|
10
|
+
exp[2] == :+ &&
|
11
|
+
exp.last &&
|
12
|
+
exp.last.sexp_type == :str
|
13
|
+
end
|
14
|
+
|
15
|
+
def raw(exp)
|
16
|
+
exp.shift
|
17
|
+
return exp.shift
|
18
|
+
end
|
19
|
+
|
20
|
+
def string_from(exp)
|
21
|
+
return raw(exp) if exp.sexp_type == :str
|
22
|
+
exp.shift
|
23
|
+
lhs = exp.shift
|
24
|
+
exp.shift
|
25
|
+
rhs = exp.shift
|
26
|
+
if lhs.sexp_type == :str
|
27
|
+
lhs.last + rhs.last
|
28
|
+
elsif stringish?(lhs)
|
29
|
+
string_from(lhs) + rhs.last
|
30
|
+
else
|
31
|
+
UnsupportedExpression
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class UnsupportedExpression; end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
require 'i18nliner/base'
|
1
3
|
require 'i18nliner/call_helpers'
|
2
4
|
require 'i18nliner/errors'
|
3
5
|
|
@@ -6,10 +8,11 @@ module I18nliner
|
|
6
8
|
class TranslateCall
|
7
9
|
include CallHelpers
|
8
10
|
|
9
|
-
|
11
|
+
attr_reader :key, :default
|
12
|
+
|
13
|
+
def initialize(scope, line, method, args)
|
10
14
|
@scope = scope
|
11
15
|
@line = line
|
12
|
-
@receiver = receiver
|
13
16
|
@method = method
|
14
17
|
|
15
18
|
normalize_arguments(args)
|
@@ -25,7 +28,7 @@ module I18nliner
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def normalize
|
28
|
-
@key = normalize_key(@key, @scope
|
31
|
+
@key = normalize_key(@key, @scope)
|
29
32
|
@default = normalize_default(@default, @options || {})
|
30
33
|
end
|
31
34
|
|
@@ -75,18 +78,13 @@ module I18nliner
|
|
75
78
|
def normalize_arguments(args)
|
76
79
|
raise InvalidSignatureError.new(@line, args) if args.empty?
|
77
80
|
|
78
|
-
|
79
|
-
args.unshift infer_key(args[0]) if !has_key && args[0].is_a?(String) || args[0].is_a?(Hash)
|
80
|
-
|
81
|
-
# [key, options] -> [key, nil, options]
|
82
|
-
args.insert(1, nil) if has_key && args[1].is_a?(Hash) && args[2].nil?
|
83
|
-
|
84
|
-
@key, @default, @options, *others = args
|
81
|
+
@key, @options, *others = infer_arguments(args)
|
85
82
|
|
86
83
|
raise InvalidSignatureError.new(@line, args) if !others.empty?
|
87
84
|
raise InvalidSignatureError.new(@line, args) unless @key.is_a?(Symbol) || @key.is_a?(String)
|
88
|
-
raise InvalidSignatureError.new(@line, args) unless @default.nil? || @default.is_a?(String) || @default.is_a?(Hash)
|
89
85
|
raise InvalidSignatureError.new(@line, args) unless @options.nil? || @options.is_a?(Hash)
|
86
|
+
@default = @options.delete(:default) if @options
|
87
|
+
raise InvalidSignatureError.new(@line, args) unless @default.nil? || @default.is_a?(String) || @default.is_a?(Hash)
|
90
88
|
end
|
91
89
|
|
92
90
|
def validate_interpolation_values(key, default)
|
@@ -1,10 +1,10 @@
|
|
1
|
-
module
|
1
|
+
module I18nliner
|
2
2
|
module Extractors
|
3
3
|
class TranslationHash < Hash
|
4
4
|
attr_accessor :line
|
5
5
|
|
6
|
-
def self.new(hash)
|
7
|
-
hash.is_a?(self) ? hash : super
|
6
|
+
def self.new(hash = {})
|
7
|
+
hash.is_a?(self) ? hash : super().replace(hash)
|
8
8
|
end
|
9
9
|
|
10
10
|
def initialize(*args)
|
@@ -23,7 +23,7 @@ module I18nLine
|
|
23
23
|
raise KeyAsScopeError, intermediate_key
|
24
24
|
end
|
25
25
|
else
|
26
|
-
hash
|
26
|
+
hash.store(part, {})
|
27
27
|
end
|
28
28
|
hash = hash[part]
|
29
29
|
end
|
@@ -37,9 +37,9 @@ module I18nLine
|
|
37
37
|
end
|
38
38
|
else
|
39
39
|
@total_size += 1
|
40
|
-
hash
|
40
|
+
hash.store(leaf, value)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
-
end
|
45
|
+
end
|