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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +14 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +8 -0
  5. data/.yardopts +10 -0
  6. data/CHANGELOG.md +125 -114
  7. data/CLAUDE.md +385 -0
  8. data/README.md +629 -244
  9. data/Rakefile +7 -1
  10. data/Steepfile +29 -0
  11. data/docs/implementation/00-overview.md +220 -0
  12. data/docs/implementation/01-setup_dependencies.md +668 -0
  13. data/docs/implementation/02-error_handling.md +65 -0
  14. data/docs/implementation/03-core_components.md +457 -0
  15. data/docs/implementation/03.5-variable_preservation.md +509 -0
  16. data/docs/implementation/04-provider_architecture.md +571 -0
  17. data/docs/implementation/05-translation_logic.md +1065 -0
  18. data/docs/implementation/06-main_module_api.md +122 -0
  19. data/docs/implementation/07-direct_translation_helpers.md +582 -0
  20. data/docs/implementation/08-rails_integration.md +323 -0
  21. data/docs/implementation/09-testing_suite.md +228 -0
  22. data/docs/implementation/10-documentation_examples.md +150 -0
  23. data/docs/implementation/11-quality_security.md +65 -0
  24. data/docs/implementation/12-cli_standalone.md +698 -0
  25. data/exe/better_translate +9 -0
  26. data/lib/better_translate/cache.rb +125 -0
  27. data/lib/better_translate/cli.rb +304 -0
  28. data/lib/better_translate/configuration.rb +201 -0
  29. data/lib/better_translate/direct_translator.rb +131 -0
  30. data/lib/better_translate/errors.rb +101 -0
  31. data/lib/better_translate/progress_tracker.rb +157 -0
  32. data/lib/better_translate/provider_factory.rb +45 -0
  33. data/lib/better_translate/providers/anthropic_provider.rb +154 -0
  34. data/lib/better_translate/providers/base_http_provider.rb +239 -0
  35. data/lib/better_translate/providers/chatgpt_provider.rb +138 -44
  36. data/lib/better_translate/providers/gemini_provider.rb +123 -61
  37. data/lib/better_translate/railtie.rb +18 -0
  38. data/lib/better_translate/rate_limiter.rb +90 -0
  39. data/lib/better_translate/strategies/base_strategy.rb +58 -0
  40. data/lib/better_translate/strategies/batch_strategy.rb +56 -0
  41. data/lib/better_translate/strategies/deep_strategy.rb +45 -0
  42. data/lib/better_translate/strategies/strategy_selector.rb +43 -0
  43. data/lib/better_translate/translator.rb +115 -284
  44. data/lib/better_translate/utils/hash_flattener.rb +104 -0
  45. data/lib/better_translate/validator.rb +105 -0
  46. data/lib/better_translate/variable_extractor.rb +259 -0
  47. data/lib/better_translate/version.rb +2 -9
  48. data/lib/better_translate/yaml_handler.rb +168 -0
  49. data/lib/better_translate.rb +97 -73
  50. data/lib/generators/better_translate/analyze/USAGE +12 -0
  51. data/lib/generators/better_translate/analyze/analyze_generator.rb +94 -0
  52. data/lib/generators/better_translate/install/USAGE +13 -0
  53. data/lib/generators/better_translate/install/install_generator.rb +71 -0
  54. data/lib/generators/better_translate/install/templates/README +20 -0
  55. data/lib/generators/better_translate/install/templates/initializer.rb.tt +47 -0
  56. data/lib/generators/better_translate/translate/USAGE +13 -0
  57. data/lib/generators/better_translate/translate/translate_generator.rb +114 -0
  58. data/lib/tasks/better_translate.rake +136 -0
  59. data/sig/better_translate/cache.rbs +28 -0
  60. data/sig/better_translate/cli.rbs +24 -0
  61. data/sig/better_translate/configuration.rbs +78 -0
  62. data/sig/better_translate/direct_translator.rbs +18 -0
  63. data/sig/better_translate/errors.rbs +46 -0
  64. data/sig/better_translate/progress_tracker.rbs +29 -0
  65. data/sig/better_translate/provider_factory.rbs +8 -0
  66. data/sig/better_translate/providers/anthropic_provider.rbs +27 -0
  67. data/sig/better_translate/providers/base_http_provider.rbs +44 -0
  68. data/sig/better_translate/providers/chatgpt_provider.rbs +25 -0
  69. data/sig/better_translate/providers/gemini_provider.rbs +22 -0
  70. data/sig/better_translate/railtie.rbs +7 -0
  71. data/sig/better_translate/rate_limiter.rbs +20 -0
  72. data/sig/better_translate/strategies/base_strategy.rbs +19 -0
  73. data/sig/better_translate/strategies/batch_strategy.rbs +13 -0
  74. data/sig/better_translate/strategies/deep_strategy.rbs +11 -0
  75. data/sig/better_translate/strategies/strategy_selector.rbs +10 -0
  76. data/sig/better_translate/translator.rbs +24 -0
  77. data/sig/better_translate/utils/hash_flattener.rbs +14 -0
  78. data/sig/better_translate/validator.rbs +14 -0
  79. data/sig/better_translate/variable_extractor.rbs +40 -0
  80. data/sig/better_translate/version.rbs +4 -0
  81. data/sig/better_translate/yaml_handler.rbs +29 -0
  82. data/sig/better_translate.rbs +32 -2
  83. data/sig/faraday.rbs +22 -0
  84. data/sig/generators/better_translate/analyze/analyze_generator.rbs +18 -0
  85. data/sig/generators/better_translate/install/install_generator.rbs +14 -0
  86. data/sig/generators/better_translate/translate/translate_generator.rbs +10 -0
  87. data/sig/optparse.rbs +9 -0
  88. data/sig/psych.rbs +5 -0
  89. data/sig/rails.rbs +34 -0
  90. metadata +89 -203
  91. data/lib/better_translate/helper.rb +0 -83
  92. data/lib/better_translate/providers/base_provider.rb +0 -102
  93. data/lib/better_translate/service.rb +0 -144
  94. data/lib/better_translate/similarity_analyzer.rb +0 -218
  95. data/lib/better_translate/utils.rb +0 -55
  96. data/lib/better_translate/writer.rb +0 -75
  97. data/lib/generators/better_translate/analyze_generator.rb +0 -57
  98. data/lib/generators/better_translate/install_generator.rb +0 -14
  99. data/lib/generators/better_translate/templates/better_translate.rb +0 -56
  100. data/lib/generators/better_translate/translate_generator.rb +0 -84
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module BetterTranslate
6
+ module Generators
7
+ # Rails generator for analyzing YAML locale files
8
+ #
9
+ # Provides statistics about translation files: string count, structure, etc.
10
+ #
11
+ # @example Analyze a file
12
+ # rails generate better_translate:analyze config/locales/en.yml
13
+ #
14
+ class AnalyzeGenerator < Rails::Generators::Base
15
+ source_root File.expand_path("templates", __dir__)
16
+
17
+ desc "Analyze YAML locale file structure and statistics"
18
+
19
+ argument :file_path,
20
+ type: :string,
21
+ required: false,
22
+ default: "config/locales/en.yml",
23
+ desc: "Path to YAML file to analyze"
24
+
25
+ # Analyze YAML file
26
+ #
27
+ # @return [void]
28
+ #
29
+ def analyze_file
30
+ full_path = Rails.root.join(file_path)
31
+
32
+ unless File.exist?(full_path)
33
+ say "File not found: #{full_path}", :red
34
+ return
35
+ end
36
+
37
+ say "Analyzing: #{file_path}", :green
38
+ say "=" * 60
39
+
40
+ begin
41
+ data = YAML.load_file(full_path)
42
+
43
+ # Flatten to count strings
44
+ flattened = BetterTranslate::Utils::HashFlattener.flatten(data)
45
+
46
+ say ""
47
+ say "Total strings: #{flattened.size}", :cyan
48
+ say ""
49
+
50
+ # Show structure
51
+ say "Structure:", :yellow
52
+ show_structure(data, 0)
53
+
54
+ say ""
55
+ say "=" * 60
56
+ say "Analysis complete", :green
57
+ rescue StandardError => e
58
+ say "Error analyzing file: #{e.message}", :red
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # Show nested structure
65
+ #
66
+ # @param hash [Hash] Hash to show
67
+ # @param level [Integer] Nesting level
68
+ # @return [void]
69
+ # @api private
70
+ #
71
+ def show_structure(hash, level)
72
+ hash.each do |key, value|
73
+ indent = " " * level
74
+ if value.is_a?(Hash)
75
+ say "#{indent}#{key}/ (#{count_strings(value)} strings)", :white
76
+ show_structure(value, level + 1)
77
+ else
78
+ say "#{indent}#{key}: #{value.to_s[0..50]}#{value.to_s.length > 50 ? "..." : ""}", :white
79
+ end
80
+ end
81
+ end
82
+
83
+ # Count strings in nested hash
84
+ #
85
+ # @param hash [Hash] Hash to count
86
+ # @return [Integer] String count
87
+ # @api private
88
+ #
89
+ def count_strings(hash)
90
+ BetterTranslate::Utils::HashFlattener.flatten(hash).size
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Installs BetterTranslate configuration files for Rails application.
3
+
4
+ This will create:
5
+ - config/initializers/better_translate.rb (Ruby configuration)
6
+ - config/better_translate.yml (YAML configuration)
7
+
8
+ Example:
9
+ bin/rails generate better_translate:install
10
+
11
+ This will create:
12
+ config/initializers/better_translate.rb
13
+ config/better_translate.yml
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module BetterTranslate
6
+ module Generators
7
+ # Rails generator for installing BetterTranslate
8
+ #
9
+ # Creates initializer and config files for Rails applications.
10
+ #
11
+ # @example Run generator
12
+ # rails generate better_translate:install
13
+ #
14
+ class InstallGenerator < Rails::Generators::Base
15
+ source_root File.expand_path("templates", __dir__)
16
+
17
+ desc "Creates BetterTranslate initializer and config files"
18
+
19
+ # Create initializer file
20
+ #
21
+ # @return [void]
22
+ #
23
+ def create_initializer_file
24
+ template "initializer.rb.tt", "config/initializers/better_translate.rb"
25
+ end
26
+
27
+ # Create YAML config file
28
+ #
29
+ # @return [void]
30
+ #
31
+ def create_config_file
32
+ sample_config = {
33
+ "provider" => "chatgpt",
34
+ "openai_key" => "YOUR_OPENAI_API_KEY",
35
+ "gemini_key" => "YOUR_GEMINI_API_KEY",
36
+ "anthropic_key" => "YOUR_ANTHROPIC_API_KEY",
37
+ "source_language" => "en",
38
+ "target_languages" => [
39
+ { "short_name" => "it", "name" => "Italian" },
40
+ { "short_name" => "es", "name" => "Spanish" },
41
+ { "short_name" => "fr", "name" => "French" }
42
+ ],
43
+ "input_file" => "config/locales/en.yml",
44
+ "output_folder" => "config/locales",
45
+ "verbose" => true,
46
+ "dry_run" => false,
47
+ "translation_mode" => "override",
48
+ "preserve_variables" => true,
49
+ "global_exclusions" => Array.new,
50
+ "exclusions_per_language" => Hash.new,
51
+ "model" => nil,
52
+ "temperature" => 0.3,
53
+ "max_tokens" => 2000,
54
+ "timeout" => 30,
55
+ "max_retries" => 3,
56
+ "rate_limit" => 10
57
+ }
58
+
59
+ create_file "config/better_translate.yml", sample_config.to_yaml
60
+ end
61
+
62
+ # Show post-install message
63
+ #
64
+ # @return [void]
65
+ #
66
+ def show_readme
67
+ readme "README" if behavior == :invoke
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,20 @@
1
+ ===============================================================================
2
+
3
+ BetterTranslate has been installed!
4
+
5
+ Created files:
6
+ • config/initializers/better_translate.rb
7
+ • config/better_translate.yml
8
+
9
+ Next steps:
10
+
11
+ 1. Edit config/better_translate.yml and add your API keys
12
+ 2. Customize translation settings in config/initializers/better_translate.rb
13
+ 3. Run translation:
14
+
15
+ rake better_translate:translate
16
+
17
+ For more information:
18
+ • https://github.com/alessiobussolari/better_translate
19
+
20
+ ===============================================================================
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # BetterTranslate Configuration
4
+ #
5
+ # For more configuration options, see config/better_translate.yml
6
+
7
+ BetterTranslate.configure do |config|
8
+ # Provider: :chatgpt, :gemini, or :anthropic
9
+ config.provider = :chatgpt
10
+
11
+ # API Keys (alternatively set via ENV variables)
12
+ config.openai_key = ENV["OPENAI_API_KEY"]
13
+ config.gemini_key = ENV["GEMINI_API_KEY"]
14
+ config.anthropic_key = ENV["ANTHROPIC_API_KEY"]
15
+
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
+ ]
23
+
24
+ # File paths
25
+ config.input_file = Rails.root.join("config", "locales", "en.yml").to_s
26
+ config.output_folder = Rails.root.join("config", "locales").to_s
27
+
28
+ # Options
29
+ config.verbose = true
30
+ config.dry_run = false # Set to true for testing
31
+ config.translation_mode = :override # or :incremental
32
+ config.preserve_variables = true
33
+
34
+ # Optional: Provider-specific settings
35
+ # config.model = "gpt-5-nano" # ChatGPT model
36
+ # config.temperature = 0.3
37
+ # config.max_tokens = 2000
38
+ # config.timeout = 30
39
+ # config.max_retries = 3
40
+ # config.rate_limit = 10
41
+
42
+ # Optional: Exclusions
43
+ # config.global_exclusions = ["excluded_key", "another_key"]
44
+ # config.exclusions_per_language = {
45
+ # "it" => ["italy_specific_exclusion"]
46
+ # }
47
+ end
@@ -0,0 +1,13 @@
1
+ Description:
2
+ Runs BetterTranslate translation based on existing configuration.
3
+
4
+ This will translate your YAML locale files using the configuration
5
+ in config/better_translate.yml.
6
+
7
+ Example:
8
+ bin/rails generate better_translate:translate
9
+
10
+ This will execute the translation task.
11
+
12
+ Options:
13
+ --dry-run Run in dry-run mode (no files will be written)
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module BetterTranslate
6
+ module Generators
7
+ # Rails generator for running translations
8
+ #
9
+ # Executes translation based on existing configuration.
10
+ #
11
+ # @example Run generator
12
+ # rails generate better_translate:translate
13
+ #
14
+ # @example Run with dry-run
15
+ # rails generate better_translate:translate --dry-run
16
+ #
17
+ class TranslateGenerator < Rails::Generators::Base
18
+ source_root File.expand_path("templates", __dir__)
19
+
20
+ desc "Run BetterTranslate translation task"
21
+
22
+ class_option :dry_run,
23
+ type: :boolean,
24
+ default: false,
25
+ desc: "Run in dry-run mode (no files written)"
26
+
27
+ # Run translation
28
+ #
29
+ # @return [void]
30
+ #
31
+ def run_translation
32
+ config_file = Rails.root.join("config", "better_translate.yml")
33
+
34
+ unless File.exist?(config_file)
35
+ say "Config file not found at #{config_file}", :red
36
+ say "Run 'rails generate better_translate:install' first"
37
+ return
38
+ end
39
+
40
+ # Load configuration from YAML
41
+ yaml_config = YAML.load_file(config_file)
42
+
43
+ # Configure BetterTranslate
44
+ BetterTranslate.configure do |config|
45
+ config.provider = yaml_config["provider"]&.to_sym
46
+ config.openai_key = yaml_config["openai_key"] || ENV["OPENAI_API_KEY"]
47
+ config.gemini_key = yaml_config["gemini_key"] || ENV["GEMINI_API_KEY"]
48
+ config.anthropic_key = yaml_config["anthropic_key"] || ENV["ANTHROPIC_API_KEY"]
49
+
50
+ config.source_language = yaml_config["source_language"]
51
+ config.target_languages = yaml_config["target_languages"]&.map do |lang|
52
+ if lang.is_a?(Hash)
53
+ { short_name: lang["short_name"], name: lang["name"] }
54
+ else
55
+ lang
56
+ end
57
+ end
58
+
59
+ config.input_file = Rails.root.join(yaml_config["input_file"]).to_s
60
+ config.output_folder = Rails.root.join(yaml_config["output_folder"]).to_s
61
+ config.verbose = yaml_config.fetch("verbose", true)
62
+ config.dry_run = options[:dry_run] || yaml_config.fetch("dry_run", false)
63
+
64
+ # Map "full" to :override for backward compatibility
65
+ translation_mode = yaml_config.fetch("translation_mode", "override")
66
+ translation_mode = "override" if translation_mode == "full"
67
+ config.translation_mode = translation_mode.to_sym
68
+
69
+ config.preserve_variables = yaml_config.fetch("preserve_variables", true)
70
+
71
+ # Exclusions
72
+ config.global_exclusions = yaml_config["global_exclusions"] || []
73
+ config.exclusions_per_language = yaml_config["exclusions_per_language"] || {}
74
+
75
+ # Provider options
76
+ config.model = yaml_config["model"] if yaml_config["model"]
77
+ config.temperature = yaml_config["temperature"] if yaml_config["temperature"]
78
+ config.max_tokens = yaml_config["max_tokens"] if yaml_config["max_tokens"]
79
+ config.timeout = yaml_config["timeout"] if yaml_config["timeout"]
80
+ config.max_retries = yaml_config["max_retries"] if yaml_config["max_retries"]
81
+ config.rate_limit = yaml_config["rate_limit"] if yaml_config["rate_limit"]
82
+ end
83
+
84
+ # Perform translation
85
+ say "Starting translation...", :green
86
+ say "Provider: #{BetterTranslate.configuration.provider}"
87
+ say "Source: #{BetterTranslate.configuration.source_language}"
88
+ say "Targets: #{BetterTranslate.configuration.target_languages.map { |l| l[:name] }.join(", ")}"
89
+
90
+ say "DRY RUN MODE - No files will be written", :yellow if options[:dry_run]
91
+
92
+ say ""
93
+
94
+ results = BetterTranslate.translate_files
95
+
96
+ # Report results
97
+ say ""
98
+ say "=" * 60
99
+ say "Translation Complete", :green
100
+ say "=" * 60
101
+ say "Success: #{results[:success_count]}", :green
102
+ say "Failure: #{results[:failure_count]}", results[:failure_count].zero? ? :green : :red
103
+
104
+ return unless results[:errors].any?
105
+
106
+ say ""
107
+ say "Errors:", :red
108
+ results[:errors].each do |error|
109
+ say " - #{error[:language]}: #{error[:error]}", :red
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ # Define :environment task as no-op if it doesn't exist (for non-Rails usage)
6
+ unless Rake::Task.task_defined?(:environment)
7
+ task :environment do
8
+ # No-op for standalone usage
9
+ end
10
+ end
11
+
12
+ namespace :better_translate do
13
+ desc "Translate YAML locale files using AI providers"
14
+ task translate: :environment do
15
+ config_file = Rails.root.join("config", "better_translate.yml")
16
+
17
+ unless File.exist?(config_file)
18
+ puts "Error: Configuration file not found at #{config_file}"
19
+ puts "Run 'rake better_translate:config:generate' to create one."
20
+ exit 1
21
+ end
22
+
23
+ # Load configuration from YAML
24
+ yaml_config = YAML.load_file(config_file)
25
+
26
+ # Configure BetterTranslate
27
+ BetterTranslate.configure do |config|
28
+ config.provider = yaml_config["provider"]&.to_sym
29
+ config.openai_key = yaml_config["openai_key"] || ENV["OPENAI_API_KEY"]
30
+ config.gemini_key = yaml_config["gemini_key"] || ENV["GEMINI_API_KEY"]
31
+ config.anthropic_key = yaml_config["anthropic_key"] || ENV["ANTHROPIC_API_KEY"]
32
+
33
+ config.source_language = yaml_config["source_language"]
34
+ config.target_languages = yaml_config["target_languages"]&.map do |lang|
35
+ if lang.is_a?(Hash)
36
+ { short_name: lang["short_name"], name: lang["name"] }
37
+ else
38
+ lang
39
+ end
40
+ end
41
+
42
+ config.input_file = yaml_config["input_file"]
43
+ config.output_folder = yaml_config["output_folder"]
44
+ config.verbose = yaml_config.fetch("verbose", true)
45
+ config.dry_run = yaml_config.fetch("dry_run", false)
46
+
47
+ # Map "full" to :override for backward compatibility
48
+ translation_mode = yaml_config.fetch("translation_mode", "override")
49
+ translation_mode = "override" if translation_mode == "full"
50
+ config.translation_mode = translation_mode.to_sym
51
+
52
+ config.preserve_variables = yaml_config.fetch("preserve_variables", true)
53
+
54
+ # Exclusions
55
+ config.global_exclusions = yaml_config["global_exclusions"] || []
56
+ config.exclusions_per_language = yaml_config["exclusions_per_language"] || {}
57
+
58
+ # Provider options
59
+ config.model = yaml_config["model"] if yaml_config["model"]
60
+ config.temperature = yaml_config["temperature"] if yaml_config["temperature"]
61
+ config.max_tokens = yaml_config["max_tokens"] if yaml_config["max_tokens"]
62
+ config.timeout = yaml_config["timeout"] if yaml_config["timeout"]
63
+ config.max_retries = yaml_config["max_retries"] if yaml_config["max_retries"]
64
+ config.rate_limit = yaml_config["rate_limit"] if yaml_config["rate_limit"]
65
+ end
66
+
67
+ # Perform translation
68
+ puts "Starting translation..."
69
+ puts "Provider: #{BetterTranslate.configuration.provider}"
70
+ puts "Source: #{BetterTranslate.configuration.source_language}"
71
+ puts "Targets: #{BetterTranslate.configuration.target_languages.map { |l| l[:name] }.join(", ")}"
72
+ puts
73
+
74
+ results = BetterTranslate.translate_files
75
+
76
+ # Report results
77
+ puts
78
+ puts "=" * 60
79
+ puts "Translation Complete"
80
+ puts "=" * 60
81
+ puts "Success: #{results[:success_count]}"
82
+ puts "Failure: #{results[:failure_count]}"
83
+
84
+ if results[:errors].any?
85
+ puts
86
+ puts "Errors:"
87
+ results[:errors].each do |error|
88
+ puts " - #{error[:language]}: #{error[:error]}"
89
+ end
90
+ end
91
+ end
92
+
93
+ namespace :config do
94
+ desc "Generate sample configuration file"
95
+ task generate: :environment do
96
+ config_file = Rails.root.join("config", "better_translate.yml")
97
+
98
+ if File.exist?(config_file)
99
+ puts "Configuration file already exists at #{config_file}"
100
+ puts "Delete it first if you want to regenerate."
101
+ next
102
+ end
103
+
104
+ sample_config = {
105
+ "provider" => "chatgpt",
106
+ "openai_key" => "YOUR_OPENAI_API_KEY",
107
+ "gemini_key" => "YOUR_GEMINI_API_KEY",
108
+ "anthropic_key" => "YOUR_ANTHROPIC_API_KEY",
109
+ "source_language" => "en",
110
+ "target_languages" => [
111
+ { "short_name" => "it", "name" => "Italian" },
112
+ { "short_name" => "es", "name" => "Spanish" },
113
+ { "short_name" => "fr", "name" => "French" }
114
+ ],
115
+ "input_file" => "config/locales/en.yml",
116
+ "output_folder" => "config/locales",
117
+ "verbose" => true,
118
+ "dry_run" => false,
119
+ "translation_mode" => "full",
120
+ "preserve_variables" => true,
121
+ "global_exclusions" => [],
122
+ "exclusions_per_language" => {},
123
+ "model" => nil,
124
+ "temperature" => 0.3,
125
+ "max_tokens" => 2000,
126
+ "timeout" => 30,
127
+ "max_retries" => 3,
128
+ "rate_limit" => 10
129
+ }
130
+
131
+ File.write(config_file, sample_config.to_yaml)
132
+ puts "Generated configuration file at #{config_file}"
133
+ puts "Please edit it with your API keys and preferences."
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,28 @@
1
+ module BetterTranslate
2
+ # LRU (Least Recently Used) Cache implementation
3
+ #
4
+ # Thread-safe cache with configurable capacity and optional TTL.
5
+ class Cache
6
+ type cache_entry = { value: String, timestamp: Time }
7
+
8
+ @capacity: Integer
9
+ @ttl: Integer?
10
+ @cache: Hash[String, cache_entry]
11
+ @mutex: Thread::Mutex
12
+
13
+ attr_reader capacity: Integer
14
+ attr_reader ttl: Integer?
15
+
16
+ def initialize: (?capacity: Integer, ?ttl: Integer?) -> void
17
+
18
+ def get: (String key) -> String?
19
+
20
+ def set: (String key, String value) -> String
21
+
22
+ def clear: () -> void
23
+
24
+ def size: () -> Integer
25
+
26
+ def key?: (String key) -> bool
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module BetterTranslate
2
+ # Command-line interface for BetterTranslate
3
+ #
4
+ # Provides standalone CLI commands for translation without Rails.
5
+ class CLI
6
+ @args: Array[String]
7
+
8
+ attr_reader args: Array[String]
9
+
10
+ def initialize: (Array[String] args) -> void
11
+
12
+ def run: () -> void
13
+
14
+ private
15
+
16
+ def show_help: () -> void
17
+
18
+ def run_translate: () -> void
19
+
20
+ def run_generate: () -> void
21
+
22
+ def run_direct: () -> void
23
+ end
24
+ end
@@ -0,0 +1,78 @@
1
+ module BetterTranslate
2
+ # Configuration class for BetterTranslate
3
+ #
4
+ # Manages all configuration options with type safety and validation.
5
+ class Configuration
6
+ type target_language = { short_name: String, name: String }
7
+
8
+ @provider: Symbol?
9
+ @openai_key: String?
10
+ @google_gemini_key: String?
11
+ @anthropic_key: String?
12
+ @source_language: String?
13
+ @target_languages: Array[target_language]
14
+ @input_file: String?
15
+ @output_folder: String?
16
+ @translation_mode: Symbol
17
+ @translation_context: String?
18
+ @max_concurrent_requests: Integer
19
+ @request_timeout: Integer
20
+ @max_retries: Integer
21
+ @retry_delay: Float
22
+ @cache_enabled: bool
23
+ @cache_size: Integer
24
+ @cache_ttl: Integer?
25
+ @verbose: bool
26
+ @dry_run: bool
27
+ @global_exclusions: Array[String]
28
+ @exclusions_per_language: Hash[String, Array[String]]
29
+ @preserve_variables: bool
30
+
31
+ attr_accessor provider: (Symbol | nil)
32
+ attr_accessor openai_key: String?
33
+ attr_accessor google_gemini_key: String?
34
+ attr_accessor gemini_key: String?
35
+ attr_accessor anthropic_key: String?
36
+ attr_accessor source_language: String?
37
+ attr_accessor target_languages: Array[target_language]
38
+ attr_accessor input_file: String?
39
+ attr_accessor output_folder: String?
40
+ attr_accessor translation_mode: Symbol
41
+ attr_accessor translation_context: String?
42
+ attr_accessor max_concurrent_requests: Integer
43
+ attr_accessor request_timeout: Integer
44
+ attr_accessor max_retries: Integer
45
+ attr_accessor retry_delay: Float
46
+ attr_accessor cache_enabled: bool
47
+ attr_accessor cache_size: Integer
48
+ attr_accessor cache_ttl: Integer?
49
+ attr_accessor verbose: bool
50
+ attr_accessor dry_run: bool
51
+ attr_accessor global_exclusions: Array[String]
52
+ attr_accessor exclusions_per_language: Hash[String, Array[String]]
53
+ attr_accessor preserve_variables: bool
54
+
55
+ # Provider-specific options
56
+ attr_accessor model: String?
57
+ attr_accessor temperature: Float?
58
+ attr_accessor max_tokens: Integer?
59
+ attr_accessor timeout: Integer?
60
+ attr_accessor rate_limit: (Integer | Float)?
61
+
62
+ def initialize: () -> void
63
+
64
+ def validate!: () -> true
65
+
66
+ private
67
+
68
+ def validate_provider!: () -> void
69
+
70
+ def validate_api_keys!: () -> void
71
+
72
+ def validate_languages!: () -> void
73
+
74
+ def validate_files!: () -> void
75
+
76
+ def validate_optional_settings!: () -> void
77
+ end
78
+ end
@@ -0,0 +1,18 @@
1
+ module BetterTranslate
2
+ # Direct text translator for non-YAML workflows
3
+ #
4
+ # Provides convenience methods for translating individual strings or batches
5
+ # without requiring YAML files. Useful for runtime translation needs.
6
+ class DirectTranslator
7
+ @config: Configuration
8
+ @provider: Providers::BaseHttpProvider
9
+
10
+ attr_reader config: Configuration
11
+
12
+ def initialize: (Configuration config) -> void
13
+
14
+ def translate: (String text, to: (String | Symbol), language_name: String) -> String
15
+
16
+ def translate_batch: (Array[String] texts, to: (String | Symbol), language_name: String, ?skip_errors: bool) -> Array[String?]
17
+ end
18
+ end