better_translate 1.1.0 → 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/CLAUDE.md +12 -7
- data/CONTRIBUTING.md +432 -0
- data/README.md +7 -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 +0 -2
- data/lib/better_translate/analyzer/orphan_detector.rb +6 -3
- data/lib/better_translate/analyzer/reporter.rb +1 -1
- data/lib/better_translate/cli.rb +2 -2
- data/lib/better_translate/configuration.rb +28 -1
- data/lib/better_translate/json_handler.rb +2 -2
- data/lib/better_translate/translator.rb +3 -2
- data/lib/better_translate/version.rb +1 -1
- data/lib/better_translate.rb +2 -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/json_handler.rbs +65 -0
- data/sig/better_translate/progress_tracker.rbs +1 -1
- data/sig/better_translate.rbs +4 -0
- data/sig/csv.rbs +16 -0
- metadata +26 -3
- data/regenerate_vcr.rb +0 -47
| @@ -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
         | 
| @@ -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
         | 
    
        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.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
         | 
| @@ -41,11 +55,15 @@ files: | |
| 41 55 | 
             
            - CHANGELOG.md
         | 
| 42 56 | 
             
            - CLAUDE.md
         | 
| 43 57 | 
             
            - CODE_OF_CONDUCT.md
         | 
| 58 | 
            +
            - CONTRIBUTING.md
         | 
| 44 59 | 
             
            - LICENSE.txt
         | 
| 45 60 | 
             
            - README.md
         | 
| 46 61 | 
             
            - RELEASE_NOTES_v1.0.0.md
         | 
| 47 62 | 
             
            - Rakefile
         | 
| 63 | 
            +
            - SECURITY.md
         | 
| 48 64 | 
             
            - Steepfile
         | 
| 65 | 
            +
            - brakeman.yml
         | 
| 66 | 
            +
            - codecov.yml
         | 
| 49 67 | 
             
            - docs/implementation/00-overview.md
         | 
| 50 68 | 
             
            - docs/implementation/01-setup_dependencies.md
         | 
| 51 69 | 
             
            - docs/implementation/02-error_handling.md
         | 
| @@ -99,13 +117,17 @@ files: | |
| 99 117 | 
             
            - lib/generators/better_translate/translate/USAGE
         | 
| 100 118 | 
             
            - lib/generators/better_translate/translate/translate_generator.rb
         | 
| 101 119 | 
             
            - lib/tasks/better_translate.rake
         | 
| 102 | 
            -
            - regenerate_vcr.rb
         | 
| 103 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
         | 
| 104 125 | 
             
            - sig/better_translate/cache.rbs
         | 
| 105 126 | 
             
            - sig/better_translate/cli.rbs
         | 
| 106 127 | 
             
            - sig/better_translate/configuration.rbs
         | 
| 107 128 | 
             
            - sig/better_translate/direct_translator.rbs
         | 
| 108 129 | 
             
            - sig/better_translate/errors.rbs
         | 
| 130 | 
            +
            - sig/better_translate/json_handler.rbs
         | 
| 109 131 | 
             
            - sig/better_translate/progress_tracker.rbs
         | 
| 110 132 | 
             
            - sig/better_translate/provider_factory.rbs
         | 
| 111 133 | 
             
            - sig/better_translate/providers/anthropic_provider.rbs
         | 
| @@ -124,6 +146,7 @@ files: | |
| 124 146 | 
             
            - sig/better_translate/variable_extractor.rbs
         | 
| 125 147 | 
             
            - sig/better_translate/version.rbs
         | 
| 126 148 | 
             
            - sig/better_translate/yaml_handler.rbs
         | 
| 149 | 
            +
            - sig/csv.rbs
         | 
| 127 150 | 
             
            - sig/faraday.rbs
         | 
| 128 151 | 
             
            - sig/generators/better_translate/analyze/analyze_generator.rbs
         | 
| 129 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
         |