Module: Locallingo::KeyFlattener

Defined in:
lib/locallingo/key_flattener.rb

Overview

Converts between nested locale hashes (as loaded from YAML) and the flat dotted-key representation the engine works in, and back.

Flat keys use dot notation with bracket indices for arrays, e.g. items[0].name. This is a faithful extraction of the algorithms that lived in TranslationManager (flatten_hash / parse_key_segments / set_nested_value / navigate_or_create) so the round-trip behavior is byte-identical.

Class Method Summary collapse

Class Method Details

.ensure_array_size(array, index) ⇒ Object



110
111
112
# File 'lib/locallingo/key_flattener.rb', line 110

def ensure_array_size(array, index)
  array << nil while array.size <= index
end

.flatten(hash, prefix = "") ⇒ Object

Flatten a nested hash to { "a.b.c" => "value" }. Arrays are indexed with bracket notation. Only leaf values are kept; the caller decides which types to retain.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/locallingo/key_flattener.rb', line 17

def flatten(hash, prefix = "")
  result = {}
  hash.each do |key, value|
    full_key = prefix.empty? ? key.to_s : "#{prefix}.#{key}"
    case value
    when Hash
      result.merge!(flatten(value, full_key))
    when Array
      value.each_with_index do |item, i|
        if item.is_a?(String)
          result["#{full_key}[#{i}]"] = item
        elsif item.is_a?(Hash)
          result.merge!(flatten(item, "#{full_key}[#{i}]"))
        end
      end
    else
      result[full_key] = value
    end
  end
  result
end

Navigate to or create the appropriate container (hash or array) for a segment.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/locallingo/key_flattener.rb', line 92

def navigate_or_create(current, segment)
  if segment[:index]
    if segment[:key]
      current[segment[:key]] ||= []
      ensure_array_size(current[segment[:key]], segment[:index])
      current[segment[:key]][segment[:index]] ||= {}
      current[segment[:key]][segment[:index]]
    else
      ensure_array_size(current, segment[:index])
      current[segment[:index]] ||= {}
      current[segment[:index]]
    end
  else
    current[segment[:key]] ||= {}
    current[segment[:key]]
  end
end

.parse_key_segments(key) ⇒ Object

Parse a flattened key into segments, handling both dot notation and bracket indices. "items.name" => ["items", index: 0, "name", index: nil] "matrix[1]" => ["matrix", index: 0, nil, index: 1]



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/locallingo/key_flattener.rb', line 62

def parse_key_segments(key)
  segments = []
  parts = key.split(".")

  parts.each do |part|
    if part =~ /^([^\[]*)\[(\d+)\](.*)$/
      base_key = ::Regexp.last_match(1)
      index = ::Regexp.last_match(2).to_i
      remainder = ::Regexp.last_match(3)

      segments << if base_key.empty?
                    { key: nil, index: }
                  else
                    { key: base_key, index: }
                  end

      while remainder =~ /^\[(\d+)\](.*)$/
        segments << { key: nil, index: ::Regexp.last_match(1).to_i }
        remainder = ::Regexp.last_match(2)
      end
    else
      segments << { key: part, index: nil }
    end
  end

  segments
end

.set_nested_value(hash, key, value) ⇒ Object

Set a flattened key to value inside the nested hash, creating intermediate hashes/arrays as needed.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/locallingo/key_flattener.rb', line 41

def set_nested_value(hash, key, value)
  segments = parse_key_segments(key)
  current = hash

  segments[0..-2].each do |segment|
    current = navigate_or_create(current, segment)
  end

  final_segment = segments.last
  if final_segment[:index]
    current[final_segment[:key]] ||= []
    current[final_segment[:key]][final_segment[:index]] = value
  else
    current[final_segment[:key]] = value
  end
end