auto-l18n 0.1.0 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +6 -6
- data/auto-l18n-0.1.0.gem +0 -0
- data/exe/auto-l18n +36 -5
- data/lib/auto/l18n/version.rb +1 -1
- data/lib/auto/l18n.rb +186 -10
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d827a80d2d978e8855592c2fe411c97f169a5c44b59d2a7e3a87da698bad39ad
|
|
4
|
+
data.tar.gz: 82096e37eb8ad1dad0473c5762c1cd04b5e2f078b952747212bc84656bbacab8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b5cbf6914f5e5c1ae1c42d3f7a18895596b621280eee99071110234c878279096d49a226886b6f8a3a9c583fec8ef55c06d5652c18779fe8d2e63d978d58aa58
|
|
7
|
+
data.tar.gz: 945cb23e8fd293d78a81e89a717f3aea3ebfac24358b84df5bc0ac10bd36c765ecb681b8f49090b1888e2e1fa2a5d38ceacb1a3e792661f643d8470b2699bc1a
|
data/README.md
CHANGED
|
@@ -50,7 +50,7 @@ texts.each { |t| puts "- #{t}" }
|
|
|
50
50
|
# Preview changes (dry run)
|
|
51
51
|
result = Auto::L18n.auto_internationalize(
|
|
52
52
|
"app/views/posts/show.html.erb",
|
|
53
|
-
namespace: "views.posts.show",
|
|
53
|
+
# namespace: "views.posts.show", # Optional – will be derived from the file path if omitted
|
|
54
54
|
dry_run: true
|
|
55
55
|
)
|
|
56
56
|
|
|
@@ -58,8 +58,7 @@ puts "Would replace #{result[:total_replaced]} strings"
|
|
|
58
58
|
|
|
59
59
|
# Apply changes
|
|
60
60
|
result = Auto::L18n.auto_internationalize(
|
|
61
|
-
"app/views/posts/show.html.erb"
|
|
62
|
-
namespace: "views.posts.show"
|
|
61
|
+
"app/views/posts/show.html.erb"
|
|
63
62
|
)
|
|
64
63
|
```
|
|
65
64
|
|
|
@@ -70,12 +69,13 @@ result = Auto::L18n.auto_internationalize(
|
|
|
70
69
|
ruby exe/auto-l18n app/views/posts/show.html.erb
|
|
71
70
|
|
|
72
71
|
# Replace with I18n calls (dry run)
|
|
72
|
+
# Note: If --namespace is omitted, it will be derived from the file path (e.g., app/views/admin/users/show.html.erb -> views.admin.users.show)
|
|
73
73
|
ruby exe/auto-l18n app/views/posts/show.html.erb \
|
|
74
|
-
--replace --
|
|
74
|
+
--replace --dry-run
|
|
75
75
|
|
|
76
76
|
# Actually apply changes
|
|
77
77
|
ruby exe/auto-l18n app/views/posts/show.html.erb \
|
|
78
|
-
--replace
|
|
78
|
+
--replace
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
## Example Transformation
|
|
@@ -178,7 +178,7 @@ Options:
|
|
|
178
178
|
--ext=EXTS File extensions (default: .html.erb)
|
|
179
179
|
--replace Replace hardcoded text with I18n calls
|
|
180
180
|
--locale-path=PATH Locale file path (default: config/locales/en.yml)
|
|
181
|
-
--namespace=NS Translation key namespace (e.g., views.posts)
|
|
181
|
+
--namespace=NS Translation key namespace (e.g., views.posts). If omitted, it is derived from the file path (folder hierarchy).
|
|
182
182
|
--dry-run Preview changes without modifying files
|
|
183
183
|
--no-backup Don't create backup files
|
|
184
184
|
-h, --help Show help
|
data/auto-l18n-0.1.0.gem
ADDED
|
Binary file
|
data/exe/auto-l18n
CHANGED
|
@@ -42,7 +42,7 @@ parser = OptionParser.new do |opts|
|
|
|
42
42
|
options[:locale_path] = path
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
opts.on("--namespace=NS", "Namespace for translation keys (e.g., views.posts)") do |ns|
|
|
45
|
+
opts.on("--namespace=NS", "Namespace for translation keys (e.g., views.posts). If omitted, it is derived from the file path (folder hierarchy).") do |ns|
|
|
46
46
|
options[:namespace] = ns
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -101,16 +101,47 @@ if paths.empty?
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
if options[:mode] == "find"
|
|
104
|
-
#
|
|
104
|
+
# Find and list hardcoded text; in --dry-run also show the keys that would be used
|
|
105
105
|
total = 0
|
|
106
106
|
paths.each do |p|
|
|
107
107
|
found = Auto::L18n.find_text(p)
|
|
108
108
|
next if found.empty?
|
|
109
109
|
|
|
110
|
-
total += found.size
|
|
111
110
|
puts "\nFile: #{p}"
|
|
112
|
-
found
|
|
113
|
-
|
|
111
|
+
# List found strings
|
|
112
|
+
total += found.size
|
|
113
|
+
found.each { |s| puts " - #{s}" }
|
|
114
|
+
|
|
115
|
+
# In dry-run, show the keys that would be used for replacement
|
|
116
|
+
if options[:dry_run]
|
|
117
|
+
structured = Auto::L18n.find_text(p, structured: true)
|
|
118
|
+
# mimic the sort used by exchange_text_for_l18n_placeholder (by line desc)
|
|
119
|
+
sorted = structured.sort_by { |f| -(f.line || 0) }
|
|
120
|
+
|
|
121
|
+
# Determine effective namespace as in the library code
|
|
122
|
+
effective_ns = options[:namespace]
|
|
123
|
+
if (effective_ns.nil? || effective_ns.empty?)
|
|
124
|
+
# Call private helper via send to derive from path
|
|
125
|
+
begin
|
|
126
|
+
effective_ns = Auto::L18n.send(:derive_namespace_from_path, p, {})
|
|
127
|
+
effective_ns = nil if effective_ns.nil? || effective_ns.empty?
|
|
128
|
+
rescue NoMethodError
|
|
129
|
+
# Fallback: no derived namespace available
|
|
130
|
+
effective_ns = options[:namespace]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
keys = sorted.each_with_index.map do |f, idx|
|
|
135
|
+
begin
|
|
136
|
+
Auto::L18n.send(:generate_translation_key, f.text, f.type, effective_ns, idx)
|
|
137
|
+
rescue NoMethodError
|
|
138
|
+
# Extremely unlikely; just show placeholder when method not accessible
|
|
139
|
+
"generated_key_#{idx}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
puts " Keys that would be used:"
|
|
144
|
+
keys.each { |k| puts " - #{k}" }
|
|
114
145
|
end
|
|
115
146
|
end
|
|
116
147
|
|
data/lib/auto/l18n/version.rb
CHANGED
data/lib/auto/l18n.rb
CHANGED
|
@@ -64,7 +64,7 @@ module Auto
|
|
|
64
64
|
record = lambda do |str, type:, source:, original_position: nil|
|
|
65
65
|
return if str.nil?
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
# Normalize whitespace
|
|
68
68
|
s = str.gsub(/\s+/, " ").strip
|
|
69
69
|
return if s.empty?
|
|
70
70
|
return if s.length < opts[:min_length]
|
|
@@ -85,14 +85,63 @@ module Auto
|
|
|
85
85
|
# Only skip if multiple curly braces (likely interpolation)
|
|
86
86
|
return if s.scan(/\{/).size > 1 && s.scan(/\}/).size > 1
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
|
|
89
|
+
# - Common field names that often appear as technical tokens rather than UI text
|
|
90
|
+
return if s.downcase == 'email'
|
|
91
|
+
# Skip file paths
|
|
89
92
|
return if s =~ %r{\A\.?/?[\w\-]+(/[\w\-\.]+)+\z}
|
|
93
|
+
|
|
94
|
+
# Heuristics to avoid common non-translatable strings
|
|
95
|
+
# - CSS class/id-like tokens (snake/kebab case, possibly space-separated list)
|
|
96
|
+
if (s.include?('_') || s.include?('-')) && s =~ /\A[a-z0-9 _\-]+\z/
|
|
97
|
+
# e.g., "btn btn-secondary", "group_show", "event-link"
|
|
98
|
+
return
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# - CamelCase or lowerCamelCase identifiers (likely variable/ID names)
|
|
102
|
+
return if s =~ /\A(?:[a-z]+(?:[A-Z][a-z0-9]+)+|[A-Z][a-z0-9]+(?:[A-Z][a-z0-9]+)+)\z/
|
|
103
|
+
|
|
104
|
+
# - Hex colors like #fff or #4CAF50
|
|
105
|
+
return if s =~ /\A#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/
|
|
106
|
+
|
|
107
|
+
# - File names with extensions (e.g., JA_Logo.png, Chart.bundle)
|
|
108
|
+
return if s =~ /\A[\w\-]+(\.[A-Za-z0-9]{2,6})+\z/
|
|
109
|
+
|
|
110
|
+
# - Date/time format strings (strftime-style like %Y-%m-%d, %I:%M %p)
|
|
111
|
+
return if s =~ /%[a-zA-Z]/
|
|
112
|
+
|
|
113
|
+
# - Mostly numeric values (amounts, percentages)
|
|
114
|
+
return if s =~ /\A\d+(?:[.,]\d+)?(?:%|[a-zA-Z]*)?\z/
|
|
115
|
+
|
|
116
|
+
# - Rails-style parameter names with bracket notation used in form helpers
|
|
117
|
+
# e.g., "webhook_event_call[event][]", "permission_ids[]"
|
|
118
|
+
return if s =~ /\A[a-zA-Z0-9_]+(?:\[[^\]]*\])+(?:\[\])?\z/
|
|
119
|
+
|
|
120
|
+
# - Common developer token seen as IDs/variables but not visible copy
|
|
121
|
+
return if s == 'breadcrumb'
|
|
122
|
+
|
|
123
|
+
# - MIME types (single or comma-separated), e.g., "image/*", "image/png,image/jpeg"
|
|
124
|
+
return if s =~ /\A(?:[a-zA-Z0-9.+-]+\/[a-zA-Z0-9+*.-]+)(?:\s*,\s*[a-zA-Z0-9.+-]+\/[a-zA-Z0-9+*.-]+)*\z/
|
|
125
|
+
|
|
126
|
+
# - CSS inline style declarations (property: value; ...)
|
|
127
|
+
# Detect at least one property:value; pair
|
|
128
|
+
return if s =~ /\b[a-zA-Z\-]+\s*:\s*[^;]+;/
|
|
90
129
|
|
|
91
130
|
# Skip code-like syntax
|
|
92
|
-
|
|
131
|
+
return if s =~ /[;=>]{2,}/ || s =~ /function\s*\(/ || s =~ /\b(?:var|const|let)\s+\w+/
|
|
132
|
+
# Method-call chains typical of JS (e.g., this.form.submit(); or foo.bar())
|
|
133
|
+
return if s =~ /[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+\s*\([^)]*\)\s*;?/
|
|
93
134
|
|
|
94
135
|
# Normalize quotes for comparison
|
|
95
136
|
normalized = s.tr('""''', %q{"''"})
|
|
137
|
+
|
|
138
|
+
# Skip common non-visible tokens that appear as ERB strings (helper options, class toggles)
|
|
139
|
+
if type == :erb_string
|
|
140
|
+
# Single lowercase identifier, typical for CSS classes or symbols used in helpers
|
|
141
|
+
return if s =~ /\A[a-z][a-z0-9_\-]*\z/
|
|
142
|
+
# Known nav/resource tokens commonly used as classes or symbols
|
|
143
|
+
return if %w[active members events partners permissions groups].include?(s)
|
|
144
|
+
end
|
|
96
145
|
|
|
97
146
|
# Try to estimate line number
|
|
98
147
|
line = estimate_line(original_position, line_map) if original_position
|
|
@@ -118,9 +167,40 @@ module Auto
|
|
|
118
167
|
|
|
119
168
|
# Skip if it's an I18n call
|
|
120
169
|
next if code =~ /\b(?:I18n\.)?(?:t|translate)\s*\(/
|
|
170
|
+
|
|
171
|
+
# Skip asset/helper calls where strings are not visible user text
|
|
172
|
+
# e.g., stylesheet_link_tag "webhooks", media: "all", "data-turbo-track": "reload"
|
|
173
|
+
next if code =~ /\b(?:stylesheet_link_tag|javascript_include_tag|javascript_pack_tag|stylesheet_pack_tag|asset_path|image_tag|image_pack_tag|font_path|font_url|asset_url)\b/
|
|
174
|
+
|
|
175
|
+
# Skip render calls where string literals represent partial/template names, not visible text
|
|
176
|
+
# e.g., render 'form', render partial: 'form'
|
|
177
|
+
next if code =~ /\brender\b/
|
|
178
|
+
|
|
179
|
+
# Skip check_box_tag and radio_button_tag: string args are names/values, not visible copy
|
|
180
|
+
next if code =~ /\b(?:check_box_tag|radio_button_tag)\b/
|
|
181
|
+
|
|
182
|
+
# Helper-aware extraction: only consider visible-content helpers
|
|
183
|
+
# Allow: link_to, button_to, submit_tag, label_tag, content_tag, form builder label/submit
|
|
184
|
+
helper_in_code = (
|
|
185
|
+
code =~ /\b(?:link_to|button_to|submit_tag|label_tag|content_tag)\b/ ||
|
|
186
|
+
code =~ /\b\w+\.(?:label|submit)\b/
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# If no known content-bearing helper is present, skip extracting strings from this ERB block
|
|
190
|
+
unless helper_in_code
|
|
191
|
+
next
|
|
192
|
+
end
|
|
121
193
|
|
|
122
194
|
# Extract double-quoted strings
|
|
123
195
|
code.scan(/"((?:[^"\\]|\\.)*)"/m).each do |string_match|
|
|
196
|
+
# Skip inline conditional class injections like: "calculated" if condition
|
|
197
|
+
# or other inline if/unless patterns composed solely of a literal
|
|
198
|
+
next if code.strip =~ /\A["'][a-z0-9 _\-]+["']\s+(?:if|unless)\b/i
|
|
199
|
+
# Skip option-hash values like: class: "...", id: "...", data: "...", style: "..."
|
|
200
|
+
literal = string_match[0]
|
|
201
|
+
escaped_lit = Regexp.escape(literal)
|
|
202
|
+
next if code =~ /\b[a-zA-Z_]\w*\s*:\s*"#{escaped_lit}"/
|
|
203
|
+
next if code =~ /"#{escaped_lit}"\s*=>/
|
|
124
204
|
unescaped = string_match[0].gsub(/\\(.)/, '\1')
|
|
125
205
|
record.call(
|
|
126
206
|
unescaped,
|
|
@@ -132,6 +212,13 @@ module Auto
|
|
|
132
212
|
|
|
133
213
|
# Extract single-quoted strings
|
|
134
214
|
code.scan(/'((?:[^'\\]|\\.)*)'/).each do |string_match|
|
|
215
|
+
# Skip inline conditional class injections like: 'calculated' if condition
|
|
216
|
+
next if code.strip =~ /\A["'][a-z0-9 _\-]+["']\s+(?:if|unless)\b/i
|
|
217
|
+
# Skip option-hash values like: class: '...'
|
|
218
|
+
literal = string_match[0]
|
|
219
|
+
escaped_lit = Regexp.escape(literal)
|
|
220
|
+
next if code =~ /\b[a-zA-Z_]\w*\s*:\s*'#{escaped_lit}'/
|
|
221
|
+
next if code =~ /'#{escaped_lit}'\s*=>/
|
|
135
222
|
unescaped = string_match[0].gsub(/\\(.)/, '\1')
|
|
136
223
|
record.call(
|
|
137
224
|
unescaped,
|
|
@@ -229,10 +316,30 @@ module Auto
|
|
|
229
316
|
fragment.css(selector).each do |el|
|
|
230
317
|
all_attrs.each do |attr|
|
|
231
318
|
next unless el[attr]
|
|
232
|
-
|
|
233
|
-
# Skip
|
|
234
|
-
next if attr == '
|
|
235
|
-
|
|
319
|
+
|
|
320
|
+
# Skip title attributes (tooltips) per project convention to reduce false positives
|
|
321
|
+
next if attr == 'title'
|
|
322
|
+
|
|
323
|
+
# Special handling for the 'value' attribute:
|
|
324
|
+
# - Only treat as visible text for input types that display their value as a label
|
|
325
|
+
# (submit, button, reset). For all other cases (hidden, text, radio, checkbox,
|
|
326
|
+
# option values, etc.) the value is not a visible label and should be ignored.
|
|
327
|
+
if attr == 'value'
|
|
328
|
+
tag = el.name.to_s.downcase
|
|
329
|
+
if tag == 'input'
|
|
330
|
+
input_type = (el['type'] || '').downcase
|
|
331
|
+
visible_button_types = %w[submit button reset]
|
|
332
|
+
# If not a visible button-like input, skip capturing the value
|
|
333
|
+
next unless visible_button_types.include?(input_type)
|
|
334
|
+
else
|
|
335
|
+
# For non-input tags (e.g., option, button), don't treat value as visible label
|
|
336
|
+
next
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Skip empty values or single characters (after applying 'value' rules above)
|
|
341
|
+
next if el[attr].strip.length < 2
|
|
342
|
+
|
|
236
343
|
position = find_position_in_original(el[attr], raw)
|
|
237
344
|
record.call(
|
|
238
345
|
el[attr],
|
|
@@ -317,7 +424,7 @@ module Auto
|
|
|
317
424
|
results
|
|
318
425
|
end
|
|
319
426
|
|
|
320
|
-
|
|
427
|
+
# Exchange hardcoded text for I18n placeholders
|
|
321
428
|
#
|
|
322
429
|
# This method replaces hardcoded strings in a file with I18n translation calls
|
|
323
430
|
# and adds the translations to a locale file (default: en.yml).
|
|
@@ -333,7 +440,7 @@ module Auto
|
|
|
333
440
|
# @option options [Boolean] :backup (true) Create backup files before modifying
|
|
334
441
|
#
|
|
335
442
|
# @return [Hash] Summary of changes made
|
|
336
|
-
|
|
443
|
+
def self.exchange_text_for_l18n_placeholder(path, options = {})
|
|
337
444
|
raise ArgumentError, "path must be a String" unless path.is_a?(String)
|
|
338
445
|
raise ArgumentError, "File not found: #{path}" unless File.file?(path)
|
|
339
446
|
|
|
@@ -342,6 +449,9 @@ module Auto
|
|
|
342
449
|
locale_path: "config/locales/en.yml",
|
|
343
450
|
locale: "en",
|
|
344
451
|
namespace: nil,
|
|
452
|
+
namespace_from_path: true, # derive namespace from folder/file path when none provided
|
|
453
|
+
path_root: nil, # optional root to strip (e.g., "app/views")
|
|
454
|
+
namespace_prefix: nil, # optional prefix to prepend (e.g., "views")
|
|
345
455
|
dry_run: false,
|
|
346
456
|
min_length: 2,
|
|
347
457
|
ignore_patterns: [],
|
|
@@ -367,9 +477,17 @@ module Auto
|
|
|
367
477
|
# Process findings in reverse order by position to maintain string positions
|
|
368
478
|
sorted_findings = findings.sort_by { |f| -(f.line || 0) }
|
|
369
479
|
|
|
480
|
+
# Determine effective namespace: user-provided or derived from file path
|
|
481
|
+
effective_namespace = opts[:namespace]
|
|
482
|
+
if (effective_namespace.nil? || effective_namespace.empty?) && opts[:namespace_from_path]
|
|
483
|
+
effective_namespace = derive_namespace_from_path(path, opts)
|
|
484
|
+
# Normalize to nil if empty
|
|
485
|
+
effective_namespace = nil if effective_namespace.nil? || effective_namespace.empty?
|
|
486
|
+
end
|
|
487
|
+
|
|
370
488
|
sorted_findings.each_with_index do |finding, idx|
|
|
371
489
|
# Generate translation key
|
|
372
|
-
key = generate_translation_key(finding.text, finding.type,
|
|
490
|
+
key = generate_translation_key(finding.text, finding.type, effective_namespace, idx)
|
|
373
491
|
|
|
374
492
|
# Add to locale file
|
|
375
493
|
set_nested_key(locale_data, key, finding.text, opts[:locale])
|
|
@@ -496,6 +614,64 @@ module Auto
|
|
|
496
614
|
parts.join('.')
|
|
497
615
|
end
|
|
498
616
|
|
|
617
|
+
# Derive a namespace from the file path, producing a dotted hierarchy
|
|
618
|
+
# Examples:
|
|
619
|
+
# - app/views/admin/users/show.html.erb -> "views.admin.users.show"
|
|
620
|
+
# - app/views/posts/index.html.erb -> "views.posts.index"
|
|
621
|
+
# - src/templates/home.html.erb (with namespace_prefix: nil) -> "src.templates.home"
|
|
622
|
+
# - test/test.html.erb (no root) -> "test.test"
|
|
623
|
+
def self.derive_namespace_from_path(path, options = {})
|
|
624
|
+
require 'pathname'
|
|
625
|
+
abs_path = File.expand_path(path)
|
|
626
|
+
|
|
627
|
+
# Resolve root to strip
|
|
628
|
+
root = options[:path_root]
|
|
629
|
+
prefix = options[:namespace_prefix]
|
|
630
|
+
|
|
631
|
+
# Auto-detect common Rails views root and default prefix
|
|
632
|
+
if root.nil? && abs_path.include?(File.join('app', 'views'))
|
|
633
|
+
root = abs_path.split(File.join('app', 'views')).first + File.join('app', 'views')
|
|
634
|
+
prefix ||= 'views'
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
rel_path = nil
|
|
638
|
+
if root && abs_path.start_with?(File.expand_path(root) + File::SEPARATOR)
|
|
639
|
+
rel_path = Pathname.new(abs_path).relative_path_from(Pathname.new(File.expand_path(root))).to_s
|
|
640
|
+
else
|
|
641
|
+
# Try relative to current working directory
|
|
642
|
+
cwd = Dir.pwd
|
|
643
|
+
if abs_path.start_with?(cwd + File::SEPARATOR)
|
|
644
|
+
rel_path = abs_path.sub(cwd + File::SEPARATOR, '')
|
|
645
|
+
else
|
|
646
|
+
rel_path = File.basename(abs_path)
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Remove all extensions (handle multi-extensions like .html.erb)
|
|
651
|
+
base = rel_path.dup
|
|
652
|
+
loop do
|
|
653
|
+
ext = File.extname(base)
|
|
654
|
+
break if ext.nil? || ext.empty?
|
|
655
|
+
base = base.chomp(ext)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# Split into segments, normalize, and join with dots
|
|
659
|
+
segments = base.split(File::SEPARATOR).map do |seg|
|
|
660
|
+
seg.downcase
|
|
661
|
+
.gsub(/[^\w\-\.]+/, '_')
|
|
662
|
+
.gsub(/[\-\.]/, '_')
|
|
663
|
+
.gsub(/_+/, '_')
|
|
664
|
+
.gsub(/^_|_$/, '')
|
|
665
|
+
end.reject(&:empty?)
|
|
666
|
+
|
|
667
|
+
derived = segments.join('.')
|
|
668
|
+
if prefix && !prefix.to_s.empty?
|
|
669
|
+
[prefix, derived].reject(&:empty?).join('.')
|
|
670
|
+
else
|
|
671
|
+
derived
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
499
675
|
# Load locale file (YAML)
|
|
500
676
|
def self.load_locale_file(path, locale)
|
|
501
677
|
if File.exist?(path)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: auto-l18n
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nicolas Reiner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -44,6 +44,7 @@ files:
|
|
|
44
44
|
- LICENSE.txt
|
|
45
45
|
- README.md
|
|
46
46
|
- Rakefile
|
|
47
|
+
- auto-l18n-0.1.0.gem
|
|
47
48
|
- exe/auto-l18n
|
|
48
49
|
- lib/auto/l18n.rb
|
|
49
50
|
- lib/auto/l18n/version.rb
|
|
@@ -56,7 +57,7 @@ metadata:
|
|
|
56
57
|
source_code_uri: https://github.com/NicolasReiner/auto-l18n
|
|
57
58
|
changelog_uri: https://github.com/NicolasReiner/auto-l18n/blob/master/CHANGELOG.md
|
|
58
59
|
bug_tracker_uri: https://github.com/NicolasReiner/auto-l18n/issues
|
|
59
|
-
rubygems_mfa_required: '
|
|
60
|
+
rubygems_mfa_required: 'false'
|
|
60
61
|
post_install_message:
|
|
61
62
|
rdoc_options: []
|
|
62
63
|
require_paths:
|