lit 0.3.3 → 0.4.0.pre.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +40 -6
- data/app/assets/javascripts/lit/lit_frontend.js +20 -12
- data/app/assets/stylesheets/lit/application.css +3 -0
- data/app/controllers/lit/api/v1/base_controller.rb +1 -1
- data/app/controllers/lit/api/v1/locales_controller.rb +1 -1
- data/app/controllers/lit/api/v1/localization_keys_controller.rb +12 -4
- data/app/controllers/lit/api/v1/localizations_controller.rb +25 -9
- data/app/controllers/lit/application_controller.rb +11 -8
- data/app/controllers/lit/concerns/request_info_store.rb +1 -0
- data/app/controllers/lit/incomming_localizations_controller.rb +22 -15
- data/app/controllers/lit/locales_controller.rb +1 -1
- data/app/controllers/lit/localization_keys_controller.rb +44 -18
- data/app/controllers/lit/localizations_controller.rb +16 -11
- data/app/controllers/lit/sources_controller.rb +9 -13
- data/app/helpers/lit/frontend_helper.rb +28 -16
- data/app/helpers/lit/localizations_helper.rb +2 -1
- data/app/jobs/lit/synchronize_source_job.rb +1 -1
- data/app/models/lit/incomming_localization.rb +71 -41
- data/app/models/lit/locale.rb +11 -13
- data/app/models/lit/localization.rb +26 -24
- data/app/models/lit/localization_key.rb +46 -55
- data/app/models/lit/source.rb +17 -74
- data/app/queries/localization_key_search_query.rb +80 -0
- data/app/services/remote_interactor_service.rb +45 -0
- data/app/services/synchronize_source_service.rb +63 -0
- data/app/views/kaminari/lit/_gap.html.erb +1 -1
- data/app/views/layouts/lit/_navigation.html.erb +1 -1
- data/app/views/lit/dashboard/index.html.erb +2 -2
- data/app/views/lit/incomming_localizations/index.html.erb +10 -6
- data/app/views/lit/localization_keys/_localization_row.html.erb +1 -1
- data/app/views/lit/localization_keys/_localizations_list.html.erb +84 -0
- data/app/views/lit/localization_keys/_sidebar.html.erb +66 -0
- data/app/views/lit/localization_keys/change_completed.js.erb +2 -0
- data/app/views/lit/localization_keys/index.html.erb +3 -121
- data/app/views/lit/localization_keys/not_translated.html.erb +9 -0
- data/app/views/lit/localization_keys/restore_deleted.js.erb +1 -0
- data/app/views/lit/localization_keys/visited_again.html.erb +9 -0
- data/app/views/lit/localizations/_previous_versions_rows.html.erb +2 -2
- data/app/views/lit/localizations/change_completed.js.erb +2 -0
- data/app/views/lit/localizations/update.js.erb +2 -0
- data/app/views/lit/sources/_form.html.erb +1 -1
- data/app/views/lit/sources/index.html.erb +1 -1
- data/config/routes.rb +5 -0
- data/db/migrate/20181017123839_lit_add_is_deleted_to_localization_keys.rb +8 -0
- data/db/migrate/20181018075955_lit_add_localization_key_is_deleted_to_localization_keys.rb +8 -0
- data/db/migrate/20181030111522_lit_add_is_visited_again_to_localization_keys.rb +8 -0
- data/lib/generators/lit/install/templates/initializer.rb +5 -1
- data/lib/lit.rb +1 -0
- data/lib/lit/adapters/redis_storage.rb +1 -1
- data/lib/lit/cache.rb +32 -54
- data/lib/lit/export.rb +80 -0
- data/lib/lit/i18n_backend.rb +12 -9
- data/lib/lit/import.rb +179 -0
- data/lib/lit/loader.rb +2 -0
- data/lib/lit/version.rb +1 -1
- data/lib/tasks/lit_tasks.rake +78 -13
- metadata +21 -6
@@ -0,0 +1,8 @@
|
|
1
|
+
class LitAddLocalizationKeyIsDeletedToLocalizationKeys < Rails::VERSION::MAJOR >= 5 ?
|
2
|
+
ActiveRecord::Migration[4.2] :
|
3
|
+
ActiveRecord::Migration
|
4
|
+
def change
|
5
|
+
add_column :lit_incomming_localizations, :localization_key_is_deleted,
|
6
|
+
:boolean, null: false, default: false
|
7
|
+
end
|
8
|
+
end
|
@@ -12,6 +12,10 @@ Lit.authentication_verification = <%= @authentication_verification || 'nil' %>
|
|
12
12
|
# environment
|
13
13
|
Lit.key_value_engine = '<%= @key_value_engine %>'
|
14
14
|
|
15
|
+
# Redis URL to use when key_value_engine is 'redis'
|
16
|
+
# When set to `nil`, it uses the REDIS_URL environment variable.
|
17
|
+
# Lit.redis_url = 'redis://redis-server:6379/0'
|
18
|
+
|
15
19
|
# Pass extra options to key_value_neinge, ie. prefix for redis (only one
|
16
20
|
# supported at the moment)
|
17
21
|
# Lit.storage_options = { prefix: "my_project" }
|
@@ -43,7 +47,7 @@ Lit.set_last_updated_at_upon_creation = true
|
|
43
47
|
|
44
48
|
# Store request info - this will store in cache additional info about request
|
45
49
|
# path that triggered translation key to be displayed / accessed
|
46
|
-
# For more
|
50
|
+
# For more information please check the README.md
|
47
51
|
Lit.store_request_info = false
|
48
52
|
|
49
53
|
# Initialize lit
|
data/lib/lit.rb
CHANGED
data/lib/lit/cache.rb
CHANGED
@@ -64,8 +64,8 @@ module Lit
|
|
64
64
|
locale_key, key_without_locale = split_key(key)
|
65
65
|
locale = find_locale(locale_key)
|
66
66
|
localization = find_localization(locale, key_without_locale, value: value, force_array: force_array, update_value: true)
|
67
|
-
return localization.
|
68
|
-
localizations[key] = localization.
|
67
|
+
return localization.translation if startup_process && localization.is_changed?
|
68
|
+
localizations[key] = localization.translation if localization
|
69
69
|
end
|
70
70
|
|
71
71
|
def update_cache(key, value)
|
@@ -82,12 +82,12 @@ module Lit
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def load_all_translations
|
85
|
-
first = Localization.order(id: :asc).first
|
86
|
-
last = Localization.order(id: :desc).first
|
85
|
+
first = Localization.active.order(id: :asc).first
|
86
|
+
last = Localization.active.order(id: :desc).first
|
87
87
|
if !first || (!localizations.has_key?(first.full_key) ||
|
88
88
|
!localizations.has_key?(last.full_key))
|
89
|
-
Localization.includes([
|
90
|
-
localizations[l.full_key] = l.
|
89
|
+
Localization.includes(%i[locale localization_key]).active.find_each do |l|
|
90
|
+
localizations[l.full_key] = l.translation
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
@@ -97,7 +97,7 @@ module Lit
|
|
97
97
|
locale_key, key_without_locale = split_key(key)
|
98
98
|
locale = find_locale(locale_key)
|
99
99
|
localization = find_localization(locale, key_without_locale, default_fallback: true)
|
100
|
-
localizations[key] = localization.
|
100
|
+
localizations[key] = localization.translation if localization
|
101
101
|
end
|
102
102
|
|
103
103
|
def delete_key(key)
|
@@ -126,40 +126,6 @@ module Lit
|
|
126
126
|
@locale_cache[locale_key]
|
127
127
|
end
|
128
128
|
|
129
|
-
# this comes directly from copycopter.
|
130
|
-
def export
|
131
|
-
reset
|
132
|
-
localizations_scope = Lit::Localization
|
133
|
-
unless ENV['LOCALES'].blank?
|
134
|
-
locale_keys = ENV['LOCALES'].to_s.split(',') || []
|
135
|
-
locale_ids = Lit::Locale.where(locale: locale_keys).pluck(:id)
|
136
|
-
localizations_scope = localizations_scope.where(locale_id: locale_ids) unless locale_ids.empty?
|
137
|
-
end
|
138
|
-
db_localizations = {}
|
139
|
-
localizations_scope.find_each do |l|
|
140
|
-
db_localizations[l.full_key] = l.get_value
|
141
|
-
end
|
142
|
-
exported_keys = nested_string_keys_to_hash(db_localizations)
|
143
|
-
exported_keys.to_yaml
|
144
|
-
end
|
145
|
-
|
146
|
-
def nested_string_keys_to_hash(db_localizations)
|
147
|
-
# http://subtech.g.hatena.ne.jp/cho45/20061122
|
148
|
-
deep_proc = proc do |_k, s, o|
|
149
|
-
if s.is_a?(Hash) && o.is_a?(Hash)
|
150
|
-
next s.merge(o, &deep_proc)
|
151
|
-
end
|
152
|
-
next o
|
153
|
-
end
|
154
|
-
nested_keys = {}
|
155
|
-
db_localizations.sort.each do |k, v|
|
156
|
-
key_parts = k.to_s.split('.')
|
157
|
-
converted = key_parts.reverse.reduce(v) { |a, n| { n => a } }
|
158
|
-
nested_keys.merge!(converted, &deep_proc)
|
159
|
-
end
|
160
|
-
nested_keys
|
161
|
-
end
|
162
|
-
|
163
129
|
def get_global_hits_counter(key)
|
164
130
|
@hits_counter['global_hits_counter.' + key]
|
165
131
|
end
|
@@ -190,11 +156,14 @@ module Lit
|
|
190
156
|
return nil if value.is_a?(Hash)
|
191
157
|
ActiveRecord::Base.transaction do
|
192
158
|
localization_key = find_localization_key(key_without_locale)
|
193
|
-
localization =
|
194
|
-
|
159
|
+
localization =
|
160
|
+
Lit::Localization.active
|
161
|
+
.where(locale_id: locale.id)
|
162
|
+
.where(localization_key_id: localization_key.id)
|
163
|
+
.first_or_initialize
|
195
164
|
if update_value || localization.new_record?
|
196
165
|
if value.is_a?(Array)
|
197
|
-
value = parse_array_value(value) unless force_array
|
166
|
+
value = parse_array_value(value, locale) unless force_array
|
198
167
|
elsif !value.nil?
|
199
168
|
value = parse_value(value, locale)
|
200
169
|
else
|
@@ -204,7 +173,10 @@ module Lit
|
|
204
173
|
value = fallback_to_default(localization_key, localization)
|
205
174
|
end
|
206
175
|
end
|
207
|
-
|
176
|
+
# Prevent overwriting existing default value with nil.
|
177
|
+
# However, if the localization record is #new_record?, we still need
|
178
|
+
# to insert it with an empty default value.
|
179
|
+
localization.update_default_value(value) if localization.new_record? || value
|
208
180
|
end
|
209
181
|
return localization
|
210
182
|
end
|
@@ -236,8 +208,8 @@ module Lit
|
|
236
208
|
def find_localization_for_delete(locale, key_without_locale)
|
237
209
|
localization_key = find_localization_key_for_delete(key_without_locale)
|
238
210
|
return nil unless localization_key
|
239
|
-
Lit::Localization.find_by(locale_id: locale.id,
|
240
|
-
|
211
|
+
Lit::Localization.active.find_by(locale_id: locale.id,
|
212
|
+
localization_key_id: localization_key.id)
|
241
213
|
end
|
242
214
|
|
243
215
|
def delete_localization(locale, key_without_locale)
|
@@ -259,9 +231,9 @@ module Lit
|
|
259
231
|
when Symbol then
|
260
232
|
lk = Lit::LocalizationKey.where(localization_key: v.to_s).first
|
261
233
|
if lk
|
262
|
-
loca = Lit::Localization.where(locale_id: locale.id).
|
234
|
+
loca = Lit::Localization.active.where(locale_id: locale.id).
|
263
235
|
where(localization_key_id: lk.id).first
|
264
|
-
new_value = loca.
|
236
|
+
new_value = loca.translation if loca && loca.translation.present?
|
265
237
|
end
|
266
238
|
when String then
|
267
239
|
new_value = v
|
@@ -275,7 +247,7 @@ module Lit
|
|
275
247
|
new_value
|
276
248
|
end
|
277
249
|
|
278
|
-
def parse_array_value(value)
|
250
|
+
def parse_array_value(value, locale)
|
279
251
|
new_value = nil
|
280
252
|
value_clone = value.dup
|
281
253
|
while (v = value_clone.shift) && v.present?
|
@@ -286,10 +258,12 @@ module Lit
|
|
286
258
|
end
|
287
259
|
|
288
260
|
def find_localization_key(key_without_locale)
|
289
|
-
|
290
|
-
|
261
|
+
if localization_keys.key?(key_without_locale)
|
262
|
+
Lit::LocalizationKey.find_by(
|
263
|
+
id: localization_keys[key_without_locale]
|
264
|
+
) || find_or_create_localization_key(key_without_locale)
|
291
265
|
else
|
292
|
-
|
266
|
+
find_or_create_localization_key(key_without_locale)
|
293
267
|
end
|
294
268
|
end
|
295
269
|
|
@@ -303,7 +277,11 @@ module Lit
|
|
303
277
|
end
|
304
278
|
|
305
279
|
def find_or_create_localization_key(key_without_locale)
|
306
|
-
localization_key = Lit::LocalizationKey.
|
280
|
+
localization_key = Lit::LocalizationKey.find_or_initialize_by(
|
281
|
+
localization_key: key_without_locale
|
282
|
+
)
|
283
|
+
localization_key.is_visited_again = true if localization_key.is_deleted?
|
284
|
+
localization_key.save! if localization_key.changed?
|
307
285
|
localization_keys[key_without_locale] = localization_key.id
|
308
286
|
localization_key
|
309
287
|
end
|
data/lib/lit/export.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Lit
|
4
|
+
class Export
|
5
|
+
def self.call(locale_keys:, format:)
|
6
|
+
raise ArgumentError, "format must be yaml or csv" if %i[yaml csv].exclude?(format)
|
7
|
+
Lit.loader.cache.load_all_translations
|
8
|
+
localizations_scope = Lit::Localization.active
|
9
|
+
if locale_keys.present?
|
10
|
+
locale_ids = Lit::Locale.where(locale: locale_keys).pluck(:id)
|
11
|
+
localizations_scope = localizations_scope.where(locale_id: locale_ids) unless locale_ids.empty?
|
12
|
+
end
|
13
|
+
db_localizations = {}
|
14
|
+
localizations_scope.find_each do |l|
|
15
|
+
db_localizations[l.full_key] = l.translation
|
16
|
+
end
|
17
|
+
|
18
|
+
case format
|
19
|
+
when :yaml
|
20
|
+
exported_keys = nested_string_keys_to_hash(db_localizations)
|
21
|
+
exported_keys.to_yaml
|
22
|
+
when :csv
|
23
|
+
relevant_locales = locale_keys.presence || I18n.available_locales.map(&:to_s)
|
24
|
+
CSV.generate do |csv|
|
25
|
+
csv << ['key', *relevant_locales]
|
26
|
+
keys_without_locales = db_localizations.keys.map { |k| k.gsub(/(#{relevant_locales.join('|')})\./, '') }.uniq
|
27
|
+
keys_without_locales.each do |key_without_locale|
|
28
|
+
# Here, we need to determine if we're dealing with an array or a scalar.
|
29
|
+
# In the former case, for simplicity of editing (which is likely the main
|
30
|
+
# intent when exporting translations to CSV), let's make the "array" be simulated
|
31
|
+
# as a number of consecutive rows that have the same key.
|
32
|
+
#
|
33
|
+
# For example:
|
34
|
+
#
|
35
|
+
# key,en
|
36
|
+
# date.abbr_month_names, <-- in this case it's empty because that array has nothing at [0]
|
37
|
+
# date.abbr_month_names,Jan
|
38
|
+
# date.abbr_month_names,Feb
|
39
|
+
# date.abbr_month_names,Mar
|
40
|
+
# date.abbr_month_names,Apr
|
41
|
+
# date.abbr_month_names,May
|
42
|
+
# ...
|
43
|
+
|
44
|
+
key_localizations_per_locale =
|
45
|
+
relevant_locales.map { |l| Array.wrap(db_localizations["#{l}.#{key_without_locale}"]) }
|
46
|
+
transpose(key_localizations_per_locale).each do |translation_series|
|
47
|
+
csv << [key_without_locale, *translation_series]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private_class_method def self.nested_string_keys_to_hash(db_localizations)
|
55
|
+
# http://subtech.g.hatena.ne.jp/cho45/20061122
|
56
|
+
deep_proc = proc do |_k, s, o|
|
57
|
+
if s.is_a?(Hash) && o.is_a?(Hash)
|
58
|
+
next s.merge(o, &deep_proc)
|
59
|
+
end
|
60
|
+
next o
|
61
|
+
end
|
62
|
+
nested_keys = {}
|
63
|
+
db_localizations.sort.each do |k, v|
|
64
|
+
key_parts = k.to_s.split('.')
|
65
|
+
converted = key_parts.reverse.reduce(v) { |a, n| { n => a } }
|
66
|
+
nested_keys.merge!(converted, &deep_proc)
|
67
|
+
end
|
68
|
+
nested_keys
|
69
|
+
end
|
70
|
+
|
71
|
+
# This is like Array#transpose but ignores size differences between inner arrays.
|
72
|
+
private_class_method def self.transpose(matrix)
|
73
|
+
maxlen = matrix.max { |x| x.length }.length
|
74
|
+
matrix.each do |array|
|
75
|
+
array[maxlen - 1] = nil if array.length < maxlen
|
76
|
+
end
|
77
|
+
matrix.transpose
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/lit/i18n_backend.rb
CHANGED
@@ -3,6 +3,7 @@ require 'i18n'
|
|
3
3
|
module Lit
|
4
4
|
class I18nBackend
|
5
5
|
include I18n::Backend::Simple::Implementation
|
6
|
+
include I18n::Backend::Pluralization
|
6
7
|
|
7
8
|
attr_reader :cache
|
8
9
|
|
@@ -10,10 +11,9 @@ module Lit
|
|
10
11
|
@cache = cache
|
11
12
|
@available_locales_cache = nil
|
12
13
|
@translations = {}
|
13
|
-
reserved_keys = I18n.const_get
|
14
|
-
reserved_keys << :lit_default_copy
|
14
|
+
reserved_keys = I18n.const_get(:RESERVED_KEYS) + %i[lit_default_copy]
|
15
15
|
I18n.send(:remove_const, :RESERVED_KEYS)
|
16
|
-
I18n.const_set(:RESERVED_KEYS, reserved_keys)
|
16
|
+
I18n.const_set(:RESERVED_KEYS, reserved_keys.freeze)
|
17
17
|
end
|
18
18
|
|
19
19
|
def translate(locale, key, options = {})
|
@@ -59,7 +59,8 @@ module Lit
|
|
59
59
|
return true if options[:default].is_a?(String)
|
60
60
|
return true if options[:default].is_a?(Array) && \
|
61
61
|
(options[:default].first.is_a?(String) || \
|
62
|
-
options[:default].first.is_a?(Symbol)
|
62
|
+
options[:default].first.is_a?(Symbol) || \
|
63
|
+
options[:default].first.is_a?(Array))
|
63
64
|
false
|
64
65
|
end
|
65
66
|
|
@@ -69,11 +70,11 @@ module Lit
|
|
69
70
|
parts = I18n.normalize_keys(locale, key, scope, options[:separator])
|
70
71
|
key_with_locale = parts.join('.')
|
71
72
|
|
72
|
-
|
73
|
+
# check in cache or in simple backend
|
73
74
|
content = @cache[key_with_locale] || super
|
74
75
|
return content if parts.size <= 1
|
75
76
|
|
76
|
-
if content.nil? && should_cache?(key_with_locale)
|
77
|
+
if content.nil? && should_cache?(key_with_locale, options)
|
77
78
|
new_content = @cache.init_key_with_value(key_with_locale, content)
|
78
79
|
content = new_content if content.nil? # Content can change when Lit.humanize is true for example
|
79
80
|
# so there is no content in cache - it might not be if ie. we're doing
|
@@ -116,7 +117,7 @@ module Lit
|
|
116
117
|
end
|
117
118
|
end
|
118
119
|
end
|
119
|
-
|
120
|
+
# return translated content
|
120
121
|
content
|
121
122
|
end
|
122
123
|
|
@@ -180,8 +181,10 @@ module Lit
|
|
180
181
|
Lit.ignored_keys.any?{ |k| key_without_locale.start_with?(k) }
|
181
182
|
end
|
182
183
|
|
183
|
-
def should_cache?(key_with_locale)
|
184
|
-
|
184
|
+
def should_cache?(key_with_locale, options)
|
185
|
+
if @cache.has_key?(key_with_locale)
|
186
|
+
return false unless options[:default]
|
187
|
+
end
|
185
188
|
|
186
189
|
_, key_without_locale = ::Lit::Cache.split_key(key_with_locale)
|
187
190
|
return false if is_ignored_key(key_without_locale)
|
data/lib/lit/import.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Lit
|
4
|
+
class Import
|
5
|
+
class << self
|
6
|
+
def call(*args)
|
7
|
+
new(*args).perform
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :input, :locale_keys, :format, :skip_nil
|
12
|
+
|
13
|
+
def initialize(input:, locale_keys: [], format:, skip_nil: true, dry_run: false, raw: false)
|
14
|
+
raise ArgumentError, 'format must be yaml or csv' if %i[yaml csv].exclude?(format.to_sym)
|
15
|
+
@input = input
|
16
|
+
@locale_keys = locale_keys.presence || I18n.available_locales
|
17
|
+
@format = format
|
18
|
+
@skip_nil = skip_nil
|
19
|
+
@dry_run = dry_run
|
20
|
+
@raw = raw
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform
|
24
|
+
send(:"import_#{format}")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def import_yaml
|
30
|
+
validate_yaml
|
31
|
+
locale_keys.each do |locale|
|
32
|
+
I18n.with_locale(locale) do
|
33
|
+
yml = parsed_yaml[locale.to_s]
|
34
|
+
Hash[*Lit::Cache.flatten_hash(yml)].each do |key, default_translation|
|
35
|
+
next if default_translation.nil? && skip_nil
|
36
|
+
puts key
|
37
|
+
upsert(locale, key, default_translation)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
rescue Psych::SyntaxError => e
|
42
|
+
raise ArgumentError, "Invalid YAML file: #{e.message}", cause: e
|
43
|
+
end
|
44
|
+
|
45
|
+
def import_csv
|
46
|
+
validate_csv
|
47
|
+
processed_csv = preprocess_csv
|
48
|
+
|
49
|
+
processed_csv.each do |row|
|
50
|
+
key = row.first
|
51
|
+
row_translations = Hash[locales_in_csv.zip(row.drop(1))]
|
52
|
+
row_translations.each do |locale, value|
|
53
|
+
next unless locale_keys.blank? || locale_keys.map(&:to_sym).include?(locale.to_sym)
|
54
|
+
next if value.nil? && skip_nil
|
55
|
+
puts key
|
56
|
+
upsert(locale, key, value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
rescue CSV::MalformedCSVError => e
|
60
|
+
raise ArgumentError, "Invalid CSV file: #{e.message}", cause: e
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_yaml
|
64
|
+
errors = []
|
65
|
+
|
66
|
+
# YAML.load can return false, hence not using #empty?
|
67
|
+
errors << :yaml_is_empty if parsed_yaml.blank?
|
68
|
+
|
69
|
+
if parsed_yaml.present? &&
|
70
|
+
(locale_keys.map(&:to_sym) - parsed_yaml.keys.map(&:to_sym)).any?
|
71
|
+
errors << :not_all_requested_locales_included_in_header
|
72
|
+
end
|
73
|
+
|
74
|
+
fail ArgumentError, errors.map { |e| e.to_s.humanize }.to_sentence if errors.any?
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_csv # rubocop:disable Metrics/AbcSize
|
78
|
+
errors = []
|
79
|
+
|
80
|
+
# CSV may not be empty
|
81
|
+
errors << :csv_is_empty if parsed_csv.empty?
|
82
|
+
|
83
|
+
# verify CSV header
|
84
|
+
if !parsed_csv.empty? &&
|
85
|
+
(locale_keys.map(&:to_s) - parsed_csv[0].drop(1)).any?
|
86
|
+
errors << :not_all_requested_locales_included_in_header
|
87
|
+
end
|
88
|
+
|
89
|
+
# any further checks that we at some time think of should fall here
|
90
|
+
|
91
|
+
fail ArgumentError, errors.map { |e| e.to_s.humanize }.to_sentence if errors.any?
|
92
|
+
end
|
93
|
+
|
94
|
+
# the main task of this routine is to replace blanks with nils (in CSV it cannot be distinguished,
|
95
|
+
# so in order for :skip_nil option to work as intended blanks must be treated as nil);
|
96
|
+
# as well as that, we need to look for multiple occurrences of certain keys and merge them
|
97
|
+
# into arrays
|
98
|
+
def preprocess_csv
|
99
|
+
concatenate_arrays(replace_blanks(parsed_csv))
|
100
|
+
end
|
101
|
+
|
102
|
+
def parsed_csv
|
103
|
+
@parsed_csv ||=
|
104
|
+
begin
|
105
|
+
CSV.parse(input)
|
106
|
+
rescue CSV::MalformedCSVError
|
107
|
+
# Some Excel versions tend to save CSVs with columns separated with tabs instead
|
108
|
+
# of commas. Let's try that out if needed.
|
109
|
+
CSV.parse(input, col_sep: "\t")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def parsed_yaml
|
114
|
+
@parsed_yaml ||= YAML.load(input)
|
115
|
+
end
|
116
|
+
|
117
|
+
def locales_in_csv
|
118
|
+
@locales_in_csv ||= parsed_csv.first.drop(1)
|
119
|
+
end
|
120
|
+
|
121
|
+
# This is mean to insert a value for a key in a given locale
|
122
|
+
# using some kind of strategy which depends on the service's options.
|
123
|
+
#
|
124
|
+
# For instance, when @raw option is true (it's the default),
|
125
|
+
# if a key already exists, it overrides the default_value of the
|
126
|
+
# existing localization key; otherwise, with @raw set to false,
|
127
|
+
# it keeps the default as it is and, no matter if a translated value
|
128
|
+
# is there, translated_value is overridden with the imported one
|
129
|
+
# and is_changed is set to true.
|
130
|
+
def upsert(locale, key, value) # rubocop:disable Metrics/MethodLength
|
131
|
+
I18n.with_locale(locale) do
|
132
|
+
# when an array has to be inserted with a default value, it needs to
|
133
|
+
# be done like:
|
134
|
+
# I18n.t('foo', default: [['bar', 'baz']])
|
135
|
+
# because without the double array, array items are treated as fallback keys
|
136
|
+
# - then, the last array element is the final fallback; so in this case we
|
137
|
+
# don't specify fallback keys and only specify the final fallback, which
|
138
|
+
# is the array
|
139
|
+
val = value.is_a?(Array) ? [value] : value
|
140
|
+
I18n.t(key, default: val)
|
141
|
+
unless @raw
|
142
|
+
# this indicates that this translation already exists
|
143
|
+
existing_translation =
|
144
|
+
Lit::Localization.joins(:locale, :localization_key)
|
145
|
+
.find_by('localization_key = ? and locale = ?',
|
146
|
+
key, locale)
|
147
|
+
if existing_translation
|
148
|
+
existing_translation.update(translated_value: value, is_changed: true)
|
149
|
+
lkey = existing_translation.localization_key
|
150
|
+
lkey.update(is_deleted: false) if lkey.is_deleted
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def concatenate_arrays(csv) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/LineLength
|
157
|
+
csv.inject([]) do |accu, row|
|
158
|
+
if row.first == accu.last&.first # equal keys
|
159
|
+
accu.tap do
|
160
|
+
accu[-1] = [
|
161
|
+
row.first,
|
162
|
+
*accu[-1].drop(1)
|
163
|
+
.map { |x| Array.wrap(x) }
|
164
|
+
.zip(row.drop(1)).map(&:flatten)
|
165
|
+
]
|
166
|
+
end
|
167
|
+
else
|
168
|
+
accu << row
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def replace_blanks(csv)
|
174
|
+
csv.drop(1).each do |row|
|
175
|
+
row.replace(row.map(&:presence))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|