Class: I18nize::Inserter

Inherits:
Object
  • Object
show all
Defined in:
lib/i18nize/inserter.rb

Constant Summary collapse

CONFLICT_POLICY =

:overwrite | :skip

:overwrite

Class Method Summary collapse

Class Method Details

.dig_nested(hash, path) ⇒ Object



121
122
123
# File 'lib/i18nize/inserter.rb', line 121

def self.dig_nested(hash, path)
  path.reduce(hash) { |h, k| h.is_a?(Hash) ? h[k] : nil }
end

.insert_missing(to_locale:, from_locale: "en", base_path: "config/locales", translator: nil) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/i18nize/inserter.rb', line 13

def self.insert_missing(to_locale:, from_locale: "en", base_path: "config/locales", translator: nil)
  missing_data = Comparer.missing_translations(from_locale: from_locale, to_locale: to_locale)

  if translator
    char_count = missing_data.character_count
    puts "\n\e[33m[i18nize] Estimated character count: #{char_count}\e[0m"
    puts "\e[31m[i18nize] Warning: DeepL Free has a 500,000 character monthly limit.\e[0m" if char_count > 500_000
    print "\e[36mProceed with translation? [y/N]: \e[0m"
    answer = $stdin.gets.to_s.strip.downcase
    unless answer == "y"
      puts "\e[90m[i18nize] Aborted by user.\e[0m"
      return
    end
  end

  overwrites = []

  missing_data.data.each do |from_rel_path, translations|
    dirname = File.dirname(from_rel_path)
    filename = File.basename(from_rel_path)

    locale_re = /(^|[._])#{Regexp.escape(from_locale)}\.(ya?ml)$/
    new_filename = filename.sub(locale_re) { "#{Regexp.last_match(1)}#{to_locale}.#{Regexp.last_match(2)}" }
    to_rel_path = dirname == "." || dirname.empty? ? new_filename : File.join(dirname, new_filename)
    to_full_path = File.join(base_path, to_rel_path)

    existing = File.exist?(to_full_path) ? YAML.load_file(to_full_path) : {}
    existing[to_locale] ||= {}

    translation_map =
      if translator
        src_values = translations.values
        translated_values = translator.translate_texts(src_values) # returns array
        translations.keys.zip(translated_values).to_h
      else
        translations
      end

    translation_map.each do |full_key, value|
      path = full_key.sub(/^#{Regexp.escape(from_locale)}\./, "").split(".")
      insert_nested(existing[to_locale], path, value, overwrites, to_rel_path, to_locale, CONFLICT_POLICY)
    end

    FileUtils.mkdir_p(File.dirname(to_full_path))
    File.write(to_full_path, existing.to_yaml(line_width: -1))

    puts "\e[32m[i18nize] Inserted #{translation_map.size} keys into #{to_full_path}\e[0m"
  end

  return unless overwrites.any?
    puts "\n\e[33m[i18nize] Overwrote #{overwrites.size} scalar node(s) to create nested structure:\e[0m"
    overwrites.each do |c|
      puts "  \e[33m- #{c[:file]}: #{c[:locale]}.#{c[:path].join('.')}\e[0m (was: #{c[:existing_class]})"
    end

end

.insert_nested(base, keys, value, overwrites, file, locale, conflict_policy) ⇒ Object

conflict_policy: :overwrite (replace scalar with {}), :skip



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/i18nize/inserter.rb', line 126

def self.insert_nested(base, keys, value, overwrites, file, locale, conflict_policy)
  key = keys.shift

  if keys.empty?
    base[key] = value
    return true
  end

  cur = base[key]

  case cur
  when nil
    base[key] = {}
    return insert_nested(base[key], keys, value, overwrites, file, locale, conflict_policy)
  when Hash
    return insert_nested(cur, keys, value, overwrites, file, locale, conflict_policy)
  else
    return false unless conflict_policy == :overwrite
      overwrites << { file: file, locale: locale, path: [key, *keys], existing_class: cur.class.to_s }
      base[key] = {}
    return insert_nested(base[key], keys, value, overwrites, file, locale, conflict_policy)

  end
end

.insert_only_blank(to_locale:, from_locale: "en", base_path: "config/locales") ⇒ Object

NEW: fill only missing/blank values with “<locale>.<full.key> missing”



71
72
73
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/i18nize/inserter.rb', line 71

def self.insert_only_blank(to_locale:, from_locale: "en", base_path: "config/locales")
  from_data = Loader.load_per_file(locale: from_locale, base_path: base_path)
  # to_data = Loader.load_per_file(locale: to_locale, base_path: base_path)

  updated_files = 0
  overwrites = []

  from_data.each do |from_rel_path, from_keys|
    dirname = File.dirname(from_rel_path)
    filename = File.basename(from_rel_path)
    locale_re = /(^|[._])#{Regexp.escape(from_locale)}\.(ya?ml)$/
    new_filename = filename.sub(locale_re) { "#{Regexp.last_match(1)}#{to_locale}.#{Regexp.last_match(2)}" }
    to_rel_path = dirname == "." || dirname.empty? ? new_filename : File.join(dirname, new_filename)
    to_full_path = File.join(base_path, to_rel_path)

    existing = File.exist?(to_full_path) ? YAML.load_file(to_full_path) : {}
    existing[to_locale] ||= {}

    changes = 0

    from_keys.each_key do |from_full_key|
      to_full_key = from_full_key.sub(/^#{Regexp.escape(from_locale)}\./, "#{to_locale}.")
      path = to_full_key.sub(/^#{Regexp.escape(to_locale)}\./, "").split(".")
      current = dig_nested(existing[to_locale], path)
      next if current.to_s.strip.empty? && ENV["I18NIZE_ALLOW_EMPTY"] == "1"
      next unless current.nil? || current.to_s.strip.empty?

      placeholder = "#{to_full_key} missing"
      insert_nested(existing[to_locale], path, placeholder, overwrites, to_rel_path, to_locale, :overwrite)
      changes += 1
    end

    next unless changes.positive?

    FileUtils.mkdir_p(File.dirname(to_full_path))
    File.write(to_full_path, existing.to_yaml(line_width: -1))
    updated_files += 1
    puts "\e[32m[i18nize] Inserted #{changes} placeholder#{changes == 1 ? '' : 's'} into #{to_full_path}\e[0m"
  end

  puts "\e[90m[i18nize] No blank or missing keys found.\e[0m" if updated_files.zero?

  return unless overwrites.any?

  puts "\n\e[33m[i18nize] Overwrote #{overwrites.size} scalar node(s) while creating nested structure:\e[0m"
  overwrites.each do |c|
    puts "  \e[33m- #{c[:file]}: #{c[:locale]}.#{c[:path].join('.')}\e[0m (was: #{c[:existing_class]})"
  end
end