Class: Locallingo::Manager
- Inherits:
-
Object
- Object
- Locallingo::Manager
- Defined in:
- lib/locallingo/manager.rb
Overview
Manages translations with source-hash change detection.
- Tracks source hashes to detect changes (drift)
- Only translates missing/changed keys
- Validates translation completeness (config-selected validators)
- Merges LLM output back into the flat
<namespace>.<locale>.ymlfiles
Everything app-specific (locales, provider/model, prompt context/glossary, per-language guides, which validators run) comes from the Configuration.
Constant Summary collapse
- MAX_RETRIES =
3- MAX_MISSING_RETRIES =
2- BASE_SLEEP_DURATION =
1.0
Instance Attribute Summary collapse
-
#cli_name ⇒ Object
readonly
Returns the value of attribute cli_name.
-
#config ⇒ Object
readonly
Returns the value of attribute config.
-
#dry_run ⇒ Object
readonly
Returns the value of attribute dry_run.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#verbose ⇒ Object
readonly
Returns the value of attribute verbose.
Instance Method Summary collapse
-
#accept_edits!(locale: nil) ⇒ Object
Mark every current target value as intentional (source_hash + target_hash + manual flag) so the manual-edits validator stops flagging it.
-
#initialize(config: nil, root_path: nil, package: nil, dry_run: false, verbose: false, logger: nil, cli_name: "lingo") ⇒ Manager
constructor
A new instance of Manager.
-
#run_after_translate_hooks ⇒ Object
Run the configured after_translate hook commands (from the app root).
-
#source_hash ⇒ Object
CRC32 hash of the current source translations (change-detection fingerprint).
-
#status ⇒ Object
Current translation status per target locale.
-
#sync_state! ⇒ Object
Rewrite state from the current translation files (initial setup / after manual edits).
-
#translate!(locale: nil, force: false, force_keys: []) ⇒ Object
Translate missing/changed keys for one or all target locales.
-
#validate ⇒ Object
Validate translations; returns an array of violation hashes.
Constructor Details
#initialize(config: nil, root_path: nil, package: nil, dry_run: false, verbose: false, logger: nil, cli_name: "lingo") ⇒ Manager
Returns a new instance of Manager.
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/locallingo/manager.rb', line 34 def initialize(config: nil, root_path: nil, package: nil, dry_run: false, verbose: false, logger: nil, cli_name: "lingo") @config = config || Configuration.load(root_path: root_path || Dir.pwd, package:) @dry_run = dry_run @verbose = verbose @cli_name = cli_name @state = StateStore.new(@config.state_dir) @provider = Providers::RubyLLM.new(provider: @config.provider) @logger = logger || build_logger end |
Instance Attribute Details
#cli_name ⇒ Object (readonly)
Returns the value of attribute cli_name.
32 33 34 |
# File 'lib/locallingo/manager.rb', line 32 def cli_name @cli_name end |
#config ⇒ Object (readonly)
Returns the value of attribute config.
32 33 34 |
# File 'lib/locallingo/manager.rb', line 32 def config @config end |
#dry_run ⇒ Object (readonly)
Returns the value of attribute dry_run.
32 33 34 |
# File 'lib/locallingo/manager.rb', line 32 def dry_run @dry_run end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
32 33 34 |
# File 'lib/locallingo/manager.rb', line 32 def logger @logger end |
#verbose ⇒ Object (readonly)
Returns the value of attribute verbose.
32 33 34 |
# File 'lib/locallingo/manager.rb', line 32 def verbose @verbose end |
Instance Method Details
#accept_edits!(locale: nil) ⇒ Object
Mark every current target value as intentional (source_hash + target_hash + manual flag) so the manual-edits validator stops flagging it.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/locallingo/manager.rb', line 102 def accept_edits!(locale: nil) source = load_source_translations locales = locale ? [locale] : config.target_locales locales.each do |target_locale| target = load_locale_translations(target_locale) locale_state = @state.load(target_locale) target.each do |key, value| next unless source[key] locale_state[key] = { "source_hash" => @state.hash(source[key]), "target_hash" => @state.hash(value), "manual" => true } end @state.save(target_locale, locale_state) unless dry_run end end |
#run_after_translate_hooks ⇒ Object
Run the configured after_translate hook commands (from the app root).
158 159 160 161 162 163 |
# File 'lib/locallingo/manager.rb', line 158 def run_after_translate_hooks config.after_translate.each do |command| log("Running: #{command}") Dir.chdir(config.root_path) { system(command) } end end |
#source_hash ⇒ Object
CRC32 hash of the current source translations (change-detection fingerprint).
125 126 127 |
# File 'lib/locallingo/manager.rb', line 125 def source_hash format("%08x", Zlib.crc32(load_source_translations.to_json)) end |
#status ⇒ Object
Current translation status per target locale.
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/locallingo/manager.rb', line 46 def status source = load_source_translations config.target_locales.each_with_object({}) do |locale, results| target = load_locale_translations(locale) locale_state = @state.load(locale) missing = source.keys - target.keys outdated = outdated_validator.outdated_keys(source, locale_state) results[locale] = { total_keys: source.keys.size, translated: target.keys.size, missing: missing.size, outdated: outdated.size, missing_keys: missing.first(10), outdated_keys: outdated.first(10) } end end |
#sync_state! ⇒ Object
Rewrite state from the current translation files (initial setup / after manual edits). Returns the combined state.
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/locallingo/manager.rb', line 131 def sync_state! source = load_source_translations en_state = @state.load(config.source_locale) source.each { |key, value| en_state[key] = { "source_hash" => @state.hash(value) } } en_state.each_key { |key| en_state.delete(key) unless source.key?(key) } @state.save(config.source_locale, en_state) unless dry_run config.target_locales.each do |locale| target = load_locale_translations(locale) locale_state = @state.load(locale) target.each_key do |key| next unless source[key] locale_state[key] = { "source_hash" => @state.hash(source[key]) } end locale_state.each_key { |key| locale_state.delete(key) unless target.key?(key) } @state.save(locale, locale_state) unless dry_run end combined = { config.source_locale => @state.load(config.source_locale) } config.target_locales.each { |locale| combined[locale] = @state.load(locale) } combined end |
#translate!(locale: nil, force: false, force_keys: []) ⇒ Object
Translate missing/changed keys for one or all target locales.
91 92 93 94 95 96 97 98 |
# File 'lib/locallingo/manager.rb', line 91 def translate!(locale: nil, force: false, force_keys: []) @provider.ensure_credentials! locales_to_process = locale ? [locale] : config.target_locales source = load_source_translations locales_to_process.each { |target_locale| translate_locale(source, target_locale, force:, force_keys:) } end |
#validate ⇒ Object
Validate translations; returns an array of violation hashes. Which checks run is config-driven.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/locallingo/manager.rb', line 68 def validate violations = [] source = load_source_translations violations.concat(Validators::DuplicateValues.new.call(source:)) if config.validator_enabled?(:duplicate_values) config.target_locales.each do |locale| target = load_locale_translations(locale) locale_state = @state.load(locale) violations.concat(missing_validator.call(source:, target:, locale:)) if config.validator_enabled?(:missing) if config.validator_enabled?(:outdated) violations.concat(outdated_validator.call(source:, locale_state:, locale:)) end if config.validator_enabled?(:manual_edits) violations.concat(Validators::ManualEdits.new(cli_name:).call(target:, locale_state:, locale:)) end end violations end |