Class: BetterTranslate::JsonHandler

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

Overview

Handles JSON file operations

Provides methods for:

  • Reading and parsing JSON files
  • Writing JSON files with proper formatting
  • Merging translations (incremental mode)
  • Handling exclusions
  • Flattening/unflattening nested structures

Examples:

Reading a JSON file

handler = JsonHandler.new(config)
data = handler.read_json("config/locales/en.json")

Writing translations

handler.write_json("config/locales/it.json", { "it" => { "greeting" => "Ciao" } })

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ JsonHandler

Initialize JSON handler

Examples:

config = Configuration.new
handler = JsonHandler.new(config)


35
36
37
# File 'lib/better_translate/json_handler.rb', line 35

def initialize(config)
  @config = config
end

Instance Attribute Details

#configConfiguration (readonly)



25
26
27
# File 'lib/better_translate/json_handler.rb', line 25

def config
  @config
end

Instance Method Details

#build_output_path(target_lang_code) ⇒ String

Build output file path for target language

Examples:

path = handler.build_output_path("it")
#=> "config/locales/it.json"


174
175
176
177
178
# File 'lib/better_translate/json_handler.rb', line 174

def build_output_path(target_lang_code)
  return "#{target_lang_code}.json" unless config.output_folder

  File.join(config.output_folder, "#{target_lang_code}.json")
end

#create_backup_file(file_path) ⇒ void (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Create backup file with rotation support



188
189
190
191
192
193
194
195
196
197
# File 'lib/better_translate/json_handler.rb', line 188

def create_backup_file(file_path)
  return unless File.exist?(file_path)

  # Rotate existing backups if max_backups > 1
  rotate_backups(file_path) if config.max_backups > 1

  # Create primary backup
  backup_path = "#{file_path}.bak"
  FileUtils.cp(file_path, backup_path)
end

#filter_exclusions(strings, target_lang_code) ⇒ Hash

Filter out excluded keys for a specific language

Examples:

filtered = handler.filter_exclusions(strings, "it")


131
132
133
134
135
136
# File 'lib/better_translate/json_handler.rb', line 131

def filter_exclusions(strings, target_lang_code)
  excluded_keys = config.global_exclusions.dup
  excluded_keys += config.exclusions_per_language[target_lang_code] || []

  strings.reject { |key, _| excluded_keys.include?(key) }
end

#get_source_stringsHash

Get translatable strings from source JSON

Reads the input file and returns a flattened hash of strings. Removes the root language key if present.

Examples:

strings = handler.get_source_strings
#=> { "greeting" => "Hello", "nav.home" => "Home" }


112
113
114
115
116
117
118
119
120
# File 'lib/better_translate/json_handler.rb', line 112

def get_source_strings
  return {} unless config.input_file

  source_data = read_json(config.input_file)
  # Remove root language key if present (e.g., "en:")
  source_data = source_data[config.source_language] || source_data

  Utils::HashFlattener.flatten(source_data)
end

#merge_translations(file_path, new_translations) ⇒ Hash

Merge translated strings with existing file (incremental mode)

Examples:

merged = handler.merge_translations("config/locales/it.json", new_translations)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/better_translate/json_handler.rb', line 147

def merge_translations(file_path, new_translations)
  if File.exist?(file_path)
    existing = read_json(file_path)
    # Extract actual translations (remove language wrapper if present)
    target_lang = config.target_languages.first[:short_name]
    existing = existing[target_lang] || existing
  else
    existing = {} # : Hash[untyped, untyped]
  end

  existing_flat = Utils::HashFlattener.flatten(existing)

  # Merge: existing takes precedence
  merged = new_translations.merge(existing_flat)

  Utils::HashFlattener.unflatten(merged)
end

#read_json(file_path) ⇒ Hash

Read and parse JSON file

Examples:

data = handler.read_json("config/locales/en.json")
#=> { "en" => { "greeting" => "Hello" } }

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/better_translate/json_handler.rb', line 50

def read_json(file_path)
  Validator.validate_file_exists!(file_path)

  content = File.read(file_path)
  return {} if content.strip.empty?

  JSON.parse(content)
rescue Errno::ENOENT => e
  raise FileError.new("File does not exist: #{file_path}", context: { error: e.message })
rescue JSON::ParserError => e
  raise JsonError.new("Invalid JSON syntax in #{file_path}", context: { error: e.message })
end

#rotate_backups(file_path) ⇒ void (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Rotate backup files, keeping only max_backups



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/better_translate/json_handler.rb', line 205

def rotate_backups(file_path)
  primary_backup = "#{file_path}.bak"
  return unless File.exist?(primary_backup)

  # Clean up ANY backups that would exceed max_backups after rotation
  10.downto(config.max_backups) do |i|
    numbered_backup = "#{file_path}.bak.#{i}"
    FileUtils.rm_f(numbered_backup) if File.exist?(numbered_backup)
  end

  # Rotate numbered backups from high to low to avoid overwrites
  (config.max_backups - 2).downto(1) do |i|
    old_path = "#{file_path}.bak.#{i}"
    new_path = "#{file_path}.bak.#{i + 1}"

    FileUtils.mv(old_path, new_path) if File.exist?(old_path)
  end

  # Move primary backup to .bak.1
  FileUtils.mv(primary_backup, "#{file_path}.bak.1")
end

#write_json(file_path, data, diff_preview: nil) ⇒ Hash?

Write hash to JSON file

Examples:

handler.write_json("config/locales/it.json", { "it" => { "greeting" => "Ciao" } })

Raises:



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/better_translate/json_handler.rb', line 74

def write_json(file_path, data, diff_preview: nil)
  summary = nil

  # Show diff preview if in dry run mode
  if config.dry_run && diff_preview
    existing_data = File.exist?(file_path) ? read_json(file_path) : {} # : Hash[untyped, untyped]
    summary = diff_preview.show_diff(existing_data, data, file_path)
  end

  return summary if config.dry_run

  # Create backup if enabled and file exists
  create_backup_file(file_path) if config.create_backup && File.exist?(file_path)

  # Ensure output directory exists
  FileUtils.mkdir_p(File.dirname(file_path))

  # Write JSON with proper indentation
  File.write(file_path, JSON.pretty_generate(data))

  nil
rescue Errno::EACCES => e
  raise FileError.new("Permission denied: #{file_path}", context: { error: e.message })
rescue StandardError => e
  raise FileError.new("Failed to write JSON: #{file_path}", context: { error: e.message })
end