Class: Locallingo::QualityChecker

Inherits:
Object
  • Object
show all
Defined in:
lib/locallingo/quality_checker.rb

Overview

Checks translation quality and suggests improvements.

Static (provider-free) checks: regex rules, universal fixes, terminology, optional British-spelling drift, and a very-long-text heuristic. An optional AI pass (RubyLLM) reviews a sample for clarity/professionalism.

fix! rewrites the auto-fixable suggestions (universal fixes + British spellings) back into the locale files, preserving case.

Constant Summary collapse

LONG_TEXT_THRESHOLD =
200

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: nil, root_path: nil, package: nil, verbose: false, logger: nil) ⇒ QualityChecker

Returns a new instance of QualityChecker.



26
27
28
29
30
31
32
# File 'lib/locallingo/quality_checker.rb', line 26

def initialize(config: nil, root_path: nil, package: nil, verbose: false, logger: nil)
  @config = config || Configuration.load(root_path: root_path || Dir.pwd, package:)
  @verbose = verbose
  @logger = logger
  @provider = Providers::RubyLLM.new(provider: @config.provider)
  @terminology = Quality::Terminology.new(@config.terminology_setting, base_path: @config.base_path)
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



24
25
26
# File 'lib/locallingo/quality_checker.rb', line 24

def config
  @config
end

#loggerObject (readonly)

Returns the value of attribute logger.



24
25
26
# File 'lib/locallingo/quality_checker.rb', line 24

def logger
  @logger
end

#verboseObject (readonly)

Returns the value of attribute verbose.



24
25
26
# File 'lib/locallingo/quality_checker.rb', line 24

def verbose
  @verbose
end

Instance Method Details

#check(locale: nil, use_ai: false) ⇒ Object

Check all translations for a locale.



35
36
37
38
39
40
41
42
# File 'lib/locallingo/quality_checker.rb', line 35

def check(locale: nil, use_ai: false)
  locale ||= config.source_locale
  translations = load_locale_translations(locale)

  suggestions = translations.flat_map { |key, text| check_text(key, text, locale) }
  suggestions.concat(ai_sample(translations, locale)) if use_ai
  suggestions
end

#check_key(key, locale, use_ai: false) ⇒ Object

Check a single key.



45
46
47
48
49
50
51
52
53
# File 'lib/locallingo/quality_checker.rb', line 45

def check_key(key, locale, use_ai: false)
  translations = load_locale_translations(locale)
  text = translations[key]
  return [{ key:, error: "Key not found" }] unless text

  suggestions = check_text(key, text, locale)
  suggestions.concat(suggest_improvements({ key => text }, locale)) if use_ai
  suggestions
end

#fix!(locale: nil, dry_run: false) ⇒ Object

Auto-fix the fixable suggestions in the locale files. Returns { fixed: , skipped: }.



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/locallingo/quality_checker.rb', line 57

def fix!(locale: nil, dry_run: false)
  locale ||= config.source_locale
  suggestions = check(locale:)
  fixable = suggestions.select { |s| s[:fix] }
  return { fixed: 0, skipped: suggestions.size - fixable.size } if fixable.empty?

  changed = apply_fixes(locale, fixable)

  changed.each { |file, content| File.write(file, content) } unless dry_run
  changed.each_key { |file| log("#{dry_run ? "Would fix" : "Fixed"}: #{file}") }

  { fixed: changed.size, skipped: suggestions.size - fixable.size }
end

#suggest_improvements(keys_with_text, locale) ⇒ Object

AI suggestions for a batch of key=>text pairs.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/locallingo/quality_checker.rb', line 72

def suggest_improvements(keys_with_text, locale)
  unless @provider.credentials?
    warn "⚠️  No LLM credentials — skipping AI suggestions"
    return []
  end

  result = @provider.chat(
    model: config.quality_model,
    instructions: quality_prompt(locale),
    payload: keys_with_text
  )

  result.map do |key, suggestion|
    symbolized = suggestion.transform_keys(&:to_sym)
    symbolized[:severity] = symbolized[:severity]&.to_sym || :info
    { key:, text: keys_with_text[key], locale:, source: :ai, **symbolized }
  end
rescue StandardError => e
  warn "⚠️  AI suggestion failed: #{e.message}"
  []
end