Class: BetterTranslate::Analyzer::CodeScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/better_translate/analyzer/code_scanner.rb

Overview

Scans code files to find i18n key references

Supports multiple patterns:

  • t('key') / t("key")
  • I18n.t(:key) / I18n.t('key')
  • <%= t('key') %> in ERB

Examples:

Basic usage

scanner = CodeScanner.new("app/")
keys = scanner.scan
#=> #<Set: {"users.greeting", "products.list"}>

Constant Summary collapse

I18N_PATTERNS =

I18n patterns to match

Matches:

  • t('users.greeting')
  • t("users.greeting")
  • I18n.t(:users.greeting)
  • I18n.t('users.greeting')
  • I18n.translate('users.greeting')
[
  /\bt\(['"]([a-z0-9_.]+)['"]/i,             # t('key') or t("key")
  /\bI18n\.t\(:([a-z0-9_.]+)/i,              # I18n.t(:key)
  /\bI18n\.t\(['"]([a-z0-9_.]+)['"]/i,       # I18n.t('key')
  /\bI18n\.translate\(['"]([a-z0-9_.]+)['"]/i # I18n.translate('key')
].freeze
SCANNABLE_EXTENSIONS =

File extensions to scan

%w[.rb .erb .html.erb .haml .slim].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ CodeScanner

Initialize scanner with path



49
50
51
52
53
# File 'lib/better_translate/analyzer/code_scanner.rb', line 49

def initialize(path)
  @path = path
  @keys = Set.new
  @files_scanned = []
end

Instance Attribute Details

#files_scannedArray<String> (readonly)



43
44
45
# File 'lib/better_translate/analyzer/code_scanner.rb', line 43

def files_scanned
  @files_scanned
end

#keysSet (readonly)



40
41
42
# File 'lib/better_translate/analyzer/code_scanner.rb', line 40

def keys
  @keys
end

#pathString (readonly)



37
38
39
# File 'lib/better_translate/analyzer/code_scanner.rb', line 37

def path
  @path
end

Instance Method Details

#collect_filesArray<String> (private)

Collect all scannable files from path



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/better_translate/analyzer/code_scanner.rb', line 104

def collect_files
  if File.file?(path)
    return [path] if scannable_file?(path)

    return []
  end

  Dir.glob(File.join(path, "**", "*")).select do |file|
    File.file?(file) && scannable_file?(file)
  end
end

#key_countInteger

Get count of unique keys found



81
82
83
# File 'lib/better_translate/analyzer/code_scanner.rb', line 81

def key_count
  @keys.size
end

#scanSet

Scan path and extract i18n keys

Examples:

scanner = CodeScanner.new("app/")
keys = scanner.scan
#=> #<Set: {"users.greeting"}>

Raises:



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/better_translate/analyzer/code_scanner.rb', line 65

def scan
  validate_path!

  files = collect_files
  files.each do |file|
    scan_file(file)
    @files_scanned << file
  end

  @keys
end

#scan_file(file) ⇒ Object (private)

Scan single file and extract keys



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/better_translate/analyzer/code_scanner.rb', line 129

def scan_file(file)
  content = File.read(file)

  # Remove commented lines to avoid false positives
  lines = content.split("\n")
  active_lines = lines.reject { |line| line.strip.start_with?("#", "//", "<!--") }
  active_content = active_lines.join("\n")

  I18N_PATTERNS.each do |pattern|
    active_content.scan(pattern) do |match|
      key = match.is_a?(Array) ? match.first : match
      @keys.add(key) if key
    end
  end
rescue StandardError => e
  # Skip files that can't be read
  warn "Warning: Could not scan #{file}: #{e.message}" if ENV["VERBOSE"]
end

#scannable_file?(file) ⇒ Boolean (private)

Check if file should be scanned



121
122
123
# File 'lib/better_translate/analyzer/code_scanner.rb', line 121

def scannable_file?(file)
  SCANNABLE_EXTENSIONS.any? { |ext| file.end_with?(ext) }
end

#validate_path!Object (private)

Validate that path exists

Raises:



91
92
93
94
95
96
97
98
# File 'lib/better_translate/analyzer/code_scanner.rb', line 91

def validate_path!
  return if File.exist?(path)

  raise FileError.new(
    "Path does not exist: #{path}",
    context: { path: path }
  )
end