extract_i18n 0.1.0
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.
- checksums.yaml +7 -0
- data/.github/workflows/verify.yml +23 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.rubocop.yml +35 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/extract-i18n +7 -0
- data/extract_i18n.gemspec +34 -0
- data/lib/extract_i18n.rb +41 -0
- data/lib/extract_i18n/adapters/adapter.rb +35 -0
- data/lib/extract_i18n/adapters/ruby_adapter.rb +126 -0
- data/lib/extract_i18n/adapters/slim_adapter.rb +70 -0
- data/lib/extract_i18n/adapters/slim_adapter_wip.rb +130 -0
- data/lib/extract_i18n/adapters/vue_adapter.rb +31 -0
- data/lib/extract_i18n/cli.rb +76 -0
- data/lib/extract_i18n/file_processor.rb +99 -0
- data/lib/extract_i18n/slimkeyfy/slim_transformer.rb +151 -0
- data/lib/extract_i18n/slimkeyfy/vue_transformer.rb +84 -0
- data/lib/extract_i18n/slimkeyfy/whitespacer.rb +46 -0
- data/lib/extract_i18n/slimkeyfy/word.rb +88 -0
- data/lib/extract_i18n/source_change.rb +98 -0
- data/lib/extract_i18n/version.rb +3 -0
- metadata +156 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ExtractI18n::Slimkeyfy::VueTransformer < ExtractI18n::Slimkeyfy::SlimTransformer
|
4
|
+
BEFORE = /(?<before>.*[\( ^])/.freeze
|
5
|
+
HTML_ARGUMENTS = {
|
6
|
+
placeholder: /(?<html_tag>[a-z\-]*placeholder=\s*)/,
|
7
|
+
title: /(?<html_tag>title=\s*)/,
|
8
|
+
kind_of_title: /(?<html_tag>[a-z\-]+title=\s*)/,
|
9
|
+
label: /(?<html_tag>[a-z\-]*label=\s*)/,
|
10
|
+
description: /(?<html_tag>description=\s*)/,
|
11
|
+
alt: /(?<html_tag>alt=\s*)/,
|
12
|
+
prepend: /(?<html_tag>prepend=\s*)/,
|
13
|
+
append: /(?<html_tag>append=\s*)/,
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def regex_list
|
17
|
+
HTML_ARGUMENTS.map { |_, regex| /#{BEFORE}#{regex}#{TRANSLATION}#{AFTER}/ }
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_html(&_block)
|
21
|
+
return @word.line if @word.line.match(TRANSLATED)
|
22
|
+
return @word.line if @word.tail.join(" ")[/^{{.*}}$/]
|
23
|
+
|
24
|
+
body = @word.tail.join(" ")
|
25
|
+
body, tagged_with_equals = ExtractI18n::Slimkeyfy::Whitespacer.convert_nbsp(body, @word.head)
|
26
|
+
|
27
|
+
tagged_with_equals = "|" if tagged_with_equals == "="
|
28
|
+
|
29
|
+
interpolate_arguments, body = extract_arguments(body)
|
30
|
+
|
31
|
+
change = ExtractI18n::SourceChange.new(
|
32
|
+
source_line: @word.line,
|
33
|
+
remove: body,
|
34
|
+
interpolate_arguments: interpolate_arguments,
|
35
|
+
interpolation_type: :vue,
|
36
|
+
i18n_key: "#{@file_key}.#{ExtractI18n.key(body)}",
|
37
|
+
i18n_string: body,
|
38
|
+
t_template: "#{@word.indentation}#{tagged_with_equals} {{ $t('%s'%s) }}"
|
39
|
+
)
|
40
|
+
yield(change)
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_html_arguments(line, token_skipped_before = nil, &block)
|
44
|
+
final_line = line
|
45
|
+
regex_list.each do |regex|
|
46
|
+
line.scan(regex) do |m_data|
|
47
|
+
next if m_data == token_skipped_before
|
48
|
+
before = m_data[0]
|
49
|
+
if before[-1] == ":" # already dynamic attribute
|
50
|
+
next
|
51
|
+
end
|
52
|
+
html_tag = m_data[1]
|
53
|
+
translation = match_string(m_data[2])
|
54
|
+
after = m_data[3]
|
55
|
+
interpolate_arguments, translation = extract_arguments(translation)
|
56
|
+
|
57
|
+
change = ExtractI18n::SourceChange.new(
|
58
|
+
source_line: final_line,
|
59
|
+
i18n_string: translation,
|
60
|
+
i18n_key: "#{@file_key}.#{ExtractI18n.key(translation)}",
|
61
|
+
remove: m_data[2],
|
62
|
+
interpolate_arguments: interpolate_arguments,
|
63
|
+
interpolation_type: :vue,
|
64
|
+
t_template: "#{before}:#{html_tag}\"$t('%s'%s)\"#{after}"
|
65
|
+
)
|
66
|
+
final_line = yield(change)
|
67
|
+
return parse_html_arguments(final_line, m_data, &block)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@word.indentation + final_line
|
71
|
+
end
|
72
|
+
|
73
|
+
def extract_arguments(translation)
|
74
|
+
args = {}
|
75
|
+
translation.scan(/\{\{([^}]*)\}\}/).each_with_index do |stripped_arg, index|
|
76
|
+
arg = Regexp.last_match[0]
|
77
|
+
key = ExtractI18n.key(arg)
|
78
|
+
key = key + index.to_s if index > 0
|
79
|
+
translation = translation.gsub(arg, "{#{key}}")
|
80
|
+
args[key] = stripped_arg[0]
|
81
|
+
end
|
82
|
+
[args, translation]
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ExtractI18n
|
4
|
+
class Slimkeyfy::Whitespacer
|
5
|
+
HTML = /([a-z\.]+[0-9\-]*)+/.freeze
|
6
|
+
HTML_TAGS = /^(?<html_tag>'|\||#{HTML})/.freeze
|
7
|
+
|
8
|
+
def self.convert_slim(string)
|
9
|
+
m = string.match(HTML_TAGS)
|
10
|
+
|
11
|
+
return string if m.nil? or m[:html_tag].nil?
|
12
|
+
return string if has_equals_tag?(string, m[:html_tag])
|
13
|
+
tag = m[:html_tag]
|
14
|
+
|
15
|
+
case tag
|
16
|
+
when /\|/
|
17
|
+
string.gsub("|", "=")
|
18
|
+
when /\'/
|
19
|
+
string.gsub("'", "=>")
|
20
|
+
when HTML
|
21
|
+
string.gsub(string, "#{string} =")
|
22
|
+
else string end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.convert_nbsp(body, tag)
|
26
|
+
lead = leading_nbsp(body)
|
27
|
+
trail = trailing_nsbp(body, tag)
|
28
|
+
|
29
|
+
tag = tag.gsub(tag, "#{tag}#{lead}#{trail}")
|
30
|
+
[body.sub(/^ /, '').sub(/ $/, '').gsub(" ", " "), tag.gsub("=><", "=<>")]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.leading_nbsp(body)
|
34
|
+
return "<" if body.start_with?(" ")
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.trailing_nsbp(body, tag)
|
38
|
+
return '' if tag.start_with?("=>")
|
39
|
+
return ">" if body.end_with?(" ")
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.has_equals_tag?(string, html_tag)
|
43
|
+
string.gsub(html_tag, "").strip.start_with?("=")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ExtractI18n::Slimkeyfy
|
4
|
+
class Word
|
5
|
+
attr_reader :line, :tokens, :indentation
|
6
|
+
|
7
|
+
def self.for(extension)
|
8
|
+
if extension == 'vue'
|
9
|
+
JsWord
|
10
|
+
else
|
11
|
+
Word
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(line)
|
16
|
+
@line = line
|
17
|
+
@indentation = " " * (@line.size - unindented_line.size)
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_list
|
21
|
+
# check for div.foo-bar(foo=bar)
|
22
|
+
has_html_form = !!@line[/^ *[\.#]?[a-z\.#-]+\(/]
|
23
|
+
delimiter_items =
|
24
|
+
@line.
|
25
|
+
sub(/^(\s*\|)(\w)/, "\\1 \\2"). # add a whitespace to support "|string"
|
26
|
+
split(has_html_form ? /(?<=[\(])| +/ : ' '). # split by whitespace or ( but keep (
|
27
|
+
drop_while { |i| i == "" } # .. but that leaves leading ""
|
28
|
+
items = []
|
29
|
+
# div: div
|
30
|
+
delimiter_items.reverse_each do |item|
|
31
|
+
if item[/^([a-z]|\.[a-z]|#[a-z]).*:/] and items.length > 0
|
32
|
+
items[-1] = "#{item} #{items[-1]}"
|
33
|
+
else
|
34
|
+
items << item
|
35
|
+
end
|
36
|
+
end
|
37
|
+
items.reverse
|
38
|
+
end
|
39
|
+
|
40
|
+
def unindented_line
|
41
|
+
@line.sub(/^\s*/, "")
|
42
|
+
end
|
43
|
+
|
44
|
+
def head
|
45
|
+
as_list.first
|
46
|
+
end
|
47
|
+
|
48
|
+
def tail
|
49
|
+
as_list.drop(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_arguments(translation)
|
53
|
+
args = {}
|
54
|
+
translation.scan(/\#{[^}]*}/).each_with_index do |arg, index|
|
55
|
+
stripped_arg = arg[2..-2]
|
56
|
+
key = arg[/\w+/]
|
57
|
+
key += index.to_s if index > 0
|
58
|
+
translation = translation.gsub(arg, "%{#{key}}")
|
59
|
+
args[key] = stripped_arg
|
60
|
+
end
|
61
|
+
[args, translation]
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_updated_key(translation_key_with_base)
|
65
|
+
return "" if translation_key_with_base.blank?
|
66
|
+
translation_key_with_base.split(".").last
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class JsWord < Word
|
71
|
+
def initialize(*args)
|
72
|
+
super
|
73
|
+
@use_absolute_key = true
|
74
|
+
end
|
75
|
+
|
76
|
+
def extract_arguments(translation)
|
77
|
+
args = {}
|
78
|
+
translation.scan(/\{\{([^}]*)\}\}/).each_with_index do |stripped_arg, index|
|
79
|
+
arg = Regexp.last_match[0]
|
80
|
+
key = arg[/\w+/]
|
81
|
+
key += index.to_s if index > 0
|
82
|
+
translation = translation.gsub(arg, "{#{key}}")
|
83
|
+
args[key] = stripped_arg[0]
|
84
|
+
end
|
85
|
+
[args, translation]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pastel'
|
4
|
+
|
5
|
+
module ExtractI18n
|
6
|
+
class SourceChange
|
7
|
+
# Data class for a proposed source change
|
8
|
+
|
9
|
+
PASTEL = Pastel.new
|
10
|
+
|
11
|
+
attr_reader :key, :i18n_string
|
12
|
+
|
13
|
+
# @param i18n_key [String]
|
14
|
+
# "models.foo.bar.button_text"
|
15
|
+
# @param interpolate_arguments [Hash]
|
16
|
+
# { "date" => "Date.new.to_s" }
|
17
|
+
# @param source_line [String]
|
18
|
+
# original souce line to modify for viewing purposes
|
19
|
+
# @param remove [String]
|
20
|
+
# what piece of source_line to replace
|
21
|
+
# @param t_template [String]
|
22
|
+
# how to format the replacement translation, use 2 placeholder %s for the string and for the optional arguments
|
23
|
+
# @param interpolation_type [Symbol]
|
24
|
+
# :ruby or :vue
|
25
|
+
def initialize(
|
26
|
+
i18n_key:,
|
27
|
+
i18n_string:,
|
28
|
+
interpolate_arguments:,
|
29
|
+
source_line:,
|
30
|
+
remove:,
|
31
|
+
t_template: %{I18n.t("%s"%s)},
|
32
|
+
interpolation_type: :ruby
|
33
|
+
)
|
34
|
+
@i18n_string = i18n_string
|
35
|
+
@key = i18n_key
|
36
|
+
@interpolate_arguments = interpolate_arguments
|
37
|
+
@source_line = source_line
|
38
|
+
@remove = remove
|
39
|
+
@t_template = t_template
|
40
|
+
@interpolation_type = interpolation_type
|
41
|
+
end
|
42
|
+
|
43
|
+
def format
|
44
|
+
s = ""
|
45
|
+
s += PASTEL.cyan("replace: ") + PASTEL.blue(@source_line).
|
46
|
+
gsub(@remove, PASTEL.red(@remove))
|
47
|
+
unless @source_line.include?("\n")
|
48
|
+
s += "\n"
|
49
|
+
end
|
50
|
+
s += PASTEL.cyan("with: ") + PASTEL.blue(@source_line).
|
51
|
+
gsub(@remove, PASTEL.green(i18n_t))
|
52
|
+
unless @source_line.include?("\n")
|
53
|
+
s += "\n"
|
54
|
+
end
|
55
|
+
s += PASTEL.cyan("add i18n: ") + PASTEL.blue("#{@key}: #{@i18n_string}")
|
56
|
+
s
|
57
|
+
end
|
58
|
+
|
59
|
+
def i18n_t(relative: false)
|
60
|
+
i18n_key = if relative
|
61
|
+
"." + key.split('.').last
|
62
|
+
else
|
63
|
+
key
|
64
|
+
end
|
65
|
+
sprintf(@t_template, i18n_key, i18n_arguments_string)
|
66
|
+
end
|
67
|
+
|
68
|
+
def increment_key!
|
69
|
+
if @key[-1][/(.*)([0-9]+)$/]
|
70
|
+
rest = $1
|
71
|
+
number = $2.to_i
|
72
|
+
else
|
73
|
+
number = 1
|
74
|
+
rest = @key
|
75
|
+
end
|
76
|
+
@key = "#{rest}#{number + 1}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def i18n_arguments_string
|
80
|
+
case @interpolation_type
|
81
|
+
when :ruby
|
82
|
+
if @interpolate_arguments.keys.length > 0
|
83
|
+
", " + @interpolate_arguments.map { |k, v| "#{k}: (#{v})" }.join(', ')
|
84
|
+
else
|
85
|
+
""
|
86
|
+
end
|
87
|
+
when :vue
|
88
|
+
if @interpolate_arguments.keys.length > 0
|
89
|
+
", { " + @interpolate_arguments.map { |k, v| "#{k}: (#{v.strip})" }.join(', ') + " }"
|
90
|
+
else
|
91
|
+
""
|
92
|
+
end
|
93
|
+
else
|
94
|
+
raise NotImplementedError, @interpolation_type
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: extract_i18n
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefan Wienert
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slim
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: tty-prompt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: zeitwerk
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: diff-lcs
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: diffy
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Extact i18n from Ruby files using Ruby parser and slim files using regex
|
98
|
+
interactively
|
99
|
+
email:
|
100
|
+
- info@stefanwienert.de
|
101
|
+
executables:
|
102
|
+
- extract-i18n
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- ".github/workflows/verify.yml"
|
107
|
+
- ".gitignore"
|
108
|
+
- ".rspec"
|
109
|
+
- ".rubocop.yml"
|
110
|
+
- Gemfile
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- bin/console
|
115
|
+
- bin/setup
|
116
|
+
- exe/extract-i18n
|
117
|
+
- extract_i18n.gemspec
|
118
|
+
- lib/extract_i18n.rb
|
119
|
+
- lib/extract_i18n/adapters/adapter.rb
|
120
|
+
- lib/extract_i18n/adapters/ruby_adapter.rb
|
121
|
+
- lib/extract_i18n/adapters/slim_adapter.rb
|
122
|
+
- lib/extract_i18n/adapters/slim_adapter_wip.rb
|
123
|
+
- lib/extract_i18n/adapters/vue_adapter.rb
|
124
|
+
- lib/extract_i18n/cli.rb
|
125
|
+
- lib/extract_i18n/file_processor.rb
|
126
|
+
- lib/extract_i18n/slimkeyfy/slim_transformer.rb
|
127
|
+
- lib/extract_i18n/slimkeyfy/vue_transformer.rb
|
128
|
+
- lib/extract_i18n/slimkeyfy/whitespacer.rb
|
129
|
+
- lib/extract_i18n/slimkeyfy/word.rb
|
130
|
+
- lib/extract_i18n/source_change.rb
|
131
|
+
- lib/extract_i18n/version.rb
|
132
|
+
homepage: https://github.com/pludoni/extract_i18n
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.7.6
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Extact i18n from Ruby files using Ruby parser and slim files using regex
|
156
|
+
test_files: []
|