better_translate 1.0.0.1 → 1.1.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 +4 -4
- data/.rubocop.yml +28 -0
- data/.rubocop_todo.yml +291 -0
- data/CHANGELOG.md +88 -0
- data/CLAUDE.md +12 -7
- data/CONTRIBUTING.md +432 -0
- data/README.md +240 -1
- data/Rakefile +14 -1
- data/SECURITY.md +160 -0
- data/Steepfile +0 -1
- data/brakeman.yml +37 -0
- data/codecov.yml +34 -0
- data/lib/better_translate/analyzer/code_scanner.rb +149 -0
- data/lib/better_translate/analyzer/key_scanner.rb +109 -0
- data/lib/better_translate/analyzer/orphan_detector.rb +91 -0
- data/lib/better_translate/analyzer/reporter.rb +155 -0
- data/lib/better_translate/cli.rb +81 -2
- data/lib/better_translate/configuration.rb +76 -3
- data/lib/better_translate/errors.rb +9 -0
- data/lib/better_translate/json_handler.rb +227 -0
- data/lib/better_translate/translator.rb +205 -23
- data/lib/better_translate/version.rb +1 -1
- data/lib/better_translate/yaml_handler.rb +59 -0
- data/lib/better_translate.rb +7 -0
- data/lib/generators/better_translate/install/install_generator.rb +2 -2
- data/lib/generators/better_translate/install/templates/initializer.rb.tt +22 -34
- data/lib/generators/better_translate/translate/translate_generator.rb +65 -46
- data/lib/tasks/better_translate.rake +62 -45
- data/sig/better_translate/analyzer/code_scanner.rbs +59 -0
- data/sig/better_translate/analyzer/key_scanner.rbs +40 -0
- data/sig/better_translate/analyzer/orphan_detector.rbs +43 -0
- data/sig/better_translate/analyzer/reporter.rbs +70 -0
- data/sig/better_translate/cli.rbs +2 -0
- data/sig/better_translate/configuration.rbs +6 -0
- data/sig/better_translate/errors.rbs +4 -0
- data/sig/better_translate/json_handler.rbs +65 -0
- data/sig/better_translate/progress_tracker.rbs +1 -1
- data/sig/better_translate/translator.rbs +12 -1
- data/sig/better_translate/yaml_handler.rbs +6 -0
- data/sig/better_translate.rbs +4 -0
- data/sig/csv.rbs +16 -0
- metadata +32 -3
- data/regenerate_vcr.rb +0 -47
|
@@ -9,59 +9,75 @@ unless Rake::Task.task_defined?(:environment)
|
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# rubocop:disable Metrics/BlockLength
|
|
12
13
|
namespace :better_translate do
|
|
13
14
|
desc "Translate YAML locale files using AI providers"
|
|
14
15
|
task translate: :environment do
|
|
15
|
-
|
|
16
|
+
# Check if configuration is already loaded (from initializer)
|
|
17
|
+
if BetterTranslate.configuration.provider.nil?
|
|
18
|
+
# No initializer configuration found, try loading from YAML
|
|
19
|
+
config_file = Rails.root.join("config", "better_translate.yml")
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
unless File.exist?(config_file)
|
|
22
|
+
puts "Error: No configuration found"
|
|
23
|
+
puts "Either:"
|
|
24
|
+
puts " 1. Create config/initializers/better_translate.rb (recommended)"
|
|
25
|
+
puts " 2. Run 'rake better_translate:config:generate' to create YAML config"
|
|
26
|
+
exit 1
|
|
27
|
+
end
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
# Load configuration from YAML
|
|
30
|
+
yaml_config = YAML.load_file(config_file)
|
|
31
|
+
|
|
32
|
+
# Configure BetterTranslate
|
|
33
|
+
BetterTranslate.configure do |config|
|
|
34
|
+
config.provider = yaml_config["provider"]&.to_sym
|
|
35
|
+
config.openai_key = yaml_config["openai_key"] || ENV["OPENAI_API_KEY"]
|
|
36
|
+
config.gemini_key = yaml_config["gemini_key"] || ENV["GEMINI_API_KEY"]
|
|
37
|
+
config.anthropic_key = yaml_config["anthropic_key"] || ENV["ANTHROPIC_API_KEY"]
|
|
38
|
+
|
|
39
|
+
config.source_language = yaml_config["source_language"]
|
|
40
|
+
config.target_languages = yaml_config["target_languages"]&.map do |lang|
|
|
41
|
+
if lang.is_a?(Hash)
|
|
42
|
+
{ short_name: lang["short_name"], name: lang["name"] }
|
|
43
|
+
else
|
|
44
|
+
lang
|
|
45
|
+
end
|
|
39
46
|
end
|
|
47
|
+
|
|
48
|
+
config.input_file = yaml_config["input_file"]
|
|
49
|
+
config.output_folder = yaml_config["output_folder"]
|
|
50
|
+
config.verbose = yaml_config.fetch("verbose", true)
|
|
51
|
+
config.dry_run = yaml_config.fetch("dry_run", false)
|
|
52
|
+
|
|
53
|
+
# Map "full" to :override for backward compatibility
|
|
54
|
+
translation_mode = yaml_config.fetch("translation_mode", "override")
|
|
55
|
+
translation_mode = "override" if translation_mode == "full"
|
|
56
|
+
config.translation_mode = translation_mode.to_sym
|
|
57
|
+
|
|
58
|
+
config.preserve_variables = yaml_config.fetch("preserve_variables", true)
|
|
59
|
+
|
|
60
|
+
# Exclusions
|
|
61
|
+
config.global_exclusions = yaml_config["global_exclusions"] || []
|
|
62
|
+
config.exclusions_per_language = yaml_config["exclusions_per_language"] || {}
|
|
63
|
+
|
|
64
|
+
# Provider options
|
|
65
|
+
config.model = yaml_config["model"] if yaml_config["model"]
|
|
66
|
+
config.temperature = yaml_config["temperature"] if yaml_config["temperature"]
|
|
67
|
+
config.max_tokens = yaml_config["max_tokens"] if yaml_config["max_tokens"]
|
|
68
|
+
config.timeout = yaml_config["timeout"] if yaml_config["timeout"]
|
|
69
|
+
config.max_retries = yaml_config["max_retries"] if yaml_config["max_retries"]
|
|
70
|
+
config.rate_limit = yaml_config["rate_limit"] if yaml_config["rate_limit"]
|
|
40
71
|
end
|
|
72
|
+
end
|
|
41
73
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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"]
|
|
74
|
+
# Validate configuration (whether from initializer or YAML)
|
|
75
|
+
begin
|
|
76
|
+
BetterTranslate.configuration.validate!
|
|
77
|
+
rescue BetterTranslate::ConfigurationError => e
|
|
78
|
+
puts "Error: Invalid configuration"
|
|
79
|
+
puts e.message
|
|
80
|
+
exit 1
|
|
65
81
|
end
|
|
66
82
|
|
|
67
83
|
# Perform translation
|
|
@@ -134,3 +150,4 @@ namespace :better_translate do
|
|
|
134
150
|
end
|
|
135
151
|
end
|
|
136
152
|
end
|
|
153
|
+
# rubocop:enable Metrics/BlockLength
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterTranslate
|
|
4
|
+
module Analyzer
|
|
5
|
+
# Scans code files to find i18n key references
|
|
6
|
+
class CodeScanner
|
|
7
|
+
# I18n patterns to match
|
|
8
|
+
I18N_PATTERNS: Array[Regexp]
|
|
9
|
+
|
|
10
|
+
# File extensions to scan
|
|
11
|
+
SCANNABLE_EXTENSIONS: Array[String]
|
|
12
|
+
|
|
13
|
+
# Path to scan (file or directory)
|
|
14
|
+
attr_reader path: String
|
|
15
|
+
|
|
16
|
+
# Found i18n keys
|
|
17
|
+
attr_reader keys: Set[String]
|
|
18
|
+
|
|
19
|
+
# List of scanned files
|
|
20
|
+
attr_reader files_scanned: Array[String]
|
|
21
|
+
|
|
22
|
+
# Initialize scanner with path
|
|
23
|
+
#
|
|
24
|
+
# @param path File or directory path to scan
|
|
25
|
+
def initialize: (String path) -> void
|
|
26
|
+
|
|
27
|
+
# Scan path and extract i18n keys
|
|
28
|
+
#
|
|
29
|
+
# @return Set of found i18n keys
|
|
30
|
+
def scan: () -> Set[String]
|
|
31
|
+
|
|
32
|
+
# Get count of unique keys found
|
|
33
|
+
#
|
|
34
|
+
# @return Number of unique keys
|
|
35
|
+
def key_count: () -> Integer
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Validate that path exists
|
|
40
|
+
def validate_path!: () -> void
|
|
41
|
+
|
|
42
|
+
# Collect all scannable files from path
|
|
43
|
+
#
|
|
44
|
+
# @return List of file paths
|
|
45
|
+
def collect_files: () -> Array[String]
|
|
46
|
+
|
|
47
|
+
# Check if file should be scanned
|
|
48
|
+
#
|
|
49
|
+
# @param file File path
|
|
50
|
+
# @return Boolean
|
|
51
|
+
def scannable_file?: (String file) -> bool
|
|
52
|
+
|
|
53
|
+
# Scan single file and extract keys
|
|
54
|
+
#
|
|
55
|
+
# @param file File path
|
|
56
|
+
def scan_file: (String file) -> void
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterTranslate
|
|
4
|
+
module Analyzer
|
|
5
|
+
# Scans YAML translation files and extracts all keys in flatten format
|
|
6
|
+
class KeyScanner
|
|
7
|
+
# Path to the YAML file
|
|
8
|
+
attr_reader file_path: String
|
|
9
|
+
|
|
10
|
+
# Flatten keys extracted from YAML
|
|
11
|
+
attr_reader keys: Hash[String, untyped]
|
|
12
|
+
|
|
13
|
+
# Initialize scanner with YAML file path
|
|
14
|
+
#
|
|
15
|
+
# @param file_path Path to YAML file
|
|
16
|
+
def initialize: (String file_path) -> void
|
|
17
|
+
|
|
18
|
+
# Scan YAML file and extract all flatten keys
|
|
19
|
+
#
|
|
20
|
+
# @return Flatten keys with their values
|
|
21
|
+
def scan: () -> Hash[String, untyped]
|
|
22
|
+
|
|
23
|
+
# Get total count of keys
|
|
24
|
+
#
|
|
25
|
+
# @return Number of keys
|
|
26
|
+
def key_count: () -> Integer
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Validate that file exists
|
|
31
|
+
def validate_file!: () -> void
|
|
32
|
+
|
|
33
|
+
# Flatten nested hash into dot-notation keys
|
|
34
|
+
#
|
|
35
|
+
# @param hash Nested hash to flatten
|
|
36
|
+
# @param prefix Prefix for current level
|
|
37
|
+
def flatten_keys: (Hash[untyped, untyped] hash, ?String? prefix) -> void
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterTranslate
|
|
4
|
+
module Analyzer
|
|
5
|
+
# Detects orphan i18n keys (keys defined but never used in code)
|
|
6
|
+
class OrphanDetector
|
|
7
|
+
# All translation keys with their values
|
|
8
|
+
attr_reader all_keys: Hash[String, untyped]
|
|
9
|
+
|
|
10
|
+
# Keys that are used in code
|
|
11
|
+
attr_reader used_keys: Set[String]
|
|
12
|
+
|
|
13
|
+
# Orphan keys (not used in code)
|
|
14
|
+
attr_reader orphans: Array[String]
|
|
15
|
+
|
|
16
|
+
# Initialize detector
|
|
17
|
+
#
|
|
18
|
+
# @param all_keys All translation keys from YAML files
|
|
19
|
+
# @param used_keys Keys found in code
|
|
20
|
+
def initialize: (Hash[String, untyped] all_keys, Set[String] used_keys) -> void
|
|
21
|
+
|
|
22
|
+
# Detect orphan keys
|
|
23
|
+
#
|
|
24
|
+
# @return List of orphan key names
|
|
25
|
+
def detect: () -> Array[String]
|
|
26
|
+
|
|
27
|
+
# Get count of orphan keys
|
|
28
|
+
#
|
|
29
|
+
# @return Number of orphan keys
|
|
30
|
+
def orphan_count: () -> Integer
|
|
31
|
+
|
|
32
|
+
# Get details of orphan keys with their values
|
|
33
|
+
#
|
|
34
|
+
# @return Hash of orphan keys and their translation values
|
|
35
|
+
def orphan_details: () -> Hash[String, untyped]
|
|
36
|
+
|
|
37
|
+
# Calculate usage percentage
|
|
38
|
+
#
|
|
39
|
+
# @return Percentage of keys that are used (0.0 to 100.0)
|
|
40
|
+
def usage_percentage: () -> Float
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterTranslate
|
|
4
|
+
module Analyzer
|
|
5
|
+
# Generates reports for orphan key analysis
|
|
6
|
+
class Reporter
|
|
7
|
+
# List of orphan keys
|
|
8
|
+
attr_reader orphans: Array[String]
|
|
9
|
+
|
|
10
|
+
# Orphan keys with their values
|
|
11
|
+
attr_reader orphan_details: Hash[String, untyped]
|
|
12
|
+
|
|
13
|
+
# Total number of keys
|
|
14
|
+
attr_reader total_keys: Integer
|
|
15
|
+
|
|
16
|
+
# Number of used keys
|
|
17
|
+
attr_reader used_keys: Integer
|
|
18
|
+
|
|
19
|
+
# Usage percentage
|
|
20
|
+
attr_reader usage_percentage: Float
|
|
21
|
+
|
|
22
|
+
# Output format (:text, :json, :csv)
|
|
23
|
+
attr_reader format: Symbol
|
|
24
|
+
|
|
25
|
+
# Initialize reporter
|
|
26
|
+
#
|
|
27
|
+
# @param orphans List of orphan keys
|
|
28
|
+
# @param orphan_details Orphan keys with values
|
|
29
|
+
# @param total_keys Total number of keys
|
|
30
|
+
# @param used_keys Number of used keys
|
|
31
|
+
# @param usage_percentage Usage percentage
|
|
32
|
+
# @param format Output format (:text, :json, :csv)
|
|
33
|
+
def initialize: (
|
|
34
|
+
orphans: Array[String],
|
|
35
|
+
orphan_details: Hash[String, untyped],
|
|
36
|
+
total_keys: Integer,
|
|
37
|
+
used_keys: Integer,
|
|
38
|
+
usage_percentage: Float,
|
|
39
|
+
?format: Symbol
|
|
40
|
+
) -> void
|
|
41
|
+
|
|
42
|
+
# Generate report in specified format
|
|
43
|
+
#
|
|
44
|
+
# @return Generated report
|
|
45
|
+
def generate: () -> String
|
|
46
|
+
|
|
47
|
+
# Save report to file
|
|
48
|
+
#
|
|
49
|
+
# @param file_path Output file path
|
|
50
|
+
def save_to_file: (String file_path) -> void
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Generate text format report
|
|
55
|
+
#
|
|
56
|
+
# @return Text report
|
|
57
|
+
def generate_text: () -> String
|
|
58
|
+
|
|
59
|
+
# Generate JSON format report
|
|
60
|
+
#
|
|
61
|
+
# @return JSON report
|
|
62
|
+
def generate_json: () -> String
|
|
63
|
+
|
|
64
|
+
# Generate CSV format report
|
|
65
|
+
#
|
|
66
|
+
# @return CSV report
|
|
67
|
+
def generate_csv: () -> String
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -27,6 +27,9 @@ module BetterTranslate
|
|
|
27
27
|
@global_exclusions: Array[String]
|
|
28
28
|
@exclusions_per_language: Hash[String, Array[String]]
|
|
29
29
|
@preserve_variables: bool
|
|
30
|
+
@input_files: (String | Array[String])?
|
|
31
|
+
@create_backup: bool
|
|
32
|
+
@max_backups: Integer
|
|
30
33
|
|
|
31
34
|
attr_accessor provider: (Symbol | nil)
|
|
32
35
|
attr_accessor openai_key: String?
|
|
@@ -56,6 +59,9 @@ module BetterTranslate
|
|
|
56
59
|
attr_accessor global_exclusions: Array[String]
|
|
57
60
|
attr_accessor exclusions_per_language: Hash[String, Array[String]]
|
|
58
61
|
attr_accessor preserve_variables: bool
|
|
62
|
+
attr_accessor input_files: (String | Array[String])?
|
|
63
|
+
attr_accessor create_backup: bool
|
|
64
|
+
attr_accessor max_backups: Integer
|
|
59
65
|
|
|
60
66
|
# Provider-specific options
|
|
61
67
|
attr_accessor model: String?
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterTranslate
|
|
4
|
+
# Handles JSON file operations
|
|
5
|
+
class JsonHandler
|
|
6
|
+
# Configuration object
|
|
7
|
+
attr_reader config: Configuration
|
|
8
|
+
|
|
9
|
+
# Initialize JSON handler
|
|
10
|
+
#
|
|
11
|
+
# @param config Configuration object
|
|
12
|
+
def initialize: (Configuration config) -> void
|
|
13
|
+
|
|
14
|
+
# Read and parse JSON file
|
|
15
|
+
#
|
|
16
|
+
# @param file_path Path to JSON file
|
|
17
|
+
# @return Parsed JSON content
|
|
18
|
+
def read_json: (String file_path) -> Hash[untyped, untyped]
|
|
19
|
+
|
|
20
|
+
# Write hash to JSON file
|
|
21
|
+
#
|
|
22
|
+
# @param file_path Output file path
|
|
23
|
+
# @param data Data to write
|
|
24
|
+
# @param diff_preview Optional diff preview instance
|
|
25
|
+
# @return Summary hash if dry_run, nil otherwise
|
|
26
|
+
def write_json: (String file_path, Hash[untyped, untyped] data, ?diff_preview: untyped) -> (Hash[Symbol, untyped] | nil)
|
|
27
|
+
|
|
28
|
+
# Get translatable strings from source JSON
|
|
29
|
+
#
|
|
30
|
+
# @return Flattened hash of translatable strings
|
|
31
|
+
def get_source_strings: () -> Hash[String, untyped]
|
|
32
|
+
|
|
33
|
+
# Filter out excluded keys for a specific language
|
|
34
|
+
#
|
|
35
|
+
# @param strings Flattened strings
|
|
36
|
+
# @param target_lang_code Target language code
|
|
37
|
+
# @return Filtered strings
|
|
38
|
+
def filter_exclusions: (Hash[String, untyped] strings, String target_lang_code) -> Hash[String, untyped]
|
|
39
|
+
|
|
40
|
+
# Merge translated strings with existing file (incremental mode)
|
|
41
|
+
#
|
|
42
|
+
# @param file_path Existing file path
|
|
43
|
+
# @param new_translations New translations (flattened)
|
|
44
|
+
# @return Merged translations (nested)
|
|
45
|
+
def merge_translations: (String file_path, Hash[String, untyped] new_translations) -> Hash[String, untyped]
|
|
46
|
+
|
|
47
|
+
# Build output file path for target language
|
|
48
|
+
#
|
|
49
|
+
# @param target_lang_code Target language code
|
|
50
|
+
# @return Output file path
|
|
51
|
+
def build_output_path: (String target_lang_code) -> String
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Create backup file with rotation support
|
|
56
|
+
#
|
|
57
|
+
# @param file_path Path to file to backup
|
|
58
|
+
def create_backup_file: (String file_path) -> void
|
|
59
|
+
|
|
60
|
+
# Rotate backup files, keeping only max_backups
|
|
61
|
+
#
|
|
62
|
+
# @param file_path Base file path
|
|
63
|
+
def rotate_backups: (String file_path) -> void
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -10,6 +10,7 @@ module BetterTranslate
|
|
|
10
10
|
@provider: Providers::BaseHttpProvider
|
|
11
11
|
@yaml_handler: YAMLHandler
|
|
12
12
|
@progress_tracker: ProgressTracker
|
|
13
|
+
@input_files: Array[String]
|
|
13
14
|
|
|
14
15
|
attr_reader config: Configuration
|
|
15
16
|
|
|
@@ -19,6 +20,16 @@ module BetterTranslate
|
|
|
19
20
|
|
|
20
21
|
private
|
|
21
22
|
|
|
22
|
-
def
|
|
23
|
+
def resolve_input_files: () -> Array[String]
|
|
24
|
+
|
|
25
|
+
def translate_parallel: (Hash[String, untyped] source_strings, (YAMLHandler | JsonHandler) handler) -> translation_results
|
|
26
|
+
|
|
27
|
+
def translate_sequential: (Hash[String, untyped] source_strings, (YAMLHandler | JsonHandler) handler) -> translation_results
|
|
28
|
+
|
|
29
|
+
def translate_language: (Hash[String, untyped] source_strings, Hash[Symbol, String] lang, (YAMLHandler | JsonHandler) handler) -> void
|
|
30
|
+
|
|
31
|
+
def record_error: (translation_results results, Hash[Symbol, String] lang, Exception error) -> void
|
|
32
|
+
|
|
33
|
+
def build_output_path_for_file: (String input_file, String target_lang_code) -> String
|
|
23
34
|
end
|
|
24
35
|
end
|
|
@@ -25,5 +25,11 @@ module BetterTranslate
|
|
|
25
25
|
def merge_translations: (String file_path, Hash[String, untyped] new_translations) -> Hash[String, untyped]
|
|
26
26
|
|
|
27
27
|
def build_output_path: (String target_lang_code) -> String
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def create_backup_file: (String file_path) -> void
|
|
32
|
+
|
|
33
|
+
def rotate_backups: (String file_path) -> void
|
|
28
34
|
end
|
|
29
35
|
end
|
data/sig/better_translate.rbs
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
module BetterTranslate
|
|
6
6
|
type translation_results = { success_count: Integer, failure_count: Integer, errors: Array[Hash[Symbol, untyped]] }
|
|
7
7
|
|
|
8
|
+
# Analyzer module for translation file analysis
|
|
9
|
+
module Analyzer
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
# Module-level instance variable (for singleton methods)
|
|
9
13
|
self.@configuration: Configuration?
|
|
10
14
|
|
data/sig/csv.rbs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# CSV library type signatures
|
|
4
|
+
class CSV
|
|
5
|
+
# Generate CSV string
|
|
6
|
+
#
|
|
7
|
+
# @yield CSV instance for writing rows
|
|
8
|
+
# @return CSV string
|
|
9
|
+
def self.generate: () { (CSV) -> void } -> String
|
|
10
|
+
|
|
11
|
+
# Add row to CSV
|
|
12
|
+
#
|
|
13
|
+
# @param row Array of values
|
|
14
|
+
# @return CSV instance
|
|
15
|
+
def <<: (Array[untyped]) -> CSV
|
|
16
|
+
end
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_translate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- alessiobussolari
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: csv
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: faraday
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -36,15 +50,20 @@ files:
|
|
|
36
50
|
- ".env.example"
|
|
37
51
|
- ".rspec"
|
|
38
52
|
- ".rubocop.yml"
|
|
53
|
+
- ".rubocop_todo.yml"
|
|
39
54
|
- ".yardopts"
|
|
40
55
|
- CHANGELOG.md
|
|
41
56
|
- CLAUDE.md
|
|
42
57
|
- CODE_OF_CONDUCT.md
|
|
58
|
+
- CONTRIBUTING.md
|
|
43
59
|
- LICENSE.txt
|
|
44
60
|
- README.md
|
|
45
61
|
- RELEASE_NOTES_v1.0.0.md
|
|
46
62
|
- Rakefile
|
|
63
|
+
- SECURITY.md
|
|
47
64
|
- Steepfile
|
|
65
|
+
- brakeman.yml
|
|
66
|
+
- codecov.yml
|
|
48
67
|
- docs/implementation/00-overview.md
|
|
49
68
|
- docs/implementation/01-setup_dependencies.md
|
|
50
69
|
- docs/implementation/02-error_handling.md
|
|
@@ -61,11 +80,16 @@ files:
|
|
|
61
80
|
- docs/implementation/12-cli_standalone.md
|
|
62
81
|
- exe/better_translate
|
|
63
82
|
- lib/better_translate.rb
|
|
83
|
+
- lib/better_translate/analyzer/code_scanner.rb
|
|
84
|
+
- lib/better_translate/analyzer/key_scanner.rb
|
|
85
|
+
- lib/better_translate/analyzer/orphan_detector.rb
|
|
86
|
+
- lib/better_translate/analyzer/reporter.rb
|
|
64
87
|
- lib/better_translate/cache.rb
|
|
65
88
|
- lib/better_translate/cli.rb
|
|
66
89
|
- lib/better_translate/configuration.rb
|
|
67
90
|
- lib/better_translate/direct_translator.rb
|
|
68
91
|
- lib/better_translate/errors.rb
|
|
92
|
+
- lib/better_translate/json_handler.rb
|
|
69
93
|
- lib/better_translate/progress_tracker.rb
|
|
70
94
|
- lib/better_translate/provider_factory.rb
|
|
71
95
|
- lib/better_translate/providers/anthropic_provider.rb
|
|
@@ -93,13 +117,17 @@ files:
|
|
|
93
117
|
- lib/generators/better_translate/translate/USAGE
|
|
94
118
|
- lib/generators/better_translate/translate/translate_generator.rb
|
|
95
119
|
- lib/tasks/better_translate.rake
|
|
96
|
-
- regenerate_vcr.rb
|
|
97
120
|
- sig/better_translate.rbs
|
|
121
|
+
- sig/better_translate/analyzer/code_scanner.rbs
|
|
122
|
+
- sig/better_translate/analyzer/key_scanner.rbs
|
|
123
|
+
- sig/better_translate/analyzer/orphan_detector.rbs
|
|
124
|
+
- sig/better_translate/analyzer/reporter.rbs
|
|
98
125
|
- sig/better_translate/cache.rbs
|
|
99
126
|
- sig/better_translate/cli.rbs
|
|
100
127
|
- sig/better_translate/configuration.rbs
|
|
101
128
|
- sig/better_translate/direct_translator.rbs
|
|
102
129
|
- sig/better_translate/errors.rbs
|
|
130
|
+
- sig/better_translate/json_handler.rbs
|
|
103
131
|
- sig/better_translate/progress_tracker.rbs
|
|
104
132
|
- sig/better_translate/provider_factory.rbs
|
|
105
133
|
- sig/better_translate/providers/anthropic_provider.rbs
|
|
@@ -118,6 +146,7 @@ files:
|
|
|
118
146
|
- sig/better_translate/variable_extractor.rbs
|
|
119
147
|
- sig/better_translate/version.rbs
|
|
120
148
|
- sig/better_translate/yaml_handler.rbs
|
|
149
|
+
- sig/csv.rbs
|
|
121
150
|
- sig/faraday.rbs
|
|
122
151
|
- sig/generators/better_translate/analyze/analyze_generator.rbs
|
|
123
152
|
- sig/generators/better_translate/install/install_generator.rbs
|
data/regenerate_vcr.rb
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
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
|