better_translate 1.0.0 → 1.0.0.1

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: 6a19ffdafe02b71cad6e1d93eba868299d8551adc3e7952450c709c3f71d1292
4
- data.tar.gz: 7c5d7dde1f0b120124d13f75c31637b2a304845955a2974fc7f0c2f58eec8e76
3
+ metadata.gz: b02561def13795980279169998c3a28170f0c2b1c4c7e4ffc58ae61b20b98ff5
4
+ data.tar.gz: d44be305f231f038918bb8dbf722db94328d9d9e71f1bcdd8bbe32aefceeb2f1
5
5
  SHA512:
6
- metadata.gz: a0c9c62cc2a35a0663d79af39c8e4a87cd86106b906b185c805f4ff9cba2bdfa82c1eaf8c366b5e91e70fe0c5ea87d5da852e03c32ae2a3959ae32ef2318d62a
7
- data.tar.gz: 6fb2cac27269257f878d3055027213dc2bef5d75332132f559493064a4c83f1360903080571a3fd1e71c7c6feec73cf41ff619d8ecc8da180246779b5e893e59
6
+ metadata.gz: 98e5a8fb7c3103220b6b2102a2242a8bb4d103a8e3b2df24298e3fa4a3f8e75dae3b48ebbb578a59de84e7c547f3bc12cab71a77c789aa9ab347b7d424aea113
7
+ data.tar.gz: 6aeaafff6b3ff756c620e229d93582ab64a48dca322d21b32d5d38a408e5f6d085ef5ba280da141d0c65b02ca54c3b5559bda29e204aff81b0a6562fac0b860f
data/README.md CHANGED
@@ -685,7 +685,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/alessi
685
685
  5. **HTTP Client**: Use Faraday for all HTTP requests (never Net::HTTP or HTTParty)
686
686
  6. **VCR Cassettes**: Record integration tests with real API responses for CI/CD
687
687
 
688
- ### Workflow
688
+ ### Development Workflow
689
689
 
690
690
  ```bash
691
691
  # 1. Clone and setup
@@ -714,6 +714,33 @@ git push origin my-feature
714
714
  # 7. Create a Pull Request
715
715
  ```
716
716
 
717
+ ### Release Workflow
718
+
719
+ Releases are automated via GitHub Actions:
720
+
721
+ ```bash
722
+ # 1. Update version
723
+ vim lib/better_translate/version.rb # VERSION = "1.0.1"
724
+
725
+ # 2. Update CHANGELOG
726
+ vim CHANGELOG.md
727
+
728
+ # 3. Commit and tag
729
+ git add -A
730
+ git commit -m "chore: Release v1.0.1"
731
+ git tag v1.0.1
732
+ git push origin main
733
+ git push origin v1.0.1
734
+
735
+ # 4. GitHub Actions automatically:
736
+ # ✅ Runs tests
737
+ # ✅ Builds gem
738
+ # ✅ Publishes to RubyGems.org
739
+ # ✅ Creates GitHub Release
740
+ ```
741
+
742
+ **Setup**: See [`.github/RUBYGEMS_SETUP.md`](.github/RUBYGEMS_SETUP.md) for configuring RubyGems trusted publishing (no API keys needed!).
743
+
717
744
  ## 📄 License
718
745
 
719
746
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,240 @@
1
+ # BetterTranslate v1.0.0 - Complete Release 🎉
2
+
3
+ **AI-powered YAML locale file translator for Rails and Ruby projects**
4
+
5
+ This is the first complete release of BetterTranslate, featuring multi-provider AI translation support, comprehensive testing infrastructure, and production-ready features.
6
+
7
+ ## 🚀 Quick Start
8
+
9
+ ```bash
10
+ # Install the gem
11
+ gem install better_translate
12
+
13
+ # Try the interactive demo
14
+ git clone https://github.com/alessiobussolari/better_translate.git
15
+ cd better_translate
16
+ bundle install
17
+ export OPENAI_API_KEY=your_key_here
18
+ ruby spec/dummy/demo_translation.rb
19
+ ```
20
+
21
+ ## ✨ Key Features
22
+
23
+ ### Multi-Provider AI Support
24
+ - **ChatGPT** (GPT-5-nano) - Fast, excellent quality
25
+ - **Google Gemini** (gemini-2.0-flash-exp) - Very fast, cost-effective
26
+ - **Anthropic Claude** (Claude 3.5) - Excellent quality (coming soon)
27
+
28
+ ### Translation Features
29
+ - 🎯 **Smart Strategies**: Automatic selection between deep (<50 strings) and batch (≥50 strings) translation
30
+ - ⚡ **Intelligent Caching**: LRU cache with optional TTL reduces API costs
31
+ - 🔄 **Translation Modes**: Override (replace all) or Incremental (merge with existing)
32
+ - 🚫 **Flexible Exclusions**: Global + language-specific exclusion rules
33
+ - 🎨 **Domain Context**: Provide context for medical, legal, financial, or technical terminology
34
+ - 📊 **Similarity Analysis**: Built-in Levenshtein distance analyzer
35
+
36
+ ### Production-Ready Quality
37
+ - 🧪 **259 Tests** passing with 90%+ coverage
38
+ - 🎬 **VCR Integration**: 18 cassettes (260KB) with real API responses
39
+ - 🏗️ **Rails Dummy App**: Interactive demo with real translations
40
+ - 📚 **Complete Documentation**: README, YARD docs, usage guides
41
+ - 🛡️ **Type-Safe**: RBS signatures with Steep type checking
42
+ - ✅ **RuboCop Compliant**: Clean, consistent code style
43
+
44
+ ### Rails Integration
45
+ ```bash
46
+ # Generate initializer
47
+ rails generate better_translate:install
48
+
49
+ # Configure in config/initializers/better_translate.rb
50
+ BetterTranslate.configure do |config|
51
+ config.provider = :chatgpt
52
+ config.openai_key = ENV["OPENAI_API_KEY"]
53
+ config.source_language = "en"
54
+ config.target_languages = [
55
+ { short_name: "it", name: "Italian" },
56
+ { short_name: "fr", name: "French" }
57
+ ]
58
+ config.input_file = "config/locales/en.yml"
59
+ config.output_folder = "config/locales"
60
+ end
61
+
62
+ # Translate all files
63
+ BetterTranslate.translate_all
64
+ ```
65
+
66
+ ## 📖 Documentation
67
+
68
+ - **[README.md](README.md)** - Complete feature documentation
69
+ - **[USAGE_GUIDE.md](spec/dummy/USAGE_GUIDE.md)** - Interactive demo guide
70
+ - **[VCR Testing Guide](spec/integration/README.md)** - Integration testing with VCR
71
+ - **[CLAUDE.md](CLAUDE.md)** - Developer guide for contributors
72
+ - **[CHANGELOG.md](CHANGELOG.md)** - Detailed changelog
73
+
74
+ ## 🎬 Demo Output
75
+
76
+ ```yaml
77
+ # en.yml (input)
78
+ en:
79
+ hello: "Hello"
80
+ users:
81
+ greeting: "Hello %{name}"
82
+ messages:
83
+ success: "Operation completed successfully"
84
+
85
+ # it.yml (generated by BetterTranslate)
86
+ it:
87
+ hello: "Ciao"
88
+ users:
89
+ greeting: "Ciao %{name}" # ✅ Variables preserved!
90
+ messages:
91
+ success: "Operazione completata con successo"
92
+
93
+ # fr.yml (generated by BetterTranslate)
94
+ fr:
95
+ hello: "Bonjour"
96
+ users:
97
+ greeting: "Bonjour %{name}" # ✅ Variables preserved!
98
+ messages:
99
+ success: "Opération réussie"
100
+ ```
101
+
102
+ ## 🏗️ Architecture Highlights
103
+
104
+ ### Provider Architecture
105
+ ```
106
+ BaseHttpProvider (abstract)
107
+ ├── ChatGPTProvider (GPT-5-nano, temp=1.0)
108
+ ├── GeminiProvider (gemini-2.0-flash-exp)
109
+ └── AnthropicProvider (coming soon)
110
+ ```
111
+
112
+ **BaseHttpProvider features:**
113
+ - Faraday-based HTTP communication
114
+ - Retry logic with exponential backoff (3 attempts)
115
+ - Rate limiting (0.5s between requests, thread-safe)
116
+ - Configurable timeouts (default: 30s)
117
+
118
+ ### Core Components
119
+ - **Configuration**: Type-safe config with validation
120
+ - **Cache**: Thread-safe LRU cache with optional TTL
121
+ - **RateLimiter**: Thread-safe request throttling
122
+ - **Validator**: Comprehensive input validation
123
+ - **HashFlattener**: Nested YAML ↔ flat structure conversion
124
+ - **VariableExtractor**: Preserves `%{name}` and `%<name>s` placeholders
125
+ - **ProgressTracker**: Real-time translation progress
126
+
127
+ ### Error Hierarchy
128
+ ```
129
+ BetterTranslate::Error (base)
130
+ ├── ConfigurationError
131
+ ├── ValidationError
132
+ ├── TranslationError
133
+ ├── ProviderError
134
+ ├── ApiError
135
+ ├── RateLimitError
136
+ ├── FileError
137
+ ├── YamlError
138
+ └── ProviderNotFoundError
139
+ ```
140
+
141
+ ## 🧪 Testing Infrastructure
142
+
143
+ ```
144
+ spec/
145
+ ├── better_translate/ # 259 unit tests
146
+ │ ├── cache_spec.rb
147
+ │ ├── providers/
148
+ │ └── ...
149
+ ├── integration/ # VCR integration tests
150
+ │ ├── chatgpt_integration_spec.rb
151
+ │ ├── gemini_integration_spec.rb
152
+ │ └── rails_dummy_app_spec.rb
153
+ ├── dummy/ # Rails dummy app
154
+ │ ├── demo_translation.rb # 🚀 Interactive demo
155
+ │ └── config/locales/
156
+ │ ├── en.yml # Source (16 keys)
157
+ │ ├── it.yml # Generated (519 bytes)
158
+ │ └── fr.yml # Generated (511 bytes)
159
+ └── vcr_cassettes/ # 18 cassettes (260KB)
160
+ ├── chatgpt/ (7)
161
+ ├── gemini/ (7)
162
+ └── rails/ (4)
163
+ ```
164
+
165
+ ## 📦 Package Details
166
+
167
+ - **Gem Name**: `better_translate`
168
+ - **Version**: `1.0.0`
169
+ - **Size**: 83KB
170
+ - **Files**: 152 files
171
+ - **Lines of Code**: 23,654
172
+ - **Ruby Version**: >= 3.0.0
173
+ - **Dependencies**: Faraday ~> 2.0 (only runtime dependency)
174
+
175
+ ## 🔧 Configuration Examples
176
+
177
+ ### Medical Terminology
178
+ ```ruby
179
+ config.translation_context = "Medical terminology for healthcare applications"
180
+ config.target_languages = [{ short_name: "es", name: "Spanish" }]
181
+ # "patient" → "paciente", "diagnosis" → "diagnóstico"
182
+ ```
183
+
184
+ ### E-commerce with Exclusions
185
+ ```ruby
186
+ config.global_exclusions = ["brand_name", "sku"] # Never translate
187
+ config.exclusions_per_language = {
188
+ "de" => ["legal.terms"], # German legal terms manually translated
189
+ "fr" => ["marketing.slogan"] # French slogan crafted by marketing
190
+ }
191
+ ```
192
+
193
+ ### Incremental Mode
194
+ ```ruby
195
+ config.translation_mode = :incremental
196
+ # Only translates missing keys, preserves manual corrections
197
+ ```
198
+
199
+ ## 🚦 Performance
200
+
201
+ **Demo Results** (16 keys, 2 languages):
202
+ - Italian: 16 strings in 1m 6s (~4s per string)
203
+ - French: 16 strings in 1m 7s (~4s per string)
204
+ - **Total**: 32 translations in 2m 13s
205
+
206
+ **With Caching Enabled**:
207
+ - Subsequent runs: < 1s (cache hits)
208
+ - API costs reduced by ~90%
209
+
210
+ ## 🤝 Contributing
211
+
212
+ Contributions are welcome! Please see [CLAUDE.md](CLAUDE.md) for development guidelines.
213
+
214
+ **Development Requirements**:
215
+ 1. TDD (Test-Driven Development) - Write tests first
216
+ 2. YARD Documentation - Document all public methods
217
+ 3. RuboCop Compliance - Clean code style
218
+ 4. VCR Cassettes - Record integration tests
219
+
220
+ ## 📜 License
221
+
222
+ MIT License - See [LICENSE.txt](LICENSE.txt)
223
+
224
+ ## 🙏 Acknowledgments
225
+
226
+ Built with:
227
+ - **Faraday** - HTTP client
228
+ - **VCR** - HTTP interaction recording
229
+ - **RSpec** - Testing framework
230
+ - **RuboCop** - Code linting
231
+ - **Steep** - Type checking
232
+ - **YARD** - Documentation
233
+
234
+ ---
235
+
236
+ **Made with ❤️ by [Alessio Bussolari](https://github.com/alessiobussolari)**
237
+
238
+ **Powered by Claude Code** 🤖
239
+
240
+ [Report Bug](https://github.com/alessiobussolari/better_translate/issues) · [Request Feature](https://github.com/alessiobussolari/better_translate/issues) · [Documentation](https://github.com/alessiobussolari/better_translate)
data/Steepfile CHANGED
@@ -6,8 +6,8 @@
6
6
  D = Steep::Diagnostic
7
7
 
8
8
  target :lib do
9
- # Specify Ruby version
10
- check "lib"
9
+ # Only check library files
10
+ check "lib/**/*.rb"
11
11
 
12
12
  # Signature files location
13
13
  signature "sig"
@@ -14,7 +14,7 @@ BetterTranslate is a powerful Ruby gem designed to automatically translate YAML
14
14
 
15
15
  ### Key Features
16
16
 
17
- - **Multiple AI Providers**: ChatGPT (GPT-5-nano), Google Gemini (gemini-2.0-flash-exp), Anthropic Claude (claude-3-5-sonnet-20241022)
17
+ - **Multiple AI Providers**: ChatGPT (GPT-5-nano), Google Gemini (gemini-2.5-flash-lite), Anthropic Claude (claude-haiku-4-5)
18
18
  - **Smart Translation Strategies**: Automatic selection between Deep (< 50 strings) and Batch (≥ 50 strings) processing
19
19
  - **Intelligent Caching**: LRU cache with configurable capacity and TTL
20
20
  - **Rails Integration**: 3 generators (install, translate, analyze)
@@ -331,10 +331,10 @@ module BetterTranslate
331
331
  module Providers
332
332
  # Google Gemini translation provider
333
333
  #
334
- # Uses gemini-2.0-flash-exp model
334
+ # Uses gemini-2.5-flash-lite model
335
335
  class GeminiProvider < BaseHttpProvider
336
- API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent"
337
- MODEL = "gemini-2.0-flash-exp"
336
+ API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent"
337
+ MODEL = "gemini-2.5-flash-lite"
338
338
 
339
339
  # Translate a single text
340
340
  #
@@ -431,10 +431,10 @@ module BetterTranslate
431
431
  module Providers
432
432
  # Anthropic Claude translation provider
433
433
  #
434
- # Uses claude-3-5-sonnet-20241022 model
434
+ # Uses claude-haiku-4-5 model
435
435
  class AnthropicProvider < BaseHttpProvider
436
436
  API_URL = "https://api.anthropic.com/v1/messages"
437
- MODEL = "claude-3-5-sonnet-20241022"
437
+ MODEL = "claude-haiku-4-5"
438
438
  API_VERSION = "2023-06-01"
439
439
 
440
440
  # Translate a single text
@@ -52,7 +52,8 @@ module BetterTranslate
52
52
  entry = @cache[key]
53
53
 
54
54
  # Check TTL
55
- if @ttl && entry && Time.now - entry[:timestamp] > @ttl
55
+ ttl_value = @ttl
56
+ if ttl_value && entry && Time.now - entry[:timestamp] > ttl_value
56
57
  @cache.delete(key)
57
58
  return nil
58
59
  end
@@ -221,8 +221,8 @@ module BetterTranslate
221
221
  "dry_run" => false,
222
222
  "translation_mode" => "override",
223
223
  "preserve_variables" => true,
224
- "global_exclusions" => Array.new,
225
- "exclusions_per_language" => Hash.new,
224
+ "global_exclusions" => [],
225
+ "exclusions_per_language" => {},
226
226
  "model" => nil,
227
227
  "temperature" => 0.3,
228
228
  "max_tokens" => 2000,
@@ -4,7 +4,7 @@ module BetterTranslate
4
4
  module Providers
5
5
  # Anthropic Claude translation provider
6
6
  #
7
- # Uses claude-3-5-sonnet-20241022 model for high-quality translations.
7
+ # Uses claude-haiku-4-5 model for fast, efficient translations.
8
8
  #
9
9
  # @example Basic usage
10
10
  # config = Configuration.new
@@ -18,7 +18,7 @@ module BetterTranslate
18
18
  API_URL = "https://api.anthropic.com/v1/messages"
19
19
 
20
20
  # Model to use for translations
21
- MODEL = "claude-3-5-sonnet-20241022"
21
+ MODEL = "claude-haiku-4-5"
22
22
 
23
23
  # API version
24
24
  API_VERSION = "2023-06-01"
@@ -97,7 +97,8 @@ module BetterTranslate
97
97
  #
98
98
  def build_system_message(target_lang_name)
99
99
  base_message = "You are a professional translator. Translate the following text to #{target_lang_name}. " \
100
- "Return ONLY the translated text, without any explanations or additional text."
100
+ "Return ONLY the translated text, without any explanations or additional text. " \
101
+ "Words like VARIABLE_0, VARIABLE_1, etc. are placeholders and must be kept unchanged in the translation."
101
102
 
102
103
  if config.translation_context && !config.translation_context.empty?
103
104
  base_message += "\n\nContext: #{config.translation_context}"
@@ -95,7 +95,8 @@ module BetterTranslate
95
95
  #
96
96
  def build_system_message(target_lang_name)
97
97
  base_message = "You are a professional translator. Translate the following text to #{target_lang_name}. " \
98
- "Return ONLY the translated text, without any explanations or additional text."
98
+ "Return ONLY the translated text, without any explanations or additional text. " \
99
+ "Words like VARIABLE_0, VARIABLE_1, etc. are placeholders and must be kept unchanged in the translation."
99
100
 
100
101
  if config.translation_context && !config.translation_context.empty?
101
102
  base_message += "\n\nContext: #{config.translation_context}"
@@ -4,7 +4,7 @@ module BetterTranslate
4
4
  module Providers
5
5
  # Google Gemini translation provider
6
6
  #
7
- # Uses gemini-2.0-flash-exp model for fast, high-quality translations.
7
+ # Uses gemini-2.5-flash-lite model for fast, high-quality translations.
8
8
  #
9
9
  # @example Basic usage
10
10
  # config = Configuration.new
@@ -15,10 +15,10 @@ module BetterTranslate
15
15
  #
16
16
  class GeminiProvider < BaseHttpProvider
17
17
  # Google Gemini API endpoint
18
- API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent"
18
+ API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent"
19
19
 
20
20
  # Model to use for translations
21
- MODEL = "gemini-2.0-flash-exp"
21
+ MODEL = "gemini-2.5-flash-lite"
22
22
 
23
23
  # Translate a single text
24
24
  #
@@ -77,7 +77,8 @@ module BetterTranslate
77
77
  #
78
78
  def build_prompt(text, target_lang_name)
79
79
  base_prompt = "Translate the following text to #{target_lang_name}. " \
80
- "Return ONLY the translated text, without any explanations.\n\n" \
80
+ "Return ONLY the translated text, without any explanations. " \
81
+ "Words like VARIABLE_0, VARIABLE_1, etc. are placeholders and must be kept unchanged in the translation.\n\n" \
81
82
  "Text: #{text}"
82
83
 
83
84
  if config.translation_context && !config.translation_context.empty?
@@ -11,7 +11,8 @@ module BetterTranslate
11
11
  #
12
12
  class Railtie < Rails::Railtie
13
13
  rake_tasks do
14
- rake_file = File.expand_path("../tasks/better_translate.rake", __dir__)
14
+ dir = __dir__
15
+ rake_file = File.expand_path("../tasks/better_translate.rake", dir) if dir
15
16
  load rake_file if rake_file
16
17
  end
17
18
  end
@@ -52,7 +52,10 @@ module BetterTranslate
52
52
  @mutex.synchronize do
53
53
  return if @last_request_time.nil?
54
54
 
55
- elapsed = Time.now - @last_request_time
55
+ last_time = @last_request_time
56
+ return unless last_time
57
+
58
+ elapsed = Time.now - last_time
56
59
  sleep_time = @delay - elapsed.to_f
57
60
 
58
61
  sleep(sleep_time) if sleep_time.positive?
@@ -37,7 +37,7 @@ module BetterTranslate
37
37
  progress_tracker.update(
38
38
  language: target_lang_name,
39
39
  current_key: "Batch #{batch_index + 1}/#{total_batches}",
40
- progress: ((batch_index + 1).to_f / total_batches * 100.0).round(1)
40
+ progress: ((batch_index + 1).to_f / total_batches * 100.0).round(1).to_f
41
41
  )
42
42
 
43
43
  translated_batch = provider.translate_batch(batch, target_lang_code, target_lang_name)
@@ -32,7 +32,7 @@ module BetterTranslate
32
32
  progress_tracker.update(
33
33
  language: target_lang_name,
34
34
  current_key: key,
35
- progress: ((index + 1).to_f / total * 100.0).round(1)
35
+ progress: ((index + 1).to_f / total * 100.0).round(1).to_f
36
36
  )
37
37
 
38
38
  translated[key] = provider.translate_text(value, target_lang_code, target_lang_name)
@@ -62,7 +62,11 @@ module BetterTranslate
62
62
  rescue StandardError => e
63
63
  results[:failure_count] += 1
64
64
  # @type var error_context: Hash[Symbol, untyped]
65
- error_context = e.respond_to?(:context) ? e.context : {}
65
+ error_context = if e.is_a?(BetterTranslate::Error)
66
+ e.context
67
+ else
68
+ {}
69
+ end
66
70
  results[:errors] << {
67
71
  language: lang[:name],
68
72
  error: e.message,
@@ -47,7 +47,7 @@ module BetterTranslate
47
47
  # #=> { "config/database/host" => "localhost" }
48
48
  #
49
49
  def self.flatten(hash, parent_key = "", separator = ".")
50
- initial_hash = {} #: Hash[String, untyped]
50
+ initial_hash = {} # : Hash[String, untyped]
51
51
  hash.each_with_object(initial_hash) do |(key, value), result|
52
52
  new_key = parent_key.empty? ? key.to_s : "#{parent_key}#{separator}#{key}"
53
53
 
@@ -85,7 +85,7 @@ module BetterTranslate
85
85
  # #=> { "config" => { "database" => { "host" => "localhost" } } }
86
86
  #
87
87
  def self.unflatten(hash, separator = ".")
88
- initial_hash = {} #: Hash[String, untyped]
88
+ initial_hash = {} # : Hash[String, untyped]
89
89
  hash.each_with_object(initial_hash) do |(key, value), result|
90
90
  keys = key.split(separator)
91
91
  last_key = keys.pop
@@ -15,9 +15,9 @@ module BetterTranslate
15
15
  # @example Basic usage
16
16
  # extractor = VariableExtractor.new("Hello %{name}, you have {{count}} messages")
17
17
  # safe_text = extractor.extract
18
- # #=> "Hello __VAR_0__, you have __VAR_1__ messages"
18
+ # #=> "Hello VARIABLE_0, you have VARIABLE_1 messages"
19
19
  #
20
- # translated = translate(safe_text) # "Ciao __VAR_0__, hai __VAR_1__ messaggi"
20
+ # translated = translate(safe_text) # "Ciao VARIABLE_0, hai VARIABLE_1 messaggi"
21
21
  # final = extractor.restore(translated)
22
22
  # #=> "Ciao %{name}, hai {{count}} messaggi"
23
23
  #
@@ -41,10 +41,10 @@ module BetterTranslate
41
41
  COMBINED_PATTERN = Regexp.union(*VARIABLE_PATTERNS.values).freeze
42
42
 
43
43
  # Placeholder prefix
44
- PLACEHOLDER_PREFIX = "__VAR_"
44
+ PLACEHOLDER_PREFIX = "VARIABLE_"
45
45
 
46
46
  # Placeholder suffix
47
- PLACEHOLDER_SUFFIX = "__"
47
+ PLACEHOLDER_SUFFIX = ""
48
48
 
49
49
  # @return [String] Original text with variables
50
50
  attr_reader :original_text
@@ -72,13 +72,13 @@ module BetterTranslate
72
72
  # Extract variables and replace with placeholders
73
73
  #
74
74
  # Scans the text for all supported variable formats and replaces them
75
- # with numbered placeholders (__VAR_0__, __VAR_1__, etc.).
75
+ # with numbered placeholders (VARIABLE_0, VARIABLE_1, etc.).
76
76
  #
77
77
  # @return [String] Text with variables replaced by placeholders
78
78
  #
79
79
  # @example
80
80
  # extractor = VariableExtractor.new("Hello %{name}")
81
- # extractor.extract #=> "Hello __VAR_0__"
81
+ # extractor.extract #=> "Hello VARIABLE_0"
82
82
  #
83
83
  def extract
84
84
  return "" if original_text.nil? || original_text.empty?
@@ -112,7 +112,7 @@ module BetterTranslate
112
112
  # @example Successful restore
113
113
  # extractor = VariableExtractor.new("Hello %{name}")
114
114
  # extractor.extract
115
- # extractor.restore("Ciao __VAR_0__") #=> "Ciao %{name}"
115
+ # extractor.restore("Ciao VARIABLE_0") #=> "Ciao %{name}"
116
116
  #
117
117
  # @example Strict mode with missing variable
118
118
  # extractor = VariableExtractor.new("Hello %{name}")
@@ -2,5 +2,5 @@
2
2
 
3
3
  module BetterTranslate
4
4
  # Current version of BetterTranslate gem
5
- VERSION = "1.0.0"
5
+ VERSION = "1.0.0.1"
6
6
  end
@@ -12,7 +12,8 @@ module BetterTranslate
12
12
  # rails generate better_translate:analyze config/locales/en.yml
13
13
  #
14
14
  class AnalyzeGenerator < Rails::Generators::Base
15
- source_root File.expand_path("templates", __dir__)
15
+ dir = __dir__
16
+ source_root File.expand_path("templates", dir) if dir
16
17
 
17
18
  desc "Analyze YAML locale file structure and statistics"
18
19
 
@@ -12,7 +12,8 @@ module BetterTranslate
12
12
  # rails generate better_translate:install
13
13
  #
14
14
  class InstallGenerator < Rails::Generators::Base
15
- source_root File.expand_path("templates", __dir__)
15
+ dir = __dir__
16
+ source_root File.expand_path("templates", dir) if dir
16
17
 
17
18
  desc "Creates BetterTranslate initializer and config files"
18
19
 
@@ -46,8 +47,8 @@ module BetterTranslate
46
47
  "dry_run" => false,
47
48
  "translation_mode" => "override",
48
49
  "preserve_variables" => true,
49
- "global_exclusions" => Array.new,
50
- "exclusions_per_language" => Hash.new,
50
+ "global_exclusions" => [],
51
+ "exclusions_per_language" => {},
51
52
  "model" => nil,
52
53
  "temperature" => 0.3,
53
54
  "max_tokens" => 2000,
@@ -14,15 +14,47 @@ BetterTranslate.configure do |config|
14
14
  config.anthropic_key = ENV["ANTHROPIC_API_KEY"]
15
15
 
16
16
  # Source and target languages
17
- config.source_language = "en"
18
- config.target_languages = [
19
- { short_name: "it", name: "Italian" },
20
- { short_name: "es", name: "Spanish" },
21
- { short_name: "fr", name: "French" }
22
- ]
17
+ # Automatically uses Rails I18n configuration
18
+ # To configure I18n in your Rails app, set in config/application.rb:
19
+ # config.i18n.default_locale = :it
20
+ # config.i18n.available_locales = [:it, :en, :es, :fr]
21
+ config.source_language = I18n.default_locale.to_s
22
+
23
+ # Target languages: automatically derived from I18n.available_locales
24
+ # Excludes the source language from targets
25
+ available_targets = (I18n.available_locales - [I18n.default_locale]).map(&:to_s)
26
+
27
+ # Language name mapping for common languages
28
+ language_names = {
29
+ "en" => "English", "it" => "Italian", "es" => "Spanish", "fr" => "French",
30
+ "de" => "German", "pt" => "Portuguese", "ru" => "Russian", "zh" => "Chinese",
31
+ "ja" => "Japanese", "ko" => "Korean", "ar" => "Arabic", "nl" => "Dutch",
32
+ "pl" => "Polish", "tr" => "Turkish", "sv" => "Swedish", "da" => "Danish",
33
+ "fi" => "Finnish", "no" => "Norwegian", "cs" => "Czech", "el" => "Greek",
34
+ "he" => "Hebrew", "hi" => "Hindi", "th" => "Thai", "vi" => "Vietnamese"
35
+ }
36
+
37
+ if available_targets.any?
38
+ # Use I18n available locales
39
+ config.target_languages = available_targets.map do |locale|
40
+ {
41
+ short_name: locale,
42
+ name: language_names[locale] || locale.capitalize
43
+ }
44
+ end
45
+ else
46
+ # Fallback: suggest common languages
47
+ # Uncomment and modify the languages you want to translate to
48
+ config.target_languages = [
49
+ { short_name: "it", name: "Italian" },
50
+ { short_name: "es", name: "Spanish" },
51
+ { short_name: "fr", name: "French" }
52
+ ]
53
+ end
23
54
 
24
55
  # File paths
25
- config.input_file = Rails.root.join("config", "locales", "en.yml").to_s
56
+ # Uses source_language for input file
57
+ config.input_file = Rails.root.join("config", "locales", "#{config.source_language}.yml").to_s
26
58
  config.output_folder = Rails.root.join("config", "locales").to_s
27
59
 
28
60
  # Options
@@ -15,7 +15,8 @@ module BetterTranslate
15
15
  # rails generate better_translate:translate --dry-run
16
16
  #
17
17
  class TranslateGenerator < Rails::Generators::Base
18
- source_root File.expand_path("templates", __dir__)
18
+ dir = __dir__
19
+ source_root File.expand_path("templates", dir) if dir
19
20
 
20
21
  desc "Run BetterTranslate translation task"
21
22
 
data/regenerate_vcr.rb ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "dotenv/load"
6
+ require "vcr"
7
+ require "webmock/rspec"
8
+ require_relative "lib/better_translate"
9
+ require "tmpdir"
10
+
11
+ # Setup VCR
12
+ VCR.configure do |config|
13
+ config.cassette_library_dir = "spec/vcr_cassettes"
14
+ config.hook_into :webmock
15
+ config.filter_sensitive_data("<GEMINI_API_KEY>") { ENV["GEMINI_API_KEY"] }
16
+ config.default_cassette_options = { record: :all }
17
+ end
18
+
19
+ test_dir = Dir.mktmpdir("gemini_test")
20
+ puts "Test output dir: #{test_dir}"
21
+
22
+ VCR.use_cassette("rails/dummy_app_gemini_translation", record: :all) do
23
+ config = BetterTranslate::Configuration.new
24
+ config.provider = :gemini
25
+ config.gemini_key = ENV["GEMINI_API_KEY"]
26
+ config.source_language = "en"
27
+ config.target_languages = [{ short_name: "fr", name: "French" }]
28
+ config.input_file = "spec/dummy/config/locales/en.yml"
29
+ config.output_folder = test_dir
30
+ config.cache_enabled = false
31
+ config.verbose = true
32
+ config.validate!
33
+
34
+ puts "Starting translation..."
35
+ translator = BetterTranslate::Translator.new(config)
36
+ results = translator.translate_all
37
+
38
+ puts "\nResults: #{results.inspect}"
39
+ puts "\nFiles created:"
40
+ Dir.entries(test_dir).each { |f| puts " - #{f}" unless f.start_with?(".") }
41
+
42
+ if results[:success_count].positive?
43
+ puts "\n✓ Successfully regenerated VCR cassette!"
44
+ else
45
+ puts "\n✗ Translation failed"
46
+ end
47
+ end
@@ -31,8 +31,13 @@ module BetterTranslate
31
31
  attr_accessor provider: (Symbol | nil)
32
32
  attr_accessor openai_key: String?
33
33
  attr_accessor google_gemini_key: String?
34
- attr_accessor gemini_key: String?
35
- attr_accessor anthropic_key: String?
34
+ attr_accessor claude_key: String?
35
+
36
+ # Aliases (these are methods created by alias in configuration.rb)
37
+ alias gemini_key google_gemini_key
38
+ alias gemini_key= google_gemini_key=
39
+ alias anthropic_key claude_key
40
+ alias anthropic_key= claude_key=
36
41
  attr_accessor source_language: String?
37
42
  attr_accessor target_languages: Array[target_language]
38
43
  attr_accessor input_file: String?
@@ -36,7 +36,7 @@ module BetterTranslate
36
36
 
37
37
  def http_client: () -> Faraday::Connection
38
38
 
39
- def with_cache: [T] (String cache_key) { () -> T } -> T
39
+ def with_cache: (String cache_key) { () -> String } -> String
40
40
 
41
41
  def build_cache_key: (String text, String target_lang_code) -> String
42
42
  end
@@ -33,7 +33,7 @@ module BetterTranslate
33
33
 
34
34
  def validate_variables!: (String text) -> true
35
35
 
36
- def self.find_variables: (String? text) -> Array[String]
36
+ def self.find_variables: (String? text) -> Array[String | Array[String?]]
37
37
 
38
38
  def self.contains_variables?: (String? text) -> bool
39
39
  end
@@ -5,7 +5,8 @@
5
5
  module BetterTranslate
6
6
  type translation_results = { success_count: Integer, failure_count: Integer, errors: Array[Hash[Symbol, untyped]] }
7
7
 
8
- @configuration: Configuration?
8
+ # Module-level instance variable (for singleton methods)
9
+ self.@configuration: Configuration?
9
10
 
10
11
  # Configure BetterTranslate
11
12
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: better_translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - alessiobussolari
@@ -42,6 +42,7 @@ files:
42
42
  - CODE_OF_CONDUCT.md
43
43
  - LICENSE.txt
44
44
  - README.md
45
+ - RELEASE_NOTES_v1.0.0.md
45
46
  - Rakefile
46
47
  - Steepfile
47
48
  - docs/implementation/00-overview.md
@@ -92,6 +93,7 @@ files:
92
93
  - lib/generators/better_translate/translate/USAGE
93
94
  - lib/generators/better_translate/translate/translate_generator.rb
94
95
  - lib/tasks/better_translate.rake
96
+ - regenerate_vcr.rb
95
97
  - sig/better_translate.rbs
96
98
  - sig/better_translate/cache.rbs
97
99
  - sig/better_translate/cli.rbs