i18nize 0.5.1 → 0.6
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/CHANGELOG.md +12 -8
- data/README.md +36 -3
- data/lib/i18nize/cli.rb +62 -67
- data/lib/i18nize/comparer.rb +3 -2
- data/lib/i18nize/inserter.rb +67 -13
- data/lib/i18nize/loader.rb +1 -0
- data/lib/i18nize/missing_set.rb +1 -1
- data/lib/i18nize/translator.rb +5 -4
- data/lib/i18nize/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed1bfd2cff604aa20337cfd2e87dd47985ab66d470ee13fa1241378e8c7613d2
|
|
4
|
+
data.tar.gz: 8e2a54ea7acb3ff57233944f81648f8243f732768278cd77478f7bf5fed08d35
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7322c2b6b1bc505090789774f340404186be3dcaed272629b19ab914bd36af3f8c1d50cde289ce29e203c60fc3273d5a8981c11060fdbfd571d261bc96803a3
|
|
7
|
+
data.tar.gz: ff89ec4d79479de9c40855916fc0a5dfa3f87a6e300459625edcfb7928aafb54ac15faec21e3a9e68d6a8c47543d716ac53a41620496363e86c7abb5314d00e4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
3
|
+
## [0.6] - 2025-09-11
|
|
7
4
|
### Added
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
- CLI flag `--only_blank`: fills only missing or blank keys with a placeholder
|
|
6
|
+
(`<locale>.<full.key> missing`) instead of translating with DeepL.
|
|
7
|
+
- Environment variable `I18NIZE_ALLOW_EMPTY=1`: skips keys where the source value is empty
|
|
8
|
+
or whitespace-only, preventing them from being copied/translated.
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
### Changed
|
|
11
|
+
- CLI parsing improved: `--from` can now be used **before or after** the target locale.
|
|
12
|
+
Examples:
|
|
13
|
+
```bash
|
|
14
|
+
i18nize cs --from de
|
|
15
|
+
i18nize --from de cs
|
|
16
|
+
````
|
|
13
17
|
|
|
14
18
|
---
|
|
15
19
|
|
data/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
|
|
1
2
|
# i18nize
|
|
2
3
|
|
|
4
|
+
[](https://github.com/nikolas2145/i18nize/actions/workflows/main.yml)
|
|
5
|
+
[](https://badge.fury.io/rb/i18nize)
|
|
6
|
+
|
|
3
7
|
🧠 Automatically fills in missing I18n YAML keys using DeepL translation API.
|
|
4
8
|
Designed to streamline multi-language support in Ruby and Rails projects.
|
|
5
9
|
|
|
@@ -8,6 +12,7 @@ Designed to streamline multi-language support in Ruby and Rails projects.
|
|
|
8
12
|
* Detects missing or empty translation keys in locale files
|
|
9
13
|
* Supports multiple YAML files and nested directories (e.g. `config/locales/api/en.yml`)
|
|
10
14
|
* Automatically translates from a source locale (**default: `en`**) to a target (e.g. `cs`)
|
|
15
|
+
* **Placeholder mode** to fill only blank/missing keys without DeepL (`--only_blank`)
|
|
11
16
|
* Integrates with DeepL API (Free or Pro)
|
|
12
17
|
* Safely merges translations into corresponding `*.yml` files
|
|
13
18
|
* Handles structural conflicts by overwriting target with source truth
|
|
@@ -20,7 +25,7 @@ Add this line to your application's Gemfile:
|
|
|
20
25
|
|
|
21
26
|
```ruby
|
|
22
27
|
gem "i18nize"
|
|
23
|
-
|
|
28
|
+
````
|
|
24
29
|
|
|
25
30
|
Then run:
|
|
26
31
|
|
|
@@ -76,6 +81,31 @@ i18nize cs --missing de
|
|
|
76
81
|
|
|
77
82
|
This mode only lists missing keys, no translations are written.
|
|
78
83
|
|
|
84
|
+
### 4. Fill placeholders for blank/missing keys (no DeepL)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Insert placeholders instead of translating
|
|
88
|
+
i18nize cs --only_blank
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This scans all keys in the **source** locale (default `en`) and, for any **missing or blank** value in the target locale, inserts a placeholder:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
<locale>.<full.dot.key> missing
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
en:
|
|
101
|
+
test:
|
|
102
|
+
title: "Testing"
|
|
103
|
+
cs:
|
|
104
|
+
test:
|
|
105
|
+
title: "cs.test.title missing"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Existing non-blank values in the target are left untouched.
|
|
79
109
|
|
|
80
110
|
## 🛡️ Character limit check
|
|
81
111
|
|
|
@@ -132,8 +162,11 @@ For now, configuration is via CLI flags and environment variables:
|
|
|
132
162
|
|
|
133
163
|
* `--from LANG` (default `en`)
|
|
134
164
|
* `--missing [FROM]` (list missing keys only)
|
|
135
|
-
* `
|
|
165
|
+
* `--only_blank` (fill only blank/missing keys with placeholders, no DeepL)
|
|
166
|
+
* `DEEPL_API_KEY` (required for translation mode)
|
|
136
167
|
* `DEEPL_ENDPOINT` (optional, only for Pro)
|
|
168
|
+
* `I18NIZE_ALLOW_EMPTY == "1"` → **skip empty/whitespace-only values** in the source when inserting or translating
|
|
169
|
+
(i.e., if a source value is `""` or `" "` it will be ignored and not propagated to the target)
|
|
137
170
|
|
|
138
171
|
---
|
|
139
172
|
|
|
@@ -142,5 +175,5 @@ For now, configuration is via CLI flags and environment variables:
|
|
|
142
175
|
* RSpec test suite
|
|
143
176
|
* YAML key sorting / format preservation
|
|
144
177
|
* Richer conflict resolution strategies (configurable overwrite / skip)
|
|
145
|
-
* Use custom char limit
|
|
178
|
+
* Use custom char limit
|
|
146
179
|
|
data/lib/i18nize/cli.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
|
-
|
|
5
4
|
require "i18nize/translator"
|
|
6
5
|
require "i18nize/inserter"
|
|
7
6
|
require "i18nize/comparer"
|
|
8
7
|
|
|
9
8
|
module I18nize
|
|
9
|
+
# CLI for i18nize
|
|
10
10
|
class CLI
|
|
11
11
|
def self.run(argv)
|
|
12
12
|
new(argv).run
|
|
@@ -16,107 +16,102 @@ module I18nize
|
|
|
16
16
|
@argv = argv.dup
|
|
17
17
|
@options = {
|
|
18
18
|
from: "en",
|
|
19
|
-
mode: :translate,
|
|
19
|
+
mode: :translate, # :translate | :missing | :only_blank
|
|
20
20
|
missing_from: nil
|
|
21
21
|
}
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def run
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
parse_flags!(@argv[1..] || [])
|
|
29
|
-
|
|
30
|
-
to_locale = parse_positional_locale!(first)
|
|
25
|
+
remaining = parse_flags!(@argv)
|
|
26
|
+
abort usage unless remaining.any?
|
|
27
|
+
to_locale = remaining.shift.strip
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
case @options[:mode]
|
|
30
|
+
when :missing
|
|
33
31
|
from = @options[:missing_from] || @options[:from]
|
|
34
32
|
show_missing_translations(from:, to: to_locale)
|
|
35
|
-
|
|
33
|
+
when :only_blank
|
|
34
|
+
run_only_blank(to_locale)
|
|
35
|
+
else
|
|
36
|
+
run_translation(to_locale)
|
|
36
37
|
end
|
|
37
|
-
|
|
38
|
-
run_translation(to_locale)
|
|
39
38
|
0
|
|
40
39
|
rescue OptionParser::InvalidOption => e
|
|
41
40
|
warn red(e.message)
|
|
42
|
-
|
|
41
|
+
puts usage
|
|
42
|
+
2
|
|
43
43
|
rescue SystemExit => e
|
|
44
44
|
raise e
|
|
45
45
|
rescue => e
|
|
46
46
|
warn red("Error: #{e.class}: #{e.message}")
|
|
47
47
|
e.backtrace.each { |l| warn l } if ENV["I18NIZE_DEBUG"] == "1"
|
|
48
|
-
|
|
48
|
+
1
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
private
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def parse_positional_locale!(arg)
|
|
58
|
-
abort yellow("Usage: i18nize <to_locale> [--from en] [--missing [FROM]]") unless arg
|
|
59
|
-
arg.strip
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def parse_flags!(rest)
|
|
63
|
-
OptionParser.new do |o|
|
|
53
|
+
def parse_flags!(args)
|
|
54
|
+
parser = OptionParser.new do |o|
|
|
64
55
|
o.banner = "Usage: i18nize <to_locale> [options]"
|
|
65
|
-
o.on("--from LANG", "Source language
|
|
66
|
-
|
|
56
|
+
o.on("--from LANG", "Source language (default: en)") { |v| @options[:from] = v }
|
|
67
57
|
o.on("--missing", "--missing [FROM]", "List missing keys only; optionally specify source locale") do |from|
|
|
68
58
|
@options[:mode] = :missing
|
|
69
59
|
@options[:missing_from] = from if from && !from.strip.empty?
|
|
70
60
|
end
|
|
61
|
+
o.on("--only_blank", "Fill only missing/blank keys with '<locale>.<full.key> missing'") do
|
|
62
|
+
@options[:mode] = :only_blank
|
|
63
|
+
end
|
|
64
|
+
o.on("-h", "--help", "Show help") { puts usage; exit 0 }
|
|
65
|
+
end
|
|
71
66
|
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
parser.parse!(args)
|
|
68
|
+
args
|
|
74
69
|
end
|
|
75
70
|
|
|
76
71
|
def show_missing_translations(from:, to:)
|
|
77
72
|
load_rails_if_available
|
|
78
73
|
puts cyan("🔍 Missing keys for #{to} (from #{from})")
|
|
79
|
-
|
|
80
74
|
missing = I18nize::Comparer.missing_translations(from_locale: from, to_locale: to)
|
|
81
|
-
|
|
82
75
|
if missing.empty?
|
|
83
76
|
puts green("✅ No missing translations.")
|
|
84
77
|
return
|
|
85
78
|
end
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
puts yellow("⚠️ #{total_keys} missing #{total_keys == 1 ? 'key' : 'keys'}:")
|
|
89
|
-
|
|
79
|
+
total = missing.total_keys
|
|
80
|
+
puts yellow("⚠️ #{total} missing #{total == 1 ? 'key' : 'keys'}:")
|
|
90
81
|
missing.values_by_file.each do |file, keys_hash|
|
|
91
82
|
puts " #{file}:"
|
|
92
|
-
keys_hash.keys.sort.each
|
|
93
|
-
puts " - #{k.sub(/^#{from}\./, '')}"
|
|
94
|
-
end
|
|
83
|
+
keys_hash.keys.sort.each { |k| puts " - #{k.sub(/^#{Regexp.escape(from)}\./, "")}" }
|
|
95
84
|
end
|
|
96
85
|
end
|
|
97
86
|
|
|
98
|
-
def
|
|
87
|
+
def run_only_blank(to_locale)
|
|
99
88
|
load_env_files
|
|
89
|
+
load_rails_if_available
|
|
90
|
+
puts cyan("Filling only blank/missing keys: from #{@options[:from]} → #{to_locale}")
|
|
91
|
+
I18nize::Inserter.insert_only_blank(
|
|
92
|
+
from_locale: @options[:from],
|
|
93
|
+
to_locale: to_locale
|
|
94
|
+
)
|
|
95
|
+
puts green("✅ Placeholders inserted for blank/missing keys.")
|
|
96
|
+
end
|
|
100
97
|
|
|
98
|
+
def run_translation(to_locale)
|
|
99
|
+
load_env_files
|
|
101
100
|
api_key = ENV["DEEPL_API_KEY"]
|
|
102
101
|
abort red("Missing DEEPL_API_KEY env variable") if api_key.to_s.strip.empty?
|
|
103
|
-
|
|
104
102
|
load_rails_if_available
|
|
105
103
|
|
|
106
104
|
puts cyan("Starting translation: from #{@options[:from]} → #{to_locale}")
|
|
107
|
-
|
|
108
105
|
translator = I18nize::Translator.new(
|
|
109
106
|
auth_key: api_key,
|
|
110
107
|
from_lang: @options[:from].upcase,
|
|
111
108
|
to_lang: to_locale.upcase
|
|
112
109
|
)
|
|
113
|
-
|
|
114
110
|
I18nize::Inserter.insert_missing(
|
|
115
111
|
from_locale: @options[:from],
|
|
116
112
|
to_locale: to_locale,
|
|
117
113
|
translator: translator
|
|
118
114
|
)
|
|
119
|
-
|
|
120
115
|
puts green("✅ Translation completed.")
|
|
121
116
|
end
|
|
122
117
|
|
|
@@ -124,18 +119,37 @@ module I18nize
|
|
|
124
119
|
begin
|
|
125
120
|
require "dotenv/load"
|
|
126
121
|
rescue LoadError
|
|
127
|
-
# Ignore
|
|
128
122
|
end
|
|
129
123
|
end
|
|
130
124
|
|
|
131
125
|
def load_rails_if_available
|
|
132
126
|
return if defined?(Rails)
|
|
133
127
|
|
|
134
|
-
if File.exist?("config/environment.rb")
|
|
135
|
-
|
|
136
|
-
end
|
|
128
|
+
require_relative "../../config/environment" if File.exist?("config/environment.rb")
|
|
129
|
+
|
|
137
130
|
rescue LoadError
|
|
138
|
-
#
|
|
131
|
+
# Ignored
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def usage
|
|
135
|
+
<<~HELP
|
|
136
|
+
Usage:
|
|
137
|
+
i18nize <to_locale> [options]
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
i18nize cs # translate missing from en -> cs
|
|
141
|
+
i18nize cs --from de # translate missing from de -> cs
|
|
142
|
+
i18nize --from de cs # flags can be before or after the locale
|
|
143
|
+
i18nize cs --missing # list missing keys for cs (from en)
|
|
144
|
+
i18nize cs --missing de # list missing keys for cs (from de)
|
|
145
|
+
i18nize cs --only_blank # fill only blank/missing keys with placeholders
|
|
146
|
+
|
|
147
|
+
Options:
|
|
148
|
+
--from LANG Source language (default: en)
|
|
149
|
+
--missing [FROM] Only list missing keys (optional override of source)
|
|
150
|
+
--only_blank Insert '<locale>.<full.key> missing' for blank/missing keys
|
|
151
|
+
-h, --help Show this help
|
|
152
|
+
HELP
|
|
139
153
|
end
|
|
140
154
|
|
|
141
155
|
def color(text, code) = "\e[#{code}m#{text}\e[0m"
|
|
@@ -147,24 +161,5 @@ module I18nize
|
|
|
147
161
|
def yellow(text) = color(text, 33)
|
|
148
162
|
|
|
149
163
|
def cyan(text) = color(text, 36)
|
|
150
|
-
|
|
151
|
-
def print_help_and_exit(code = 0)
|
|
152
|
-
puts <<~HELP
|
|
153
|
-
Usage:
|
|
154
|
-
i18nize <to_locale> [options]
|
|
155
|
-
|
|
156
|
-
Examples:
|
|
157
|
-
i18nize cs # translate missing from en -> cs
|
|
158
|
-
i18nize cs --from de # translate missing from de -> cs
|
|
159
|
-
i18nize cs --missing # list missing keys for cs (from en)
|
|
160
|
-
i18nize cs --missing de # list missing keys for cs (from de)
|
|
161
|
-
|
|
162
|
-
Options:
|
|
163
|
-
--from LANG Source language (default: en)
|
|
164
|
-
--missing [FROM] Only list missing keys (optional override of source)
|
|
165
|
-
-h, --help Show this help
|
|
166
|
-
HELP
|
|
167
|
-
exit code
|
|
168
|
-
end
|
|
169
164
|
end
|
|
170
165
|
end
|
data/lib/i18nize/comparer.rb
CHANGED
|
@@ -4,8 +4,9 @@ require_relative "loader"
|
|
|
4
4
|
require_relative "missing_set"
|
|
5
5
|
|
|
6
6
|
module I18nize
|
|
7
|
+
# Small utility to compare translations from one locale to another
|
|
7
8
|
class Comparer
|
|
8
|
-
def self.missing_translations(from_locale: "en",
|
|
9
|
+
def self.missing_translations(to_locale:, from_locale: "en") # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
9
10
|
from_data = Loader.load_per_file(locale: from_locale)
|
|
10
11
|
to_data = Loader.load_per_file(locale: to_locale)
|
|
11
12
|
|
|
@@ -37,7 +38,7 @@ module I18nize
|
|
|
37
38
|
MissingSet.new(from_locale: from_locale, to_locale: to_locale, data: result)
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
def self.missing_keys(from_locale: "en"
|
|
41
|
+
def self.missing_keys(to_locale:, from_locale: "en")
|
|
41
42
|
missing_translations(from_locale: from_locale, to_locale: to_locale).keys_by_file
|
|
42
43
|
end
|
|
43
44
|
end
|
data/lib/i18nize/inserter.rb
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "byebug"
|
|
5
6
|
require_relative "comparer"
|
|
7
|
+
require_relative "loader"
|
|
6
8
|
|
|
7
9
|
module I18nize
|
|
8
10
|
class Inserter
|
|
9
11
|
CONFLICT_POLICY = :overwrite # :overwrite | :skip
|
|
10
12
|
|
|
11
|
-
def self.insert_missing(from_locale: "en",
|
|
13
|
+
def self.insert_missing(to_locale:, from_locale: "en", base_path: "config/locales", translator: nil)
|
|
12
14
|
missing_data = Comparer.missing_translations(from_locale: from_locale, to_locale: to_locale)
|
|
13
15
|
|
|
14
16
|
if translator
|
|
@@ -26,7 +28,7 @@ module I18nize
|
|
|
26
28
|
overwrites = []
|
|
27
29
|
|
|
28
30
|
missing_data.data.each do |from_rel_path, translations|
|
|
29
|
-
dirname
|
|
31
|
+
dirname = File.dirname(from_rel_path)
|
|
30
32
|
filename = File.basename(from_rel_path)
|
|
31
33
|
|
|
32
34
|
locale_re = /(^|[._])#{Regexp.escape(from_locale)}\.(ya?ml)$/
|
|
@@ -40,7 +42,7 @@ module I18nize
|
|
|
40
42
|
translation_map =
|
|
41
43
|
if translator
|
|
42
44
|
src_values = translations.values
|
|
43
|
-
translated_values = translator.translate_texts(src_values) #
|
|
45
|
+
translated_values = translator.translate_texts(src_values) # returns array
|
|
44
46
|
translations.keys.zip(translated_values).to_h
|
|
45
47
|
else
|
|
46
48
|
translations
|
|
@@ -57,15 +59,70 @@ module I18nize
|
|
|
57
59
|
puts "\e[32m[i18nize] Inserted #{translation_map.size} keys into #{to_full_path}\e[0m"
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
return unless overwrites.any?
|
|
61
63
|
puts "\n\e[33m[i18nize] Overwrote #{overwrites.size} scalar node(s) to create nested structure:\e[0m"
|
|
62
64
|
overwrites.each do |c|
|
|
63
65
|
puts " \e[33m- #{c[:file]}: #{c[:locale]}.#{c[:path].join('.')}\e[0m (was: #{c[:existing_class]})"
|
|
64
66
|
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# NEW: fill only missing/blank values with "<locale>.<full.key> missing"
|
|
71
|
+
def self.insert_only_blank(to_locale:, from_locale: "en", base_path: "config/locales")
|
|
72
|
+
from_data = Loader.load_per_file(locale: from_locale, base_path: base_path)
|
|
73
|
+
# to_data = Loader.load_per_file(locale: to_locale, base_path: base_path)
|
|
74
|
+
|
|
75
|
+
updated_files = 0
|
|
76
|
+
overwrites = []
|
|
77
|
+
|
|
78
|
+
from_data.each do |from_rel_path, from_keys|
|
|
79
|
+
dirname = File.dirname(from_rel_path)
|
|
80
|
+
filename = File.basename(from_rel_path)
|
|
81
|
+
locale_re = /(^|[._])#{Regexp.escape(from_locale)}\.(ya?ml)$/
|
|
82
|
+
new_filename = filename.sub(locale_re) { "#{Regexp.last_match(1)}#{to_locale}.#{Regexp.last_match(2)}" }
|
|
83
|
+
to_rel_path = dirname == "." || dirname.empty? ? new_filename : File.join(dirname, new_filename)
|
|
84
|
+
to_full_path = File.join(base_path, to_rel_path)
|
|
85
|
+
|
|
86
|
+
existing = File.exist?(to_full_path) ? YAML.load_file(to_full_path) : {}
|
|
87
|
+
existing[to_locale] ||= {}
|
|
88
|
+
|
|
89
|
+
changes = 0
|
|
90
|
+
|
|
91
|
+
from_keys.each_key do |from_full_key|
|
|
92
|
+
to_full_key = from_full_key.sub(/^#{Regexp.escape(from_locale)}\./, "#{to_locale}.")
|
|
93
|
+
path = to_full_key.sub(/^#{Regexp.escape(to_locale)}\./, "").split(".")
|
|
94
|
+
current = dig_nested(existing[to_locale], path)
|
|
95
|
+
next if current.to_s.strip.empty? && ENV["I18NIZE_ALLOW_EMPTY"] == "1"
|
|
96
|
+
next unless current.nil? || current.to_s.strip.empty?
|
|
97
|
+
|
|
98
|
+
placeholder = "#{to_full_key} missing"
|
|
99
|
+
insert_nested(existing[to_locale], path, placeholder, overwrites, to_rel_path, to_locale, :overwrite)
|
|
100
|
+
changes += 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
next unless changes.positive?
|
|
104
|
+
|
|
105
|
+
FileUtils.mkdir_p(File.dirname(to_full_path))
|
|
106
|
+
File.write(to_full_path, existing.to_yaml(line_width: -1))
|
|
107
|
+
updated_files += 1
|
|
108
|
+
puts "\e[32m[i18nize] Inserted #{changes} placeholder#{changes == 1 ? '' : 's'} into #{to_full_path}\e[0m"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
puts "\e[90m[i18nize] No blank or missing keys found.\e[0m" if updated_files.zero?
|
|
112
|
+
|
|
113
|
+
return unless overwrites.any?
|
|
114
|
+
|
|
115
|
+
puts "\n\e[33m[i18nize] Overwrote #{overwrites.size} scalar node(s) while creating nested structure:\e[0m"
|
|
116
|
+
overwrites.each do |c|
|
|
117
|
+
puts " \e[33m- #{c[:file]}: #{c[:locale]}.#{c[:path].join('.')}\e[0m (was: #{c[:existing_class]})"
|
|
65
118
|
end
|
|
66
119
|
end
|
|
67
120
|
|
|
68
|
-
|
|
121
|
+
def self.dig_nested(hash, path)
|
|
122
|
+
path.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# conflict_policy: :overwrite (replace scalar with {}), :skip
|
|
69
126
|
def self.insert_nested(base, keys, value, overwrites, file, locale, conflict_policy)
|
|
70
127
|
key = keys.shift
|
|
71
128
|
|
|
@@ -79,18 +136,15 @@ module I18nize
|
|
|
79
136
|
case cur
|
|
80
137
|
when nil
|
|
81
138
|
base[key] = {}
|
|
82
|
-
insert_nested(base[key], keys, value, overwrites, file, locale, conflict_policy)
|
|
139
|
+
return insert_nested(base[key], keys, value, overwrites, file, locale, conflict_policy)
|
|
83
140
|
when Hash
|
|
84
|
-
insert_nested(cur, keys, value, overwrites, file, locale, conflict_policy)
|
|
141
|
+
return insert_nested(cur, keys, value, overwrites, file, locale, conflict_policy)
|
|
85
142
|
else
|
|
86
|
-
|
|
143
|
+
return false unless conflict_policy == :overwrite
|
|
87
144
|
overwrites << { file: file, locale: locale, path: [key, *keys], existing_class: cur.class.to_s }
|
|
88
145
|
base[key] = {}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# :skip
|
|
92
|
-
false
|
|
93
|
-
end
|
|
146
|
+
return insert_nested(base[key], keys, value, overwrites, file, locale, conflict_policy)
|
|
147
|
+
|
|
94
148
|
end
|
|
95
149
|
end
|
|
96
150
|
end
|
data/lib/i18nize/loader.rb
CHANGED
data/lib/i18nize/missing_set.rb
CHANGED
data/lib/i18nize/translator.rb
CHANGED
|
@@ -4,14 +4,15 @@ require "uri"
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
6
|
module I18nize
|
|
7
|
+
# Translator for i18nize
|
|
7
8
|
class Translator
|
|
8
9
|
ENDPOINT = ENV.fetch("DEEPL_ENDPOINT", "https://api-free.deepl.com/v2/translate")
|
|
9
10
|
PLACEHOLDER_RE = /%{\s*[^}]+\s*}/
|
|
10
11
|
|
|
11
12
|
def initialize(auth_key:, from_lang: "EN", to_lang:)
|
|
12
|
-
@auth_key
|
|
13
|
+
@auth_key = auth_key
|
|
13
14
|
@from_lang = from_lang
|
|
14
|
-
@to_lang
|
|
15
|
+
@to_lang = to_lang
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def translate_texts(texts)
|
|
@@ -30,7 +31,7 @@ module I18nize
|
|
|
30
31
|
body_params = [
|
|
31
32
|
["auth_key", @auth_key],
|
|
32
33
|
["source_lang", @from_lang],
|
|
33
|
-
["target_lang", @to_lang]
|
|
34
|
+
["target_lang", @to_lang]
|
|
34
35
|
] + protected.map { |text| ["text", text] }
|
|
35
36
|
|
|
36
37
|
req = Net::HTTP::Post.new(uri)
|
|
@@ -43,7 +44,7 @@ module I18nize
|
|
|
43
44
|
json = JSON.parse(res.body)
|
|
44
45
|
out = json["translations"].map { |t| t["text"] }
|
|
45
46
|
|
|
46
|
-
out.map { |s| s.gsub(/__I18NIZE_PLH_(\d+)__/) { placeholders[
|
|
47
|
+
out.map { |s| s.gsub(/__I18NIZE_PLH_(\d+)__/) { placeholders[::Regexp.last_match(1).to_i] } }
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
end
|
data/lib/i18nize/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: i18nize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.6'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nikolas2145
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-09-
|
|
11
|
+
date: 2025-09-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: "i18nize helps Ruby and Rails projects stay multilingual by automatically
|
|
14
14
|
filling in \nmissing translations in I18n YAML locale files. It scans for missing
|