extract_i18n 0.3.0 → 0.6.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/.github/workflows/verify.yml +1 -1
- data/README.md +4 -2
- data/lib/extract_i18n/adapters/adapter.rb +6 -1
- data/lib/extract_i18n/adapters/vue_adapter.rb +129 -22
- data/lib/extract_i18n/adapters/vue_pug_adapter.rb +31 -0
- data/lib/extract_i18n/cli.rb +1 -1
- data/lib/extract_i18n/slimkeyfy/slim_transformer.rb +2 -2
- data/lib/extract_i18n/source_change.rb +7 -3
- data/lib/extract_i18n/version.rb +1 -1
- data/lib/extract_i18n.rb +3 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d6886c7a13cbbe6698326a72004018aaea5f57e753a9fab278f4025181bdd01
|
4
|
+
data.tar.gz: 10172e9a05996706d92f314291137c13bb8c957a0a974e9d9852cd295cb45ceb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcc33ec2bfab6ec9970508beebb22f57266679c20df3d97ee1eeaea2650c97f052a037ee4ce46de109bc09984b96df7df6a3097edfcb66772e6156c330a466e6
|
7
|
+
data.tar.gz: 7d75f557e0a6e1074e504931fa4ea6985153589dd0307fdd08d51faf3d6173ae6a29a0c15c146fee2bbc490d4a201628c067ba5de12257d7999e6a1b13c0bc86
|
data/README.md
CHANGED
@@ -12,15 +12,17 @@ This Gem **supports** the following source files:
|
|
12
12
|
|
13
13
|
- Ruby files (controllers, models etc.) via Ruby-Parser, e.g. walking all Ruby Strings
|
14
14
|
- Slim Views (via Regexp parser by [SlimKeyfy](https://github.com/phrase/slimkeyfy) (MIT License))
|
15
|
+
- Vue templates
|
16
|
+
- will scan all texts and common string-attributes such as title, alt etc. for static strings and replace with vue-i18n's $t
|
17
|
+
- Caveats: because of limitations of the HTML/XML parser it will slightly transform the HTML, for example, self closing tags are expanded (e.g. ``<Component />`` will become ``<Component></Component>``). Also multi-line arrangements of attributes, tags etc. might produce unexpected results, so make sure to use Git and diff the result.
|
15
18
|
- Vue Pug views
|
16
|
-
- Pug is very similar to slim and thus relatively
|
19
|
+
- Pug is very similar to slim and thus relatively well extractable via Regexp.
|
17
20
|
- ERB views
|
18
21
|
- by vendoring/extending https://github.com/ProGM/i18n-html_extractor (MIT License)
|
19
22
|
|
20
23
|
CURRENTLY THERE IS **NO SUPPORT** FOR:
|
21
24
|
|
22
25
|
- haml ( integrating https://github.com/shaiguitar/haml-i18n-extractor)
|
23
|
-
- vue html templates ([Check out my vue pug converting script](https://gist.github.com/zealot128/6c41df1d33a810856a557971a04989f6))
|
24
26
|
|
25
27
|
But I am open to integrating PRs for those!
|
26
28
|
|
@@ -6,7 +6,12 @@ module ExtractI18n::Adapters
|
|
6
6
|
case file_path
|
7
7
|
when /\.rb$/ then RubyAdapter
|
8
8
|
when /\.slim$/ then SlimAdapter
|
9
|
-
when /\.vue$/
|
9
|
+
when /\.vue$/
|
10
|
+
if File.read(file_path)[/lang=.pug./]
|
11
|
+
VuePugAdapter
|
12
|
+
else
|
13
|
+
VueAdapter
|
14
|
+
end
|
10
15
|
end
|
11
16
|
end
|
12
17
|
|
@@ -1,31 +1,138 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'nokogiri'
|
3
2
|
module ExtractI18n::Adapters
|
4
|
-
class VueAdapter <
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
3
|
+
class VueAdapter < Adapter
|
4
|
+
ATTRIBUTES_WITH_TEXT = %w[
|
5
|
+
title
|
6
|
+
alt
|
7
|
+
placeholder
|
8
|
+
label
|
9
|
+
description
|
10
|
+
]
|
11
|
+
def run(original_content)
|
12
|
+
content2, replace_pairs = replace_camelcase_attributes(original_content)
|
13
|
+
|
14
|
+
doc = Nokogiri::HTML5.fragment(content2)
|
15
|
+
template = doc.at('template')
|
16
|
+
return original_content unless template
|
17
|
+
return original_content if template['lang']
|
18
|
+
|
19
|
+
nodes = template.xpath(".//text() | text()").select { |i| i.to_s.strip.length > 0 }
|
20
|
+
|
21
|
+
nodes.each do |node|
|
22
|
+
process_change(node)
|
23
|
+
end
|
24
|
+
|
25
|
+
ATTRIBUTES_WITH_TEXT.each do |attr|
|
26
|
+
template.search("*[#{attr}]").each do |node|
|
27
|
+
process_attribute_change(node, attr)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
out = unreplace_camelcase_attributes(doc.to_s, replace_pairs)
|
32
|
+
out.gsub!(/ (\w+)=""/) do |all_match|
|
33
|
+
m = $1
|
34
|
+
if original_content[/ #{m}[^=]/]
|
35
|
+
" #{m}"
|
36
|
+
else
|
37
|
+
all_match
|
21
38
|
end
|
39
|
+
end
|
40
|
+
out
|
41
|
+
end
|
22
42
|
|
23
|
-
|
24
|
-
|
43
|
+
# nokogir::html can parse @click.prevent stuff, but transforms all CamelCase Attributes to kebab-case by default
|
44
|
+
# To keep the original case, we replace it with a placeholder and unreplace it back
|
45
|
+
def replace_camelcase_attributes(text)
|
46
|
+
cc = 1
|
47
|
+
pairs = []
|
48
|
+
replaced = text.gsub(/ ([a-zA-Z:]+=)/) { |pp|
|
49
|
+
match = Regexp.last_match
|
50
|
+
cc += 1
|
51
|
+
# kebab-case
|
52
|
+
if pp[/[A-Z]/]
|
53
|
+
kebab = match[1].gsub(/([A-Z])/) { |i| "-#{i.downcase}" }
|
54
|
+
hack = " :camelcase-nokogiri-hack-#{cc}-#{kebab}"
|
55
|
+
pairs << [hack, " " + match[1]]
|
56
|
+
hack
|
25
57
|
else
|
26
|
-
|
58
|
+
pp
|
27
59
|
end
|
60
|
+
}
|
61
|
+
# tags
|
62
|
+
replaced.scan(/<([a-zA-Z0-9]+)/).flatten.each do |pp|
|
63
|
+
next pp unless pp[/[A-Z]/]
|
64
|
+
|
65
|
+
cc += 1
|
66
|
+
|
67
|
+
kebab = pp.gsub(/([A-Z])/) { |i| "-#{i.downcase}" }.sub(/^-/, '')
|
68
|
+
hack = "camelcase-nokogiri-hack-#{cc}-#{kebab}"
|
69
|
+
replaced.gsub!(/<#{pp}/, "<#{hack}")
|
70
|
+
replaced.gsub!(/<\/ *#{pp}/, "</#{hack}")
|
71
|
+
pairs << ["<#{hack}", "<#{pp}"]
|
72
|
+
pairs << ["</#{hack}", "</#{pp}"]
|
73
|
+
end
|
74
|
+
[replaced, pairs]
|
75
|
+
end
|
76
|
+
|
77
|
+
def unreplace_camelcase_attributes(text, pairs)
|
78
|
+
txt = text.dup
|
79
|
+
pairs.each do |hack, camel|
|
80
|
+
txt.gsub!(hack, camel)
|
81
|
+
end
|
82
|
+
txt
|
83
|
+
end
|
84
|
+
|
85
|
+
def process_attribute_change(node, attr)
|
86
|
+
_, ws_before, content, ws_after = node[attr].to_s.match(/(\s*)(.*?)(\s*)$/m).to_a
|
87
|
+
|
88
|
+
interpolate_arguments, content = extract_arguments(content)
|
89
|
+
|
90
|
+
change = ExtractI18n::SourceChange.new(
|
91
|
+
i18n_key: "#{@file_key}.#{ExtractI18n.key(content)}",
|
92
|
+
i18n_string: content,
|
93
|
+
source_line: node.to_s,
|
94
|
+
remove: content,
|
95
|
+
t_template: %[$t('%s'%s)],
|
96
|
+
interpolation_type: :vue,
|
97
|
+
interpolate_arguments: interpolate_arguments,
|
98
|
+
)
|
99
|
+
if @on_ask.call(change)
|
100
|
+
node.remove_attribute "title"
|
101
|
+
node[":" + attr] = "#{ws_before}#{change.i18n_t}#{ws_after}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def process_change(node)
|
106
|
+
_, ws_before, content, ws_after = node.to_s.match(/(\s*)(.*?)(\s*)$/m).to_a
|
107
|
+
|
108
|
+
return if content.include?("{{")
|
109
|
+
|
110
|
+
interpolate_arguments, content = extract_arguments(content)
|
111
|
+
|
112
|
+
change = ExtractI18n::SourceChange.new(
|
113
|
+
i18n_key: "#{@file_key}.#{ExtractI18n.key(content)}",
|
114
|
+
i18n_string: content,
|
115
|
+
source_line: node.parent.to_s,
|
116
|
+
remove: content,
|
117
|
+
t_template: %[{{ $t('%s'%s) }}],
|
118
|
+
interpolation_type: :vue,
|
119
|
+
interpolate_arguments: interpolate_arguments,
|
120
|
+
)
|
121
|
+
if @on_ask.call(change)
|
122
|
+
node.replace("#{ws_before}#{change.i18n_t}#{ws_after}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def extract_arguments(translation)
|
127
|
+
args = {}
|
128
|
+
translation.scan(/\{\{([^}]*)\}\}/).each_with_index do |stripped_arg, index|
|
129
|
+
arg = Regexp.last_match[0]
|
130
|
+
key = ExtractI18n.key(arg)
|
131
|
+
key = key + index.to_s if index > 0
|
132
|
+
translation = translation.gsub(arg, "{#{key}}")
|
133
|
+
args[key] = stripped_arg[0]
|
28
134
|
end
|
135
|
+
[args, translation]
|
29
136
|
end
|
30
137
|
end
|
31
138
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ExtractI18n::Adapters
|
4
|
+
class VuePugAdapter < SlimAdapter
|
5
|
+
def process_line(old_line)
|
6
|
+
@mode ||= :template
|
7
|
+
if old_line[/^<template/]
|
8
|
+
@mode = :template
|
9
|
+
elsif old_line[/^<script/]
|
10
|
+
@mode = :script
|
11
|
+
elsif old_line[/^<style/]
|
12
|
+
@mode = :style
|
13
|
+
end
|
14
|
+
if @mode != :template
|
15
|
+
return old_line
|
16
|
+
end
|
17
|
+
word = ExtractI18n::Slimkeyfy::Word.for('.vue').new(old_line)
|
18
|
+
ExtractI18n::Slimkeyfy::VueTransformer.new(word, @file_key).transform do |change|
|
19
|
+
if change.nil? # nothing to do
|
20
|
+
return old_line
|
21
|
+
end
|
22
|
+
|
23
|
+
if @on_ask.call(change)
|
24
|
+
change.i18n_t
|
25
|
+
else
|
26
|
+
old_line
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/extract_i18n/cli.rb
CHANGED
@@ -27,7 +27,7 @@ module ExtractI18n
|
|
27
27
|
end
|
28
28
|
|
29
29
|
opts.on('-nNAMESPACE', '--namespace=NAMESPACE', 'Locale base key to wrap locations in') do |f|
|
30
|
-
@options[:
|
30
|
+
@options[:namespace] = f
|
31
31
|
end
|
32
32
|
|
33
33
|
opts.on('-r', '--slim-relative', 'When activated, will use relative keys like t(".title")') do |f|
|
@@ -23,8 +23,8 @@ module ExtractI18n
|
|
23
23
|
append: /(?<html_tag>append:\s*)/,
|
24
24
|
label: /(?<html_tag>[a-z]*_?label:\s*)/,
|
25
25
|
optionals: /(?<html_tag>(default|include_blank|alt):\s*)/,
|
26
|
-
input: /(?<html_tag>[a-z]*\.?input
|
27
|
-
button: /(?<html_tag>[a-z]*\.?button
|
26
|
+
input: /(?<html_tag>[a-z]*\.?input:\s*)/,
|
27
|
+
button: /(?<html_tag>[a-z]*\.?button:\s*(\:[a-z]+\s*,\s*)?)/,
|
28
28
|
tag: /(?<html_tag>(submit|content)_tag[\:\(]?\s*)/,
|
29
29
|
data_naive: /(?<html_tag>data:\s*\{\s*(confirm|content):\s*)/
|
30
30
|
}.freeze
|
@@ -47,8 +47,12 @@ module ExtractI18n
|
|
47
47
|
unless @source_line.include?("\n")
|
48
48
|
s += "\n"
|
49
49
|
end
|
50
|
-
|
51
|
-
|
50
|
+
if @source_line[@remove]
|
51
|
+
s += PASTEL.cyan("with: ") + PASTEL.blue(@source_line).
|
52
|
+
gsub(@remove, PASTEL.green(i18n_t))
|
53
|
+
else
|
54
|
+
s += PASTEL.cyan("with: ") + PASTEL.green(i18n_t)
|
55
|
+
end
|
52
56
|
unless @source_line.include?("\n")
|
53
57
|
s += "\n"
|
54
58
|
end
|
@@ -66,7 +70,7 @@ module ExtractI18n
|
|
66
70
|
end
|
67
71
|
|
68
72
|
def increment_key!
|
69
|
-
if @key[
|
73
|
+
if @key[/(.+)([0-9]+)$/]
|
70
74
|
rest = $1
|
71
75
|
number = $2.to_i
|
72
76
|
else
|
data/lib/extract_i18n/version.rb
CHANGED
data/lib/extract_i18n.rb
CHANGED
@@ -21,7 +21,8 @@ module ExtractI18n
|
|
21
21
|
self.ignore_functions = %w[where order group select sql]
|
22
22
|
self.ignorelist = [
|
23
23
|
'_',
|
24
|
-
'::'
|
24
|
+
'::',
|
25
|
+
%r{^/}
|
25
26
|
]
|
26
27
|
self.html_fields_with_plaintext = %w[title placeholder alt label aria-label modal-title]
|
27
28
|
|
@@ -37,6 +38,7 @@ module ExtractI18n
|
|
37
38
|
gsub(%r{^/|/$}, '').
|
38
39
|
gsub(/\.(vue|rb|html\.slim|\.slim)$/, '').
|
39
40
|
gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
41
|
+
gsub('/_', '.').
|
40
42
|
gsub('/', '.').
|
41
43
|
tr("-", "_").downcase
|
42
44
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extract_i18n
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Wienert
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- lib/extract_i18n/adapters/slim_adapter.rb
|
137
137
|
- lib/extract_i18n/adapters/slim_adapter_wip.rb
|
138
138
|
- lib/extract_i18n/adapters/vue_adapter.rb
|
139
|
+
- lib/extract_i18n/adapters/vue_pug_adapter.rb
|
139
140
|
- lib/extract_i18n/cli.rb
|
140
141
|
- lib/extract_i18n/file_processor.rb
|
141
142
|
- lib/extract_i18n/html_extractor/erb_document.rb
|
@@ -157,7 +158,7 @@ homepage: https://github.com/pludoni/extract_i18n
|
|
157
158
|
licenses:
|
158
159
|
- MIT
|
159
160
|
metadata: {}
|
160
|
-
post_install_message:
|
161
|
+
post_install_message:
|
161
162
|
rdoc_options: []
|
162
163
|
require_paths:
|
163
164
|
- lib
|
@@ -172,9 +173,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
173
|
- !ruby/object:Gem::Version
|
173
174
|
version: '0'
|
174
175
|
requirements: []
|
175
|
-
|
176
|
-
|
177
|
-
signing_key:
|
176
|
+
rubygems_version: 3.1.6
|
177
|
+
signing_key:
|
178
178
|
specification_version: 4
|
179
179
|
summary: Extact i18n from Ruby files using Ruby parser and slim files using regex
|
180
180
|
test_files: []
|