extract_i18n 0.1.0 → 0.2.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 +4 -4
- data/.gitignore +1 -0
- data/README.md +5 -2
- data/extract_i18n.gemspec +1 -0
- data/lib/extract_i18n.rb +5 -1
- data/lib/extract_i18n/adapters/erb_adapter.rb +43 -0
- data/lib/extract_i18n/html_extractor/erb_document.rb +83 -0
- data/lib/extract_i18n/html_extractor/match.rb +43 -0
- data/lib/extract_i18n/html_extractor/match/attribute_match.rb +25 -0
- data/lib/extract_i18n/html_extractor/match/base_match.rb +18 -0
- data/lib/extract_i18n/html_extractor/match/erb_directive_match.rb +33 -0
- data/lib/extract_i18n/html_extractor/match/node_match.rb +28 -0
- data/lib/extract_i18n/html_extractor/match/plain_text_match.rb +19 -0
- data/lib/extract_i18n/html_extractor/runner.rb +94 -0
- data/lib/extract_i18n/html_extractor/two_way_regexp.rb +70 -0
- data/lib/extract_i18n/version.rb +1 -1
- metadata +26 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e8c03edd94a9dd0fd99ecb17c2f2140b9ba96894a3e36b29ae623b5a79cfec8
|
|
4
|
+
data.tar.gz: 2c7bd0bae8c25e443d7f33c31dbdc5eb0832ad0d9bf2367a051555e16dcc8176
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3b44e7bdc8c7b4db6a8391c6cdfc2b82ec9bde4c0fc4cf4feffc982cbeb016dec5ee979799a77b7ed96029693cc059082161882b8e7443aceee50be8651e9fe
|
|
7
|
+
data.tar.gz: 637756d6127ccb0a719e723a6d155206a5b87a485e3b91c2698bd8749ab24f73765df1fb66cf79ec3e1af253e3fe28c8333bf009ffce7db79ea58f535e1e13c1
|
data/.gitignore
CHANGED
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ExtractI18n
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/extract_i18n)
|
|
4
|
+
|
|
3
5
|
CLI helper program to automatically extract bare text strings into Rails I18n interactively.
|
|
4
6
|
|
|
5
7
|
Useful when adding i18n to a medium/large Rails app.
|
|
@@ -7,13 +9,14 @@ Useful when adding i18n to a medium/large Rails app.
|
|
|
7
9
|
This Gem **supports** the following source files:
|
|
8
10
|
|
|
9
11
|
- Ruby files (controllers, models etc.) via Ruby-Parser, e.g. walking all Ruby Strings
|
|
10
|
-
- Slim Views (via Regexp parser by SlimKeyfy)
|
|
12
|
+
- Slim Views (via Regexp parser by [SlimKeyfy](https://github.com/phrase/slimkeyfy) (MIT License))
|
|
11
13
|
- Vue Pug views
|
|
12
14
|
- Pug is very similar to slim and thus relatively good extractable via Regexp.
|
|
15
|
+
- ERB views
|
|
16
|
+
- by vendoring/extending https://github.com/ProGM/i18n-html_extractor (MIT License)
|
|
13
17
|
|
|
14
18
|
CURRENTLY THERE IS **NO SUPPORT** FOR:
|
|
15
19
|
|
|
16
|
-
- erb ( integrating/forking https://github.com/zigzag/ready_for_i18n or https://github.com/ProGM/i18n-html_extractor)
|
|
17
20
|
- haml ( integrating https://github.com/shaiguitar/haml-i18n-extractor)
|
|
18
21
|
- vue html templates ([Check out my vue pug converting script](https://gist.github.com/zealot128/6c41df1d33a810856a557971a04989f6))
|
|
19
22
|
|
data/extract_i18n.gemspec
CHANGED
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
26
26
|
spec.require_paths = ["lib"]
|
|
27
27
|
|
|
28
|
+
spec.add_runtime_dependency 'nokogiri'
|
|
28
29
|
spec.add_runtime_dependency 'parser', '>= 2.6'
|
|
29
30
|
spec.add_runtime_dependency 'slim'
|
|
30
31
|
spec.add_runtime_dependency 'tty-prompt'
|
data/lib/extract_i18n.rb
CHANGED
|
@@ -4,11 +4,14 @@ require "extract_i18n/version"
|
|
|
4
4
|
|
|
5
5
|
require "zeitwerk"
|
|
6
6
|
loader = Zeitwerk::Loader.for_gem
|
|
7
|
+
loader.inflector.inflect(
|
|
8
|
+
"html_extractor" => "HTMLExtractor",
|
|
9
|
+
)
|
|
7
10
|
loader.setup # ready!
|
|
8
11
|
|
|
9
12
|
module ExtractI18n
|
|
10
13
|
class << self
|
|
11
|
-
attr_accessor :strip_path, :ignore_hash_keys, :ignore_functions, :ignorelist
|
|
14
|
+
attr_accessor :strip_path, :ignore_hash_keys, :ignore_functions, :ignorelist, :html_fields_with_plaintext
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
self.strip_path = %r{^app/(javascript|controllers|views)|^lib|^src|^app}
|
|
@@ -20,6 +23,7 @@ module ExtractI18n
|
|
|
20
23
|
'_',
|
|
21
24
|
'::'
|
|
22
25
|
]
|
|
26
|
+
self.html_fields_with_plaintext = %w[title placeholder alt label aria-label modal-title]
|
|
23
27
|
|
|
24
28
|
def self.key(string, length: 25)
|
|
25
29
|
string.strip.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module ExtractI18n::Adapters
|
|
2
|
+
class ErbAdapter < Adapter
|
|
3
|
+
def run(original_content)
|
|
4
|
+
unless valid_erb?(original_content)
|
|
5
|
+
puts "ERB invalid!"
|
|
6
|
+
return original_content
|
|
7
|
+
end
|
|
8
|
+
document = ExtractI18n::HTMLExtractor::ErbDocument.parse_string(original_content)
|
|
9
|
+
nodes_to_translate = ExtractI18n::HTMLExtractor::Match::Finder.new(document).matches
|
|
10
|
+
nodes_to_translate.each { |node|
|
|
11
|
+
next if node.text == ""
|
|
12
|
+
|
|
13
|
+
process_change(node)
|
|
14
|
+
}
|
|
15
|
+
result = document.save
|
|
16
|
+
|
|
17
|
+
result
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def valid_erb?(content)
|
|
21
|
+
Parser::CurrentRuby.parse(ERB.new(content).src)
|
|
22
|
+
true
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
warn e.inspect
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def process_change(node)
|
|
29
|
+
change = ExtractI18n::SourceChange.new(
|
|
30
|
+
i18n_key: "#{@file_key}.#{ExtractI18n.key(node.text.strip)}",
|
|
31
|
+
i18n_string: node.text,
|
|
32
|
+
interpolate_arguments: {},
|
|
33
|
+
source_line: node.to_s,
|
|
34
|
+
remove: node.text,
|
|
35
|
+
t_template: %{ t('%s') },
|
|
36
|
+
interpolation_type: :ruby
|
|
37
|
+
)
|
|
38
|
+
if @on_ask.call(change)
|
|
39
|
+
node.replace_text!(change.key, change.i18n_t)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module ExtractI18n
|
|
4
|
+
module HTMLExtractor
|
|
5
|
+
class ErbDocument
|
|
6
|
+
ERB_REGEXPS = [
|
|
7
|
+
TwoWayRegexp.new(/<%=(?<inner_text>.+?)%>/m, /@@=(?<inner_text>[a-z0-9\-\._]+)@@/m),
|
|
8
|
+
TwoWayRegexp.new(/<%#(?<inner_text>.+?)%>/m, /@@#(?<inner_text>[a-z0-9\-\._]+)@@/m),
|
|
9
|
+
TwoWayRegexp.new(/<%(?<inner_text>.+?)%>/m, /@@(?<inner_text>[a-z0-9\-\._]+)@@/m)
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :erb_directives
|
|
13
|
+
|
|
14
|
+
def initialize(document, erb_directives)
|
|
15
|
+
@document = document
|
|
16
|
+
@erb_directives = erb_directives
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def save
|
|
20
|
+
result = @document.to_html(indent: 2, encoding: 'UTF-8')
|
|
21
|
+
ERB_REGEXPS.each do |regexp|
|
|
22
|
+
regexp.inverse_replace!(result) do |string_format, data|
|
|
23
|
+
string_format % { inner_text: erb_directives[data[:inner_text]] }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
result
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def method_missing(name, *args, &block)
|
|
30
|
+
@document.public_send(name, *args, &block) if @document.respond_to? name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class <<self
|
|
34
|
+
def parse(filename, verbose: false)
|
|
35
|
+
file_content = ''
|
|
36
|
+
File.open(filename) do |file|
|
|
37
|
+
file.read(nil, file_content)
|
|
38
|
+
return parse_string(file_content, verbose: verbose)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parse_string(string, verbose: false)
|
|
43
|
+
erb_directives = extract_erb_directives! string
|
|
44
|
+
document = create_document(string)
|
|
45
|
+
log_errors(document.errors, string) if verbose
|
|
46
|
+
ErbDocument.new(document, erb_directives)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def create_document(file_content)
|
|
52
|
+
if file_content.start_with?('<!DOCTYPE')
|
|
53
|
+
Nokogiri::HTML(file_content)
|
|
54
|
+
else
|
|
55
|
+
Nokogiri::HTML.fragment(file_content)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def log_errors(errors, file_content)
|
|
60
|
+
return if errors.empty?
|
|
61
|
+
text = file_content.split("\n")
|
|
62
|
+
errors.each do |e|
|
|
63
|
+
puts "Error at line #{e.line}: #{e}".red
|
|
64
|
+
puts text[e.line - 1]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def extract_erb_directives!(text)
|
|
69
|
+
erb_directives = {}
|
|
70
|
+
|
|
71
|
+
ERB_REGEXPS.each do |regexp|
|
|
72
|
+
regexp.replace!(text) do |string_format, data|
|
|
73
|
+
key = SecureRandom.uuid
|
|
74
|
+
erb_directives[key] = data[:inner_text]
|
|
75
|
+
string_format % { inner_text: key }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
erb_directives
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
module Match
|
|
4
|
+
class Finder
|
|
5
|
+
attr_reader :document
|
|
6
|
+
|
|
7
|
+
def initialize(document)
|
|
8
|
+
@document = document
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def matches
|
|
12
|
+
erb_nodes(document) + plain_text_nodes(document) + form_fields(document)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def erb_nodes(document)
|
|
18
|
+
document.erb_directives.map do |fragment_id, _|
|
|
19
|
+
ErbDirectiveMatch.create(document, fragment_id)
|
|
20
|
+
end.flatten.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def plain_text_nodes(document)
|
|
24
|
+
leaf_nodes.map! { |node| PlainTextMatch.create(document, node) }.flatten.compact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def form_fields(document)
|
|
28
|
+
ExtractI18n.html_fields_with_plaintext.flat_map do |field|
|
|
29
|
+
document.
|
|
30
|
+
css("[#{field}]").
|
|
31
|
+
select { |input| input[field] && !input[field].empty? }.
|
|
32
|
+
reject { |n| n[field] =~ /\@\@(=?)[a-z0-9\-]+\@\@/ }.
|
|
33
|
+
flat_map { |node| AttributeMatch.create(document, node, field) }
|
|
34
|
+
end.compact
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def leaf_nodes
|
|
38
|
+
@leaf_nodes ||= document.css('*:not(:has(*))').select { |n| n.text && !n.text.empty? }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
module Match
|
|
4
|
+
class AttributeMatch < BaseMatch
|
|
5
|
+
def initialize(document, node, text, attribute)
|
|
6
|
+
super(document, node, text)
|
|
7
|
+
@attribute = attribute
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.create(document, node, attribute)
|
|
11
|
+
if node[attribute] && !node[attribute].empty?
|
|
12
|
+
[new(document, node, node[attribute], attribute)]
|
|
13
|
+
else
|
|
14
|
+
[]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def replace_text!(key, i18n_t)
|
|
19
|
+
document.erb_directives[key] = i18n_t
|
|
20
|
+
node[@attribute] = "@@=#{key}@@"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
module Match
|
|
4
|
+
class BaseMatch < NodeMatch
|
|
5
|
+
attr_reader :node
|
|
6
|
+
|
|
7
|
+
def initialize(document, node, text)
|
|
8
|
+
super(document, text)
|
|
9
|
+
@node = node
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def replace_text!
|
|
13
|
+
node.content = translation_key_object
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
module Match
|
|
4
|
+
class ErbDirectiveMatch < NodeMatch
|
|
5
|
+
REGEXPS = [
|
|
6
|
+
[/^([ \t]*link_to )(("[^"]+")|('[^']+'))/, '\1%s', 2],
|
|
7
|
+
[/^([ \t]*link_to (.*),[ ]?title:[ ]?)(("[^"]+")|('[^']+'))/, '\1%s', 3],
|
|
8
|
+
[/^([ \t]*[a-z_]+\.[a-z_]+_field (.*),[ ]?placeholder:[ ]?)(("[^"]+")|('[^']+'))/, '\1%s', 3],
|
|
9
|
+
[/^([ \t]*[a-z_]+\.text_area (.*),[ ]?placeholder:[ ]?)(("[^"]+")|('[^']+'))/, '\1%s', 3],
|
|
10
|
+
[/^([ \t]*[a-z_]+\.submit )(("[^"]+")|('[^']+'))/, '\1%s', 2],
|
|
11
|
+
[/^([ \t]*[a-z_]+\.label\s+\:[a-z_]+\,\s+)(("[^"]+")|('[^']+'))/, '\1%s', 2]
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(document, fragment_id, text, regexp)
|
|
15
|
+
super(document, text)
|
|
16
|
+
@fragment_id = fragment_id
|
|
17
|
+
@regexp = regexp
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def replace_text!(key, i18n_t)
|
|
21
|
+
document.erb_directives[@fragment_id].gsub!(@regexp[0], @regexp[1] % i18n_t.strip)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.create(document, fragment_id)
|
|
25
|
+
REGEXPS.map do |r|
|
|
26
|
+
match = document.erb_directives[fragment_id].match(r[0])
|
|
27
|
+
new(document, fragment_id, match[r[2]][1...-1], r) if match && match[r[2]]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
module Match
|
|
4
|
+
class NodeMatch
|
|
5
|
+
attr_reader :document, :text
|
|
6
|
+
|
|
7
|
+
def initialize(document, text)
|
|
8
|
+
@document = document
|
|
9
|
+
@text = text
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def translation_key_object
|
|
13
|
+
"t('.#{key}')"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def replace_text!
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_writer :key
|
|
21
|
+
|
|
22
|
+
def key
|
|
23
|
+
@key ||= text.parameterize.underscore
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
module Match
|
|
4
|
+
class PlainTextMatch < BaseMatch
|
|
5
|
+
def self.create(document, node)
|
|
6
|
+
return nil if node.name.start_with?('script')
|
|
7
|
+
node.text.split(/\@\@(=?)[a-z0-9\-]+\@\@/).map! do |text|
|
|
8
|
+
new(document, node, text.strip) if !text.nil? && !text.empty?
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def replace_text!(key, i18n_t)
|
|
13
|
+
document.erb_directives[key] = i18n_t
|
|
14
|
+
node.content = node.content.gsub(text, "@@=#{key}@@")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module ExtractI18
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
class Runner
|
|
4
|
+
include Cli
|
|
5
|
+
|
|
6
|
+
def initialize(args = {})
|
|
7
|
+
@files = file_list_from_pattern(args[:file_pattern])
|
|
8
|
+
@locale = args[:locale].presence
|
|
9
|
+
@verbose = args[:verbose]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run_interactive
|
|
13
|
+
each_translation do |file, document, node|
|
|
14
|
+
puts "Found \"#{node.text}\" in #{file}:#{node.text}".green
|
|
15
|
+
next unless confirm 'Create a translation?', 'Yes', 'No', default: 'Yes'
|
|
16
|
+
|
|
17
|
+
node.key = prompt 'Choose i18n key', default: node.key
|
|
18
|
+
node.replace_text!
|
|
19
|
+
|
|
20
|
+
document.save!(file)
|
|
21
|
+
|
|
22
|
+
add_translations! node.key, node.text, default_locale: @locale
|
|
23
|
+
puts
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
each_translation do |file, document, node|
|
|
29
|
+
puts "Found \"#{node.text}\" in #{file}:#{node.text}".green
|
|
30
|
+
node.replace_text!
|
|
31
|
+
document.save!(file)
|
|
32
|
+
|
|
33
|
+
add_translation! I18n.default_locale, node.key, node.text
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_run
|
|
38
|
+
each_translation do |file, _, node|
|
|
39
|
+
puts "Found \"#{node.text}\" in #{file}:#{node.text}".green
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def file_list_from_pattern(pattern)
|
|
46
|
+
if pattern.present?
|
|
47
|
+
Dir[Rails.root.join(pattern)]
|
|
48
|
+
else
|
|
49
|
+
Dir[Rails.root.join('app', 'views', '**', '*.erb')] -
|
|
50
|
+
Dir[Rails.root.join('app', 'views', '**', '*.*.*.erb')]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_translations!(key, text, default_locale: nil)
|
|
55
|
+
return prompt_and_add_translation!(default_locale, key, default_text: text) if default_locale
|
|
56
|
+
prompt_and_add_translation!(I18n.default_locale, key, default_text: text)
|
|
57
|
+
|
|
58
|
+
I18n.available_locales.each do |locale|
|
|
59
|
+
next if locale == I18n.default_locale
|
|
60
|
+
|
|
61
|
+
prompt_and_add_translation!(locale.to_s, key)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def prompt_and_add_translation!(locale, key, default_text: nil)
|
|
66
|
+
out_text = prompt "Choose #{locale} value", default: default_text
|
|
67
|
+
add_translation! locale, key, out_text
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_translation!(locale, key, value)
|
|
71
|
+
new_keys = i18n.missing_keys(locales: [locale]).set_each_value!(value)
|
|
72
|
+
i18n.data.merge! new_keys
|
|
73
|
+
puts "Added t(.#{key}), translated in #{locale} as #{value}:".green
|
|
74
|
+
puts new_keys.inspect
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def i18n
|
|
78
|
+
I18n::Tasks::BaseTask.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def each_translation
|
|
82
|
+
@files.each do |file|
|
|
83
|
+
document = I18n::HTMLExtractor::ErbDocument.parse file
|
|
84
|
+
nodes_to_translate = extract_all_nodes_to_translate(document)
|
|
85
|
+
nodes_to_translate.each { |node| yield(file, document, node) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def extract_all_nodes_to_translate(document)
|
|
90
|
+
Match::Finder.new(document).matches
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module ExtractI18n
|
|
2
|
+
module HTMLExtractor
|
|
3
|
+
class TwoWayRegexp
|
|
4
|
+
attr_reader :from, :to
|
|
5
|
+
|
|
6
|
+
def initialize(from, to)
|
|
7
|
+
@from = from
|
|
8
|
+
@to = to
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def replace(text)
|
|
12
|
+
if block_given?
|
|
13
|
+
text.gsub(@from) do |matched_text|
|
|
14
|
+
yield(to_as_format, Regexp.last_match, matched_text)
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
text.gsub(@from, reverse_to)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def replace!(text)
|
|
22
|
+
if block_given?
|
|
23
|
+
text.gsub!(@from) do |matched_text|
|
|
24
|
+
yield(to_as_format, Regexp.last_match, matched_text)
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
text.gsub!(@from, reverse_to)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def inverse_replace(text)
|
|
32
|
+
if block_given?
|
|
33
|
+
text.gsub(@to) do |matched_text|
|
|
34
|
+
yield(from_as_format, Regexp.last_match, matched_text)
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
text.gsub(@to, reverse_from)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inverse_replace!(text)
|
|
42
|
+
if block_given?
|
|
43
|
+
text.gsub!(@to) do |matched_text|
|
|
44
|
+
yield(from_as_format, Regexp.last_match, matched_text)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
text.gsub!(@to, reverse_from)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def to_as_format
|
|
54
|
+
@to_as_format ||= @to.source.gsub('%', '%%').gsub!(/\(\?<([a-z_]+)>.*\)/, '%{\1}')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def from_as_format
|
|
58
|
+
@from_as_format ||= @from.source.gsub('%', '%%').gsub!(/\(\?<([a-z_]+)>.*\)/, '%{\1}')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def reverse_from
|
|
62
|
+
@reverse_from ||= @from.source.gsub(/\(\?<([a-z_]+)>.*\)/, '\k{\1}')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def reverse_to
|
|
66
|
+
@reverse_to ||= @to.source.gsub(/\(\?<([a-z_]+)>.*\)/, '\k{\1}')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/extract_i18n/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: extract_i18n
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Wienert
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-10-
|
|
11
|
+
date: 2020-10-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: nokogiri
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: parser
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -117,12 +131,22 @@ files:
|
|
|
117
131
|
- extract_i18n.gemspec
|
|
118
132
|
- lib/extract_i18n.rb
|
|
119
133
|
- lib/extract_i18n/adapters/adapter.rb
|
|
134
|
+
- lib/extract_i18n/adapters/erb_adapter.rb
|
|
120
135
|
- lib/extract_i18n/adapters/ruby_adapter.rb
|
|
121
136
|
- lib/extract_i18n/adapters/slim_adapter.rb
|
|
122
137
|
- lib/extract_i18n/adapters/slim_adapter_wip.rb
|
|
123
138
|
- lib/extract_i18n/adapters/vue_adapter.rb
|
|
124
139
|
- lib/extract_i18n/cli.rb
|
|
125
140
|
- lib/extract_i18n/file_processor.rb
|
|
141
|
+
- lib/extract_i18n/html_extractor/erb_document.rb
|
|
142
|
+
- lib/extract_i18n/html_extractor/match.rb
|
|
143
|
+
- lib/extract_i18n/html_extractor/match/attribute_match.rb
|
|
144
|
+
- lib/extract_i18n/html_extractor/match/base_match.rb
|
|
145
|
+
- lib/extract_i18n/html_extractor/match/erb_directive_match.rb
|
|
146
|
+
- lib/extract_i18n/html_extractor/match/node_match.rb
|
|
147
|
+
- lib/extract_i18n/html_extractor/match/plain_text_match.rb
|
|
148
|
+
- lib/extract_i18n/html_extractor/runner.rb
|
|
149
|
+
- lib/extract_i18n/html_extractor/two_way_regexp.rb
|
|
126
150
|
- lib/extract_i18n/slimkeyfy/slim_transformer.rb
|
|
127
151
|
- lib/extract_i18n/slimkeyfy/vue_transformer.rb
|
|
128
152
|
- lib/extract_i18n/slimkeyfy/whitespacer.rb
|