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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d5459b5c55363dfd1893ecafd4d3eb0bea7db570a54da421782a36750dfc50c
4
- data.tar.gz: a9a335109c2ad690a9234f95e7bfe5a24fff8f5a5eb7d642031d3a402773a223
3
+ metadata.gz: 1d6886c7a13cbbe6698326a72004018aaea5f57e753a9fab278f4025181bdd01
4
+ data.tar.gz: 10172e9a05996706d92f314291137c13bb8c957a0a974e9d9852cd295cb45ceb
5
5
  SHA512:
6
- metadata.gz: 756c430da5da81bf5e1a1211c43b1039cd17863e46f64b239d5eaf42c57fe45bc98dfc5f63a4974310a7ed77fa26efb1441d5aa2f036df655ea836589626d598
7
- data.tar.gz: 98b61188778616f1cb643310e3dfd1d76f7cfc6e4b7d5b4b41b10296d26aa438034639133d4cedc5c43f2faacae69bed46fa94e33692519bbd5c22f720c06463
6
+ metadata.gz: dcc33ec2bfab6ec9970508beebb22f57266679c20df3d97ee1eeaea2650c97f052a037ee4ce46de109bc09984b96df7df6a3097edfcb66772e6156c330a466e6
7
+ data.tar.gz: 7d75f557e0a6e1074e504931fa4ea6985153589dd0307fdd08d51faf3d6173ae6a29a0c15c146fee2bbc490d4a201628c067ba5de12257d7999e6a1b13c0bc86
@@ -22,8 +22,8 @@ jobs:
22
22
  run: bundle exec rspec
23
23
  - name: Build and publish gem
24
24
  uses: jstastny/publish-gem-to-github@master
25
+ continue-on-error: true
25
26
  with:
26
- continue-on-error: true
27
27
  token: ${{ secrets.GITHUB_TOKEN }}
28
28
  owner: pludoni
29
29
 
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 good extractable via Regexp.
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$/ then VueAdapter
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
- # frozen_string_literal: true
2
-
1
+ require 'nokogiri'
3
2
  module ExtractI18n::Adapters
4
- class VueAdapter < 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
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
- if @on_ask.call(change)
24
- change.i18n_t
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
- old_line
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
@@ -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[:base_key] = f
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:?\s*)/,
27
- button: /(?<html_tag>[a-z]*\.?button:?\s*(\:[a-z]+\s*,\s*)?)/,
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
- s += PASTEL.cyan("with: ") + PASTEL.blue(@source_line).
51
- gsub(@remove, PASTEL.green(i18n_t))
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[-1][/(.*)([0-9]+)$/]
73
+ if @key[/(.+)([0-9]+)$/]
70
74
  rest = $1
71
75
  number = $2.to_i
72
76
  else
@@ -1,3 +1,3 @@
1
1
  module ExtractI18n
2
- VERSION = "0.3.0"
2
+ VERSION = "0.6.0"
3
3
  end
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.3.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: 2020-10-15 00:00:00.000000000 Z
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
- rubyforge_project:
176
- rubygems_version: 2.7.6
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: []