better_translate 0.5.0 โ 1.0.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/.env.example +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +125 -114
- data/CLAUDE.md +385 -0
- data/README.md +629 -244
- data/Rakefile +7 -1
- data/Steepfile +29 -0
- data/docs/implementation/00-overview.md +220 -0
- data/docs/implementation/01-setup_dependencies.md +668 -0
- data/docs/implementation/02-error_handling.md +65 -0
- data/docs/implementation/03-core_components.md +457 -0
- data/docs/implementation/03.5-variable_preservation.md +509 -0
- data/docs/implementation/04-provider_architecture.md +571 -0
- data/docs/implementation/05-translation_logic.md +1065 -0
- data/docs/implementation/06-main_module_api.md +122 -0
- data/docs/implementation/07-direct_translation_helpers.md +582 -0
- data/docs/implementation/08-rails_integration.md +323 -0
- data/docs/implementation/09-testing_suite.md +228 -0
- data/docs/implementation/10-documentation_examples.md +150 -0
- data/docs/implementation/11-quality_security.md +65 -0
- data/docs/implementation/12-cli_standalone.md +698 -0
- data/exe/better_translate +9 -0
- data/lib/better_translate/cache.rb +125 -0
- data/lib/better_translate/cli.rb +304 -0
- data/lib/better_translate/configuration.rb +201 -0
- data/lib/better_translate/direct_translator.rb +131 -0
- data/lib/better_translate/errors.rb +101 -0
- data/lib/better_translate/progress_tracker.rb +157 -0
- data/lib/better_translate/provider_factory.rb +45 -0
- data/lib/better_translate/providers/anthropic_provider.rb +154 -0
- data/lib/better_translate/providers/base_http_provider.rb +239 -0
- data/lib/better_translate/providers/chatgpt_provider.rb +138 -44
- data/lib/better_translate/providers/gemini_provider.rb +123 -61
- data/lib/better_translate/railtie.rb +18 -0
- data/lib/better_translate/rate_limiter.rb +90 -0
- data/lib/better_translate/strategies/base_strategy.rb +58 -0
- data/lib/better_translate/strategies/batch_strategy.rb +56 -0
- data/lib/better_translate/strategies/deep_strategy.rb +45 -0
- data/lib/better_translate/strategies/strategy_selector.rb +43 -0
- data/lib/better_translate/translator.rb +115 -284
- data/lib/better_translate/utils/hash_flattener.rb +104 -0
- data/lib/better_translate/validator.rb +105 -0
- data/lib/better_translate/variable_extractor.rb +259 -0
- data/lib/better_translate/version.rb +2 -9
- data/lib/better_translate/yaml_handler.rb +168 -0
- data/lib/better_translate.rb +97 -73
- data/lib/generators/better_translate/analyze/USAGE +12 -0
- data/lib/generators/better_translate/analyze/analyze_generator.rb +94 -0
- data/lib/generators/better_translate/install/USAGE +13 -0
- data/lib/generators/better_translate/install/install_generator.rb +71 -0
- data/lib/generators/better_translate/install/templates/README +20 -0
- data/lib/generators/better_translate/install/templates/initializer.rb.tt +47 -0
- data/lib/generators/better_translate/translate/USAGE +13 -0
- data/lib/generators/better_translate/translate/translate_generator.rb +114 -0
- data/lib/tasks/better_translate.rake +136 -0
- data/sig/better_translate/cache.rbs +28 -0
- data/sig/better_translate/cli.rbs +24 -0
- data/sig/better_translate/configuration.rbs +78 -0
- data/sig/better_translate/direct_translator.rbs +18 -0
- data/sig/better_translate/errors.rbs +46 -0
- data/sig/better_translate/progress_tracker.rbs +29 -0
- data/sig/better_translate/provider_factory.rbs +8 -0
- data/sig/better_translate/providers/anthropic_provider.rbs +27 -0
- data/sig/better_translate/providers/base_http_provider.rbs +44 -0
- data/sig/better_translate/providers/chatgpt_provider.rbs +25 -0
- data/sig/better_translate/providers/gemini_provider.rbs +22 -0
- data/sig/better_translate/railtie.rbs +7 -0
- data/sig/better_translate/rate_limiter.rbs +20 -0
- data/sig/better_translate/strategies/base_strategy.rbs +19 -0
- data/sig/better_translate/strategies/batch_strategy.rbs +13 -0
- data/sig/better_translate/strategies/deep_strategy.rbs +11 -0
- data/sig/better_translate/strategies/strategy_selector.rbs +10 -0
- data/sig/better_translate/translator.rbs +24 -0
- data/sig/better_translate/utils/hash_flattener.rbs +14 -0
- data/sig/better_translate/validator.rbs +14 -0
- data/sig/better_translate/variable_extractor.rbs +40 -0
- data/sig/better_translate/version.rbs +4 -0
- data/sig/better_translate/yaml_handler.rbs +29 -0
- data/sig/better_translate.rbs +32 -2
- data/sig/faraday.rbs +22 -0
- data/sig/generators/better_translate/analyze/analyze_generator.rbs +18 -0
- data/sig/generators/better_translate/install/install_generator.rbs +14 -0
- data/sig/generators/better_translate/translate/translate_generator.rbs +10 -0
- data/sig/optparse.rbs +9 -0
- data/sig/psych.rbs +5 -0
- data/sig/rails.rbs +34 -0
- metadata +89 -203
- data/lib/better_translate/helper.rb +0 -83
- data/lib/better_translate/providers/base_provider.rb +0 -102
- data/lib/better_translate/service.rb +0 -144
- data/lib/better_translate/similarity_analyzer.rb +0 -218
- data/lib/better_translate/utils.rb +0 -55
- data/lib/better_translate/writer.rb +0 -75
- data/lib/generators/better_translate/analyze_generator.rb +0 -57
- data/lib/generators/better_translate/install_generator.rb +0 -14
- data/lib/generators/better_translate/templates/better_translate.rb +0 -56
- data/lib/generators/better_translate/translate_generator.rb +0 -84
data/README.md
CHANGED
|
@@ -1,348 +1,733 @@
|
|
|
1
|
-
# BetterTranslate
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://www.ruby-lang.org/en/)
|
|
6
|
+
[](LICENSE.txt)
|
|
7
|
+
[](https://rubygems.org/gems/better_translate)
|
|
8
|
+
|
|
9
|
+
BetterTranslate automatically translates your YAML locale files using cutting-edge AI providers (ChatGPT, Google Gemini, and Anthropic Claude). It's designed for Rails applications but works with any Ruby project that uses YAML-based internationalization.
|
|
10
|
+
|
|
11
|
+
**๐ฏ Why BetterTranslate?**
|
|
12
|
+
- โ
**Production-Ready**: Tested with real APIs via VCR cassettes (18 cassettes, 260KB)
|
|
13
|
+
- โ
**Interactive Demo**: Try it in 2 minutes with `ruby spec/dummy/demo_translation.rb`
|
|
14
|
+
- โ
**Variable Preservation**: `%{name}` placeholders maintained in translations
|
|
15
|
+
- โ
**Nested YAML Support**: Complex structures preserved perfectly
|
|
16
|
+
- โ
**Multiple Providers**: Choose ChatGPT, Gemini, or Claude
|
|
17
|
+
|
|
18
|
+
| Provider | Model | Speed | Quality | Cost |
|
|
19
|
+
|----------|-------|-------|---------|------|
|
|
20
|
+
| **ChatGPT** | GPT-5-nano | โกโกโก Fast | โญโญโญโญโญ Excellent | ๐ฐ๐ฐ Medium |
|
|
21
|
+
| **Gemini** | gemini-2.0-flash-exp | โกโกโกโก Very Fast | โญโญโญโญ Very Good | ๐ฐ Low |
|
|
22
|
+
| **Claude** | Claude 3.5 | โกโก Medium | โญโญโญโญโญ Excellent | ๐ฐ๐ฐ๐ฐ High |
|
|
23
|
+
|
|
24
|
+
## โจ Features
|
|
25
|
+
|
|
26
|
+
### Core Translation Features
|
|
27
|
+
- ๐ค **Multiple AI Providers**: Support for ChatGPT (GPT-5-nano), Google Gemini (gemini-2.0-flash-exp), and Anthropic Claude
|
|
28
|
+
- โก **Intelligent Caching**: LRU cache with optional TTL reduces API costs and speeds up repeated translations
|
|
29
|
+
- ๐ **Translation Modes**: Choose between override (replace entire files) or incremental (merge with existing translations)
|
|
30
|
+
- ๐ฏ **Smart Strategies**: Automatic selection between deep translation (< 50 strings) and batch translation (โฅ 50 strings)
|
|
31
|
+
- ๐ซ **Flexible Exclusions**: Global exclusions for all languages + language-specific exclusions for fine-grained control
|
|
32
|
+
- ๐จ **Translation Context**: Provide domain-specific context for medical, legal, financial, or technical terminology
|
|
33
|
+
- ๐ **Similarity Analysis**: Built-in Levenshtein distance analyzer to identify similar translations
|
|
34
|
+
|
|
35
|
+
### Development & Quality
|
|
36
|
+
- ๐งช **Comprehensive Testing**: Unit tests + integration tests with VCR cassettes (18 cassettes, 260KB)
|
|
37
|
+
- ๐ฌ **Rails Dummy App**: Interactive demo with real translations (`ruby spec/dummy/demo_translation.rb`)
|
|
38
|
+
- ๐ **VCR Integration**: Record real API responses, test without API keys, CI/CD friendly
|
|
39
|
+
- ๐ก๏ธ **Type-Safe Configuration**: Comprehensive validation with detailed error messages
|
|
40
|
+
- ๐ **YARD Documentation**: Complete API documentation with examples
|
|
41
|
+
- ๐ **Retry Logic**: Exponential backoff for failed API calls (3 attempts, configurable)
|
|
42
|
+
- ๐ฆ **Rate Limiting**: Thread-safe rate limiter prevents API overload
|
|
43
|
+
|
|
44
|
+
## ๐ Quick Start
|
|
45
|
+
|
|
46
|
+
### Try It Now (Interactive Demo)
|
|
47
|
+
|
|
48
|
+
Clone the repo and run the demo to see BetterTranslate in action:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/alessiobussolari/better_translate.git
|
|
52
|
+
cd better_translate
|
|
53
|
+
bundle install
|
|
54
|
+
|
|
55
|
+
# Set your OpenAI API key
|
|
56
|
+
export OPENAI_API_KEY=your_key_here
|
|
57
|
+
|
|
58
|
+
# Run the demo!
|
|
59
|
+
ruby spec/dummy/demo_translation.rb
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**What happens:**
|
|
63
|
+
- โ
Reads `en.yml` with 16 translation keys
|
|
64
|
+
- โ
Translates to Italian and French using ChatGPT
|
|
65
|
+
- โ
Generates `it.yml` and `fr.yml` files
|
|
66
|
+
- โ
Shows progress, results, and sample translations
|
|
67
|
+
- โ
Takes ~2 minutes (real API calls)
|
|
68
|
+
|
|
69
|
+
**Sample Output:**
|
|
70
|
+
```yaml
|
|
71
|
+
# en.yml (input)
|
|
72
|
+
en:
|
|
73
|
+
hello: "Hello"
|
|
74
|
+
users:
|
|
75
|
+
greeting: "Hello %{name}"
|
|
76
|
+
|
|
77
|
+
# it.yml (generated) โ
|
|
78
|
+
it:
|
|
79
|
+
hello: "Ciao"
|
|
80
|
+
users:
|
|
81
|
+
greeting: "Ciao %{name}" # Variable preserved!
|
|
82
|
+
|
|
83
|
+
# fr.yml (generated) โ
|
|
84
|
+
fr:
|
|
85
|
+
hello: "Bonjour"
|
|
86
|
+
users:
|
|
87
|
+
greeting: "Bonjour %{name}" # Variable preserved!
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
See [`spec/dummy/USAGE_GUIDE.md`](spec/dummy/USAGE_GUIDE.md) for more examples.
|
|
91
|
+
|
|
92
|
+
### Rails Integration
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# config/initializers/better_translate.rb
|
|
96
|
+
BetterTranslate.configure do |config|
|
|
97
|
+
config.provider = :chatgpt
|
|
98
|
+
config.openai_key = ENV["OPENAI_API_KEY"]
|
|
99
|
+
|
|
100
|
+
config.source_language = "en"
|
|
101
|
+
config.target_languages = [
|
|
102
|
+
{ short_name: "it", name: "Italian" },
|
|
103
|
+
{ short_name: "fr", name: "French" },
|
|
104
|
+
{ short_name: "es", name: "Spanish" }
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
config.input_file = "config/locales/en.yml"
|
|
108
|
+
config.output_folder = "config/locales"
|
|
109
|
+
|
|
110
|
+
# Optional: Provide context for better translations
|
|
111
|
+
config.translation_context = "E-commerce application with product catalog"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Translate all files
|
|
115
|
+
BetterTranslate.translate_all
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## ๐ฆ Installation
|
|
119
|
+
|
|
120
|
+
Add this line to your application's Gemfile:
|
|
49
121
|
|
|
50
122
|
```ruby
|
|
51
|
-
gem
|
|
123
|
+
gem "better_translate"
|
|
52
124
|
```
|
|
53
125
|
|
|
54
|
-
|
|
126
|
+
And then execute:
|
|
55
127
|
|
|
56
128
|
```bash
|
|
57
129
|
bundle install
|
|
58
130
|
```
|
|
59
131
|
|
|
60
|
-
Or install
|
|
132
|
+
Or install it yourself as:
|
|
61
133
|
|
|
62
134
|
```bash
|
|
63
135
|
gem install better_translate
|
|
64
136
|
```
|
|
65
137
|
|
|
66
|
-
|
|
138
|
+
### Rails Integration
|
|
67
139
|
|
|
68
|
-
|
|
140
|
+
For Rails applications, generate the initializer:
|
|
69
141
|
|
|
70
142
|
```bash
|
|
71
143
|
rails generate better_translate:install
|
|
72
144
|
```
|
|
73
145
|
|
|
74
|
-
This
|
|
146
|
+
This creates `config/initializers/better_translate.rb` with example configuration for all supported providers.
|
|
147
|
+
|
|
148
|
+
## โ๏ธ Configuration
|
|
149
|
+
|
|
150
|
+
### Provider Setup
|
|
151
|
+
|
|
152
|
+
#### ChatGPT (OpenAI)
|
|
75
153
|
|
|
76
154
|
```ruby
|
|
77
155
|
BetterTranslate.configure do |config|
|
|
78
|
-
# Choose the provider to use: :chatgpt or :gemini
|
|
79
156
|
config.provider = :chatgpt
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
config.
|
|
86
|
-
|
|
87
|
-
# Source language (e.g., "en" if the source file is in English)
|
|
88
|
-
config.source_language = "en"
|
|
89
|
-
|
|
90
|
-
# Output folder where the translated files will be saved
|
|
91
|
-
config.output_folder = Rails.root.join("config", "locales", "translated").to_s
|
|
92
|
-
|
|
93
|
-
# List of target languages (short_name and name)
|
|
94
|
-
config.target_languages = [
|
|
95
|
-
# Example:
|
|
96
|
-
{ short_name: "it", name: "italian" }
|
|
97
|
-
]
|
|
98
|
-
|
|
99
|
-
# Global exclusions (keys in dot notation) to exclude from translation
|
|
100
|
-
config.global_exclusions = [
|
|
101
|
-
"key.child_key"
|
|
102
|
-
]
|
|
103
|
-
|
|
104
|
-
# Language-specific exclusions: keys to exclude only for specific target languages
|
|
105
|
-
config.exclusions_per_language = {
|
|
106
|
-
"es" => [],
|
|
107
|
-
"it" => ["sample.valid"],
|
|
108
|
-
"fr" => [],
|
|
109
|
-
"de" => [],
|
|
110
|
-
"pt" => [],
|
|
111
|
-
"ru" => []
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
# Path to the input file (e.g., en.yml)
|
|
115
|
-
config.input_file = Rails.root.join("config", "locales", "en.yml").to_s
|
|
116
|
-
|
|
117
|
-
# Translation mode: :override or :incremental
|
|
118
|
-
config.translation_mode = :override
|
|
157
|
+
config.openai_key = ENV["OPENAI_API_KEY"]
|
|
158
|
+
|
|
159
|
+
# Optional: customize model settings (defaults shown)
|
|
160
|
+
config.request_timeout = 30 # seconds
|
|
161
|
+
config.max_retries = 3
|
|
162
|
+
config.retry_delay = 2.0 # seconds
|
|
119
163
|
end
|
|
120
164
|
```
|
|
121
165
|
|
|
122
|
-
|
|
166
|
+
Get your API key from [OpenAI Platform](https://platform.openai.com/api-keys).
|
|
123
167
|
|
|
124
|
-
|
|
168
|
+
#### Google Gemini
|
|
125
169
|
|
|
126
|
-
|
|
170
|
+
```ruby
|
|
171
|
+
BetterTranslate.configure do |config|
|
|
172
|
+
config.provider = :gemini
|
|
173
|
+
config.google_gemini_key = ENV["GOOGLE_GEMINI_API_KEY"]
|
|
174
|
+
|
|
175
|
+
# Same optional settings as ChatGPT
|
|
176
|
+
config.request_timeout = 30
|
|
177
|
+
config.max_retries = 3
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Get your API key from [Google AI Studio](https://makersuite.google.com/app/apikey).
|
|
182
|
+
|
|
183
|
+
#### Anthropic Claude
|
|
127
184
|
|
|
128
185
|
```ruby
|
|
129
|
-
BetterTranslate.
|
|
186
|
+
BetterTranslate.configure do |config|
|
|
187
|
+
config.provider = :anthropic
|
|
188
|
+
config.anthropic_key = ENV["ANTHROPIC_API_KEY"]
|
|
189
|
+
|
|
190
|
+
# Same optional settings
|
|
191
|
+
config.request_timeout = 30
|
|
192
|
+
config.max_retries = 3
|
|
193
|
+
end
|
|
130
194
|
```
|
|
131
195
|
|
|
132
|
-
|
|
133
|
-
1. Reads the input YAML file.
|
|
134
|
-
2. Applies the global exclusion filtering.
|
|
135
|
-
3. Applies additional language-specific exclusion filtering for each target language.
|
|
136
|
-
4. Translates the strings from the source language into the configured target languages.
|
|
137
|
-
5. Writes the translated files to the output folder, either in **override** or **incremental** mode based on the configuration.
|
|
196
|
+
Get your API key from [Anthropic Console](https://console.anthropic.com/).
|
|
138
197
|
|
|
139
|
-
###
|
|
198
|
+
### Language Configuration
|
|
140
199
|
|
|
141
|
-
|
|
200
|
+
```ruby
|
|
201
|
+
config.source_language = "en" # ISO 639-1 code (2 letters)
|
|
142
202
|
|
|
143
|
-
|
|
203
|
+
config.target_languages = [
|
|
204
|
+
{ short_name: "it", name: "Italian" },
|
|
205
|
+
{ short_name: "fr", name: "French" },
|
|
206
|
+
{ short_name: "de", name: "German" },
|
|
207
|
+
{ short_name: "es", name: "Spanish" },
|
|
208
|
+
{ short_name: "pt", name: "Portuguese" },
|
|
209
|
+
{ short_name: "ja", name: "Japanese" },
|
|
210
|
+
{ short_name: "zh", name: "Chinese" }
|
|
211
|
+
]
|
|
212
|
+
```
|
|
144
213
|
|
|
145
|
-
|
|
146
|
-
rails generate better_translate:install
|
|
147
|
-
```
|
|
214
|
+
### File Paths
|
|
148
215
|
|
|
149
|
-
|
|
216
|
+
```ruby
|
|
217
|
+
config.input_file = "config/locales/en.yml" # Source file
|
|
218
|
+
config.output_folder = "config/locales" # Output directory
|
|
219
|
+
```
|
|
150
220
|
|
|
151
|
-
|
|
152
|
-
rails generate better_translate:translate
|
|
153
|
-
```
|
|
221
|
+
## ๐จ Features in Detail
|
|
154
222
|
|
|
155
|
-
|
|
223
|
+
### Translation Modes
|
|
156
224
|
|
|
157
|
-
|
|
225
|
+
#### Override Mode (Default)
|
|
158
226
|
|
|
159
|
-
|
|
160
|
-
rails generate better_translate:analyze
|
|
161
|
-
```
|
|
227
|
+
Replaces the entire target file with fresh translations:
|
|
162
228
|
|
|
163
|
-
|
|
229
|
+
```ruby
|
|
230
|
+
config.translation_mode = :override # default
|
|
231
|
+
```
|
|
164
232
|
|
|
165
|
-
|
|
166
|
-
rails generate better_translate:provider YourProviderName
|
|
167
|
-
```
|
|
233
|
+
**Use when:** Starting fresh or regenerating all translations.
|
|
168
234
|
|
|
169
|
-
|
|
170
|
-
- Scan all YAML files in your locales directory
|
|
171
|
-
- Find similar translations using Levenshtein distance
|
|
172
|
-
- Generate two reports:
|
|
173
|
-
- `translation_similarities.json`: Detailed JSON report
|
|
174
|
-
- `translation_similarities_summary.txt`: Human-readable summary
|
|
175
|
-
|
|
176
|
-
This helps you:
|
|
177
|
-
- Identify potentially redundant translations
|
|
178
|
-
- Maintain consistency across your translations
|
|
179
|
-
- Optimize your translation files
|
|
180
|
-
- Reduce translation costs
|
|
235
|
+
#### Incremental Mode
|
|
181
236
|
|
|
182
|
-
|
|
237
|
+
Merges with existing translations, only translating missing keys:
|
|
183
238
|
|
|
184
|
-
|
|
239
|
+
```ruby
|
|
240
|
+
config.translation_mode = :incremental
|
|
241
|
+
```
|
|
185
242
|
|
|
186
|
-
|
|
243
|
+
**Use when:** Preserving manual corrections or adding new keys to existing translations.
|
|
187
244
|
|
|
188
|
-
|
|
189
|
-
rails generate better_translate:provider DeepL
|
|
190
|
-
```
|
|
245
|
+
### Caching System
|
|
191
246
|
|
|
192
|
-
|
|
247
|
+
The LRU (Least Recently Used) cache stores translations to reduce API costs:
|
|
193
248
|
|
|
194
|
-
|
|
249
|
+
```ruby
|
|
250
|
+
config.cache_enabled = true # default: true
|
|
251
|
+
config.cache_size = 1000 # default: 1000 items
|
|
252
|
+
config.cache_ttl = 3600 # optional: 1 hour in seconds (nil = no expiration)
|
|
253
|
+
```
|
|
195
254
|
|
|
196
|
-
|
|
255
|
+
**Cache key format:** `"#{text}:#{target_lang_code}"`
|
|
197
256
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def initialize(api_key)
|
|
203
|
-
@api_key = api_key
|
|
204
|
-
end
|
|
257
|
+
**Benefits:**
|
|
258
|
+
- Reduces API costs for repeated translations
|
|
259
|
+
- Speeds up re-runs during development
|
|
260
|
+
- Thread-safe with Mutex protection
|
|
205
261
|
|
|
206
|
-
|
|
207
|
-
# Implement your API call to DeepL here
|
|
208
|
-
# Return the translated text as a string
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
```
|
|
262
|
+
### Rate Limiting
|
|
213
263
|
|
|
214
|
-
|
|
264
|
+
Prevent API overload with built-in rate limiting:
|
|
215
265
|
|
|
216
|
-
|
|
266
|
+
```ruby
|
|
267
|
+
config.max_concurrent_requests = 3 # default: 3
|
|
268
|
+
```
|
|
217
269
|
|
|
218
|
-
|
|
219
|
-
# config/initializers/better_translate_providers.rb
|
|
220
|
-
# Require the provider file to ensure it's loaded
|
|
221
|
-
require Rails.root.join('app', 'providers', 'deep_l_provider')
|
|
222
|
-
|
|
223
|
-
BetterTranslate::Service.register_provider(
|
|
224
|
-
:deepl,
|
|
225
|
-
->(api_key) { Providers::DeepLProvider.new(api_key) }
|
|
226
|
-
)
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Note: The `require` statement is important to ensure the provider class is loaded before it's used.
|
|
270
|
+
The rate limiter enforces a 0.5-second delay between requests by default. This is handled automatically by the `BaseHttpProvider`.
|
|
230
271
|
|
|
231
|
-
|
|
272
|
+
### Exclusion System
|
|
232
273
|
|
|
233
|
-
|
|
274
|
+
#### Global Exclusions
|
|
234
275
|
|
|
235
|
-
|
|
236
|
-
# config/initializers/better_translate.rb
|
|
237
|
-
BetterTranslate.configure do |config|
|
|
238
|
-
config.provider = :deepl
|
|
239
|
-
config.deepl_key = ENV['DEEPL_API_KEY']
|
|
240
|
-
# ... other configuration
|
|
241
|
-
end
|
|
242
|
-
```
|
|
276
|
+
Keys excluded from translation in **all** target languages (useful for brand names, product codes, etc.):
|
|
243
277
|
|
|
244
|
-
|
|
278
|
+
```ruby
|
|
279
|
+
config.global_exclusions = [
|
|
280
|
+
"app.name", # "MyApp" should never be translated
|
|
281
|
+
"app.company", # "ACME Inc." stays the same
|
|
282
|
+
"product.sku" # "SKU-12345" is language-agnostic
|
|
283
|
+
]
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### Language-Specific Exclusions
|
|
245
287
|
|
|
246
|
-
|
|
288
|
+
Keys excluded only for **specific** languages (useful for manually translated legal text, locale-specific content, etc.):
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
config.exclusions_per_language = {
|
|
292
|
+
"it" => ["legal.terms", "legal.privacy"], # Italian legal text manually reviewed
|
|
293
|
+
"de" => ["legal.terms", "legal.privacy"], # German legal text manually reviewed
|
|
294
|
+
"fr" => ["marketing.slogan"] # French slogan crafted by marketing team
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
- `legal.terms` is translated for Spanish, Portuguese, etc.
|
|
300
|
+
- But excluded for Italian and German (already manually translated)
|
|
301
|
+
|
|
302
|
+
### Translation Context
|
|
303
|
+
|
|
304
|
+
Provide domain-specific context to improve translation accuracy:
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
config.translation_context = "Medical terminology for healthcare applications"
|
|
308
|
+
```
|
|
247
309
|
|
|
248
|
-
|
|
310
|
+
This context is included in the AI system prompt, helping with specialized terminology in fields like:
|
|
249
311
|
|
|
250
|
-
|
|
312
|
+
- ๐ฅ **Medical/Healthcare**: "patient", "diagnosis", "treatment"
|
|
313
|
+
- โ๏ธ **Legal**: "plaintiff", "defendant", "liability"
|
|
314
|
+
- ๐ฐ **Financial**: "dividend", "amortization", "escrow"
|
|
315
|
+
- ๐ **E-commerce**: "checkout", "cart", "inventory"
|
|
316
|
+
- ๐ง **Technical**: "API", "endpoint", "authentication"
|
|
251
317
|
|
|
252
|
-
### Translation
|
|
318
|
+
### Translation Strategies
|
|
253
319
|
|
|
254
|
-
BetterTranslate
|
|
320
|
+
BetterTranslate automatically selects the optimal strategy based on content size:
|
|
255
321
|
|
|
256
|
-
####
|
|
322
|
+
#### Deep Translation (< 50 strings)
|
|
323
|
+
- Translates each string individually
|
|
324
|
+
- Detailed progress tracking
|
|
325
|
+
- Best for small to medium files
|
|
257
326
|
|
|
258
|
-
|
|
327
|
+
#### Batch Translation (โฅ 50 strings)
|
|
328
|
+
- Processes in batches of 10 strings
|
|
329
|
+
- Faster for large files
|
|
330
|
+
- Reduced API overhead
|
|
259
331
|
|
|
260
|
-
**
|
|
332
|
+
**You don't need to configure this** - it's automatic! ๐ฏ
|
|
333
|
+
|
|
334
|
+
## ๐ง Rails Integration
|
|
335
|
+
|
|
336
|
+
BetterTranslate provides three Rails generators:
|
|
337
|
+
|
|
338
|
+
### 1. Install Generator
|
|
339
|
+
|
|
340
|
+
Generate the initializer with example configuration:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
rails generate better_translate:install
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Creates: `config/initializers/better_translate.rb`
|
|
347
|
+
|
|
348
|
+
### 2. Translate Generator
|
|
349
|
+
|
|
350
|
+
Run the translation process:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
rails generate better_translate:translate
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
This triggers the translation based on your configuration and displays progress messages.
|
|
357
|
+
|
|
358
|
+
### 3. Analyze Generator
|
|
359
|
+
|
|
360
|
+
Analyze translation similarities using Levenshtein distance:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
rails generate better_translate:analyze
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Output:**
|
|
367
|
+
- Console summary with similar translation pairs
|
|
368
|
+
- Detailed JSON report: `tmp/translation_similarity_report.json`
|
|
369
|
+
- Human-readable summary: `tmp/translation_similarity_summary.txt`
|
|
370
|
+
|
|
371
|
+
**Use cases:**
|
|
372
|
+
- Identify potential translation inconsistencies
|
|
373
|
+
- Find duplicate or near-duplicate translations
|
|
374
|
+
- Quality assurance for translation output
|
|
375
|
+
|
|
376
|
+
## ๐ Advanced Usage
|
|
377
|
+
|
|
378
|
+
### Programmatic Translation
|
|
379
|
+
|
|
380
|
+
#### Translate Multiple Texts to Multiple Languages
|
|
261
381
|
|
|
262
382
|
```ruby
|
|
263
|
-
|
|
264
|
-
|
|
383
|
+
texts = ["Hello", "Goodbye", "Thank you"]
|
|
384
|
+
target_langs = [
|
|
265
385
|
{ short_name: "it", name: "Italian" },
|
|
266
386
|
{ short_name: "fr", name: "French" }
|
|
267
387
|
]
|
|
268
388
|
|
|
269
|
-
|
|
270
|
-
text,
|
|
271
|
-
target_languages,
|
|
272
|
-
"en", # source language code
|
|
273
|
-
:chatgpt # provider: can be :chatgpt or :gemini
|
|
274
|
-
)
|
|
389
|
+
results = BetterTranslate::TranslationHelper.translate_texts_to_languages(texts, target_langs)
|
|
275
390
|
|
|
276
|
-
|
|
277
|
-
#
|
|
278
|
-
#
|
|
391
|
+
# Results structure:
|
|
392
|
+
# {
|
|
393
|
+
# "it" => ["Ciao", "Arrivederci", "Grazie"],
|
|
394
|
+
# "fr" => ["Bonjour", "Au revoir", "Merci"]
|
|
395
|
+
# }
|
|
279
396
|
```
|
|
280
397
|
|
|
281
|
-
#### Multiple
|
|
282
|
-
|
|
283
|
-
The `translate_texts_to_languages` helper extends the functionality to translate an array of texts into multiple target languages. It returns a hash where each key is a target language code and the value is an array of translated texts.
|
|
284
|
-
|
|
285
|
-
**Example Usage:**
|
|
398
|
+
#### Translate Single Text to Multiple Languages
|
|
286
399
|
|
|
287
400
|
```ruby
|
|
288
|
-
|
|
289
|
-
|
|
401
|
+
text = "Welcome to our application"
|
|
402
|
+
target_langs = [
|
|
290
403
|
{ short_name: "it", name: "Italian" },
|
|
291
|
-
{ short_name: "
|
|
404
|
+
{ short_name: "es", name: "Spanish" }
|
|
292
405
|
]
|
|
293
406
|
|
|
294
|
-
|
|
295
|
-
texts,
|
|
296
|
-
target_languages,
|
|
297
|
-
"en", # source language code
|
|
298
|
-
:chatgpt # provider: can be :chatgpt or :gemini
|
|
299
|
-
)
|
|
407
|
+
results = BetterTranslate::TranslationHelper.translate_text_to_languages(text, target_langs)
|
|
300
408
|
|
|
301
|
-
|
|
302
|
-
# Expected output:
|
|
409
|
+
# Results:
|
|
303
410
|
# {
|
|
304
|
-
# "it" =>
|
|
305
|
-
# "
|
|
411
|
+
# "it" => "Benvenuto nella nostra applicazione",
|
|
412
|
+
# "es" => "Bienvenido a nuestra aplicaciรณn"
|
|
306
413
|
# }
|
|
307
414
|
```
|
|
308
415
|
|
|
309
|
-
|
|
416
|
+
### Custom Configuration for Specific Tasks
|
|
417
|
+
|
|
418
|
+
```ruby
|
|
419
|
+
# Separate configuration for different domains
|
|
420
|
+
medical_config = BetterTranslate::Configuration.new
|
|
421
|
+
medical_config.provider = :chatgpt
|
|
422
|
+
medical_config.openai_key = ENV["OPENAI_API_KEY"]
|
|
423
|
+
medical_config.translation_context = "Medical terminology for patient records"
|
|
424
|
+
medical_config.validate!
|
|
425
|
+
|
|
426
|
+
# Use the custom config...
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Dry Run Mode
|
|
430
|
+
|
|
431
|
+
Test your configuration without writing files:
|
|
432
|
+
|
|
433
|
+
```ruby
|
|
434
|
+
config.dry_run = true
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
This validates everything and simulates the translation process without creating output files.
|
|
438
|
+
|
|
439
|
+
### Verbose Logging
|
|
310
440
|
|
|
311
|
-
|
|
441
|
+
Enable detailed logging for debugging:
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
config.verbose = true
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## ๐งช Development & Testing
|
|
448
|
+
|
|
449
|
+
BetterTranslate includes comprehensive testing infrastructure with **unit tests**, **integration tests**, and a **Rails dummy app** for realistic testing.
|
|
450
|
+
|
|
451
|
+
### Test Structure
|
|
452
|
+
|
|
453
|
+
```
|
|
454
|
+
spec/
|
|
455
|
+
โโโ better_translate/ # Unit tests (fast, no API calls)
|
|
456
|
+
โ โโโ cache_spec.rb
|
|
457
|
+
โ โโโ configuration_spec.rb
|
|
458
|
+
โ โโโ providers/
|
|
459
|
+
โ โ โโโ chatgpt_provider_spec.rb
|
|
460
|
+
โ โ โโโ gemini_provider_spec.rb
|
|
461
|
+
โ โโโ ...
|
|
462
|
+
โ
|
|
463
|
+
โโโ integration/ # Integration tests (real API via VCR)
|
|
464
|
+
โ โโโ chatgpt_integration_spec.rb
|
|
465
|
+
โ โโโ gemini_integration_spec.rb
|
|
466
|
+
โ โโโ rails_dummy_app_spec.rb
|
|
467
|
+
โ โโโ README.md
|
|
468
|
+
โ
|
|
469
|
+
โโโ dummy/ # Rails dummy app for testing
|
|
470
|
+
โ โโโ config/
|
|
471
|
+
โ โ โโโ locales/
|
|
472
|
+
โ โ โโโ en.yml # Source file
|
|
473
|
+
โ โ โโโ it.yml # Generated translations
|
|
474
|
+
โ โ โโโ fr.yml
|
|
475
|
+
โ โโโ demo_translation.rb # Interactive demo script
|
|
476
|
+
โ โโโ USAGE_GUIDE.md
|
|
477
|
+
โ
|
|
478
|
+
โโโ vcr_cassettes/ # Recorded API responses (18 cassettes, 260KB)
|
|
479
|
+
โโโ chatgpt/ (7)
|
|
480
|
+
โโโ gemini/ (7)
|
|
481
|
+
โโโ rails/ (4)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Running Tests
|
|
312
485
|
|
|
313
486
|
```bash
|
|
487
|
+
# Run all tests (unit + integration)
|
|
488
|
+
bundle exec rake spec
|
|
489
|
+
# or
|
|
314
490
|
bundle exec rspec
|
|
491
|
+
|
|
492
|
+
# Run only unit tests (fast, no API calls)
|
|
493
|
+
bundle exec rspec spec/better_translate/
|
|
494
|
+
|
|
495
|
+
# Run only integration tests (uses VCR cassettes)
|
|
496
|
+
bundle exec rspec spec/integration/
|
|
497
|
+
|
|
498
|
+
# Run specific test file
|
|
499
|
+
bundle exec rspec spec/better_translate/configuration_spec.rb
|
|
500
|
+
|
|
501
|
+
# Run tests with coverage
|
|
502
|
+
bundle exec rspec --format documentation
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### VCR Cassettes & API Testing
|
|
506
|
+
|
|
507
|
+
BetterTranslate uses **VCR** (Video Cassette Recorder) to record real API interactions for integration tests. This allows:
|
|
508
|
+
|
|
509
|
+
โ
**Realistic testing** with actual provider responses
|
|
510
|
+
โ
**No API keys needed** after initial recording
|
|
511
|
+
โ
**Fast test execution** (no real API calls)
|
|
512
|
+
โ
**CI/CD friendly** (cassettes committed to repo)
|
|
513
|
+
โ
**API keys anonymized** (safe to commit)
|
|
514
|
+
|
|
515
|
+
#### Setup API Keys for Recording
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
# Copy environment template
|
|
519
|
+
cp .env.example .env
|
|
520
|
+
|
|
521
|
+
# Edit .env and add your API keys
|
|
522
|
+
OPENAI_API_KEY=sk-...
|
|
523
|
+
GEMINI_API_KEY=...
|
|
524
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Re-record Cassettes
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
# Delete and re-record all cassettes
|
|
531
|
+
rm -rf spec/vcr_cassettes/
|
|
532
|
+
bundle exec rspec spec/integration/
|
|
533
|
+
|
|
534
|
+
# Re-record specific provider
|
|
535
|
+
rm -rf spec/vcr_cassettes/chatgpt/
|
|
536
|
+
bundle exec rspec spec/integration/chatgpt_integration_spec.rb
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Note**: The `.env` file is gitignored. API keys in cassettes are automatically replaced with `<OPENAI_API_KEY>`, `<GEMINI_API_KEY>`, etc.
|
|
540
|
+
|
|
541
|
+
### Rails Dummy App Demo
|
|
542
|
+
|
|
543
|
+
Test BetterTranslate with a realistic Rails app:
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
# Run interactive demo
|
|
547
|
+
ruby spec/dummy/demo_translation.rb
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Output:**
|
|
551
|
+
```
|
|
552
|
+
๐ Starting translation...
|
|
553
|
+
[BetterTranslate] Italian | hello | 6.3%
|
|
554
|
+
[BetterTranslate] Italian | world | 12.5%
|
|
555
|
+
...
|
|
556
|
+
โ
Success: 2 language(s)
|
|
557
|
+
โ it.yml generated (519 bytes)
|
|
558
|
+
โ fr.yml generated (511 bytes)
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Generated files:**
|
|
562
|
+
- `spec/dummy/config/locales/it.yml` - Italian translation
|
|
563
|
+
- `spec/dummy/config/locales/fr.yml` - French translation
|
|
564
|
+
|
|
565
|
+
See [`spec/dummy/USAGE_GUIDE.md`](spec/dummy/USAGE_GUIDE.md) for more examples.
|
|
566
|
+
|
|
567
|
+
### Code Quality
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
# Run RuboCop linter
|
|
571
|
+
bundle exec rubocop
|
|
572
|
+
|
|
573
|
+
# Auto-fix violations
|
|
574
|
+
bundle exec rubocop -a
|
|
575
|
+
|
|
576
|
+
# Run both tests and linter
|
|
577
|
+
bundle exec rake
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Documentation
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
# Generate YARD documentation
|
|
584
|
+
bundle exec yard doc
|
|
585
|
+
|
|
586
|
+
# Start documentation server (http://localhost:8808)
|
|
587
|
+
bundle exec yard server
|
|
588
|
+
|
|
589
|
+
# Check documentation coverage
|
|
590
|
+
bundle exec yard stats
|
|
315
591
|
```
|
|
316
592
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
593
|
+
### Interactive Console
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# Load the gem in an interactive console
|
|
597
|
+
bin/console
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Security Audit
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# Check for security vulnerabilities
|
|
604
|
+
bundle exec bundler-audit check --update
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
## ๐๏ธ Architecture
|
|
608
|
+
|
|
609
|
+
### Provider Architecture
|
|
610
|
+
|
|
611
|
+
All providers inherit from `BaseHttpProvider`:
|
|
612
|
+
|
|
613
|
+
```
|
|
614
|
+
BaseHttpProvider (abstract)
|
|
615
|
+
โโโ ChatGPTProvider
|
|
616
|
+
โโโ GeminiProvider
|
|
617
|
+
โโโ AnthropicProvider
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**BaseHttpProvider responsibilities:**
|
|
621
|
+
- HTTP communication via Faraday
|
|
622
|
+
- Retry logic with exponential backoff
|
|
623
|
+
- Rate limiting
|
|
624
|
+
- Timeout handling
|
|
625
|
+
- Error wrapping
|
|
626
|
+
|
|
627
|
+
### Core Components
|
|
628
|
+
|
|
629
|
+
- **Configuration**: Type-safe config with validation
|
|
630
|
+
- **Cache**: LRU cache with optional TTL
|
|
631
|
+
- **RateLimiter**: Thread-safe request throttling
|
|
632
|
+
- **Validator**: Input validation (language codes, text, paths, keys)
|
|
633
|
+
- **HashFlattener**: Converts nested YAML โ flat structure
|
|
323
634
|
|
|
324
|
-
|
|
635
|
+
### Error Hierarchy
|
|
636
|
+
|
|
637
|
+
All errors inherit from `BetterTranslate::Error`:
|
|
638
|
+
|
|
639
|
+
```
|
|
640
|
+
BetterTranslate::Error
|
|
641
|
+
โโโ ConfigurationError
|
|
642
|
+
โโโ ValidationError
|
|
643
|
+
โโโ TranslationError
|
|
644
|
+
โโโ ProviderError
|
|
645
|
+
โโโ ApiError
|
|
646
|
+
โโโ RateLimitError
|
|
647
|
+
โโโ FileError
|
|
648
|
+
โโโ YamlError
|
|
649
|
+
โโโ ProviderNotFoundError
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
## ๐ Documentation
|
|
653
|
+
|
|
654
|
+
- **[USAGE_GUIDE.md](spec/dummy/USAGE_GUIDE.md)** - Complete guide to dummy app and demos
|
|
655
|
+
- **[VCR Testing Guide](spec/integration/README.md)** - How to test with VCR cassettes
|
|
656
|
+
- **[CLAUDE.md](CLAUDE.md)** - Developer guide for AI assistants (Claude Code)
|
|
657
|
+
- **[YARD Docs](https://rubydoc.info/gems/better_translate)** - Complete API documentation
|
|
658
|
+
|
|
659
|
+
### Key Documentation Files
|
|
660
|
+
|
|
661
|
+
```
|
|
662
|
+
better_translate/
|
|
663
|
+
โโโ README.md # This file (main documentation)
|
|
664
|
+
โโโ CLAUDE.md # Development guide (commands, architecture)
|
|
665
|
+
โโโ spec/
|
|
666
|
+
โ โโโ dummy/
|
|
667
|
+
โ โ โโโ USAGE_GUIDE.md # ๐ Interactive demo guide
|
|
668
|
+
โ โ โโโ demo_translation.rb # ๐ Runnable demo script
|
|
669
|
+
โ โโโ integration/
|
|
670
|
+
โ โโโ README.md # ๐งช VCR testing guide
|
|
671
|
+
โโโ docs/
|
|
672
|
+
โโโ implementation/ # Design docs
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
## ๐ค Contributing
|
|
676
|
+
|
|
677
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/alessiobussolari/better_translate.
|
|
678
|
+
|
|
679
|
+
### Development Guidelines
|
|
680
|
+
|
|
681
|
+
1. **TDD (Test-Driven Development)**: Always write tests before implementing features
|
|
682
|
+
2. **YARD Documentation**: Document all public methods with `@param`, `@return`, `@raise`, and `@example`
|
|
683
|
+
3. **RuboCop Compliance**: Ensure code passes `bundle exec rubocop` before committing
|
|
684
|
+
4. **Frozen String Literals**: Include `# frozen_string_literal: true` at the top of all files
|
|
685
|
+
5. **HTTP Client**: Use Faraday for all HTTP requests (never Net::HTTP or HTTParty)
|
|
686
|
+
6. **VCR Cassettes**: Record integration tests with real API responses for CI/CD
|
|
687
|
+
|
|
688
|
+
### Workflow
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
# 1. Clone and setup
|
|
692
|
+
git clone https://github.com/alessiobussolari/better_translate.git
|
|
693
|
+
cd better_translate
|
|
694
|
+
bundle install
|
|
695
|
+
|
|
696
|
+
# 2. Create a feature branch
|
|
697
|
+
git checkout -b my-feature
|
|
698
|
+
|
|
699
|
+
# 3. Write tests first (TDD)
|
|
700
|
+
# Edit spec/better_translate/my_feature_spec.rb
|
|
701
|
+
|
|
702
|
+
# 4. Implement the feature
|
|
703
|
+
# Edit lib/better_translate/my_feature.rb
|
|
704
|
+
|
|
705
|
+
# 5. Ensure tests pass and code is clean
|
|
706
|
+
bundle exec rspec
|
|
707
|
+
bundle exec rubocop
|
|
708
|
+
|
|
709
|
+
# 6. Commit and push
|
|
710
|
+
git add .
|
|
711
|
+
git commit -m "Add my feature"
|
|
712
|
+
git push origin my-feature
|
|
713
|
+
|
|
714
|
+
# 7. Create a Pull Request
|
|
715
|
+
```
|
|
325
716
|
|
|
326
|
-
|
|
327
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
328
|
-
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
329
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
330
|
-
5. Open a Pull Request
|
|
717
|
+
## ๐ License
|
|
331
718
|
|
|
332
|
-
|
|
719
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
333
720
|
|
|
334
|
-
|
|
335
|
-
- **Issues**: [GitHub Issues](https://github.com/alessiobussolari/better_translate/issues)
|
|
336
|
-
- **Discussions**: [GitHub Discussions](https://github.com/alessiobussolari/better_translate/discussions)
|
|
721
|
+
## ๐ Code of Conduct
|
|
337
722
|
|
|
338
|
-
|
|
723
|
+
Everyone interacting in the BetterTranslate project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
|
339
724
|
|
|
340
|
-
|
|
725
|
+
---
|
|
341
726
|
|
|
342
|
-
|
|
727
|
+
<div align="center">
|
|
343
728
|
|
|
344
|
-
|
|
729
|
+
**Made with โค๏ธ by [Alessio Bussolari](https://github.com/alessiobussolari)**
|
|
345
730
|
|
|
346
|
-
|
|
731
|
+
[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)
|
|
347
732
|
|
|
348
|
-
|
|
733
|
+
</div>
|