rails_i18n_manager 1.0.0
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 +7 -0
- data/LICENSE +7 -0
- data/README.md +177 -0
- data/Rakefile +18 -0
- data/app/controllers/rails_i18n_manager/application_controller.rb +5 -0
- data/app/controllers/rails_i18n_manager/translation_apps_controller.rb +71 -0
- data/app/controllers/rails_i18n_manager/translations_controller.rb +248 -0
- data/app/helpers/rails_i18n_manager/application_helper.rb +74 -0
- data/app/helpers/rails_i18n_manager/custom_form_builder.rb +223 -0
- data/app/jobs/rails_i18n_manager/application_job.rb +5 -0
- data/app/jobs/rails_i18n_manager/translations_import_job.rb +70 -0
- data/app/lib/rails_i18n_manager/forms/base.rb +25 -0
- data/app/lib/rails_i18n_manager/forms/translation_file_form.rb +55 -0
- data/app/lib/rails_i18n_manager/google_translate.rb +72 -0
- data/app/models/rails_i18n_manager/application_record.rb +20 -0
- data/app/models/rails_i18n_manager/translation_app.rb +108 -0
- data/app/models/rails_i18n_manager/translation_key.rb +161 -0
- data/app/models/rails_i18n_manager/translation_value.rb +11 -0
- data/app/views/layouts/rails_i18n_manager/application.html.slim +55 -0
- data/app/views/layouts/rails_i18n_manager/application.js.erb +5 -0
- data/app/views/rails_i18n_manager/form_builder/_basic_field.html.erb +108 -0
- data/app/views/rails_i18n_manager/form_builder/_error_notification.html.erb +7 -0
- data/app/views/rails_i18n_manager/shared/_flash.html.slim +6 -0
- data/app/views/rails_i18n_manager/translation_apps/_breadcrumbs.html.slim +12 -0
- data/app/views/rails_i18n_manager/translation_apps/_filter_bar.html.slim +9 -0
- data/app/views/rails_i18n_manager/translation_apps/form.html.slim +30 -0
- data/app/views/rails_i18n_manager/translation_apps/index.html.slim +27 -0
- data/app/views/rails_i18n_manager/translations/_breadcrumbs.html.slim +17 -0
- data/app/views/rails_i18n_manager/translations/_filter_bar.html.slim +17 -0
- data/app/views/rails_i18n_manager/translations/_form.html.slim +29 -0
- data/app/views/rails_i18n_manager/translations/_sub_nav.html.slim +7 -0
- data/app/views/rails_i18n_manager/translations/_translation_value_fields.html.slim +10 -0
- data/app/views/rails_i18n_manager/translations/edit.html.slim +6 -0
- data/app/views/rails_i18n_manager/translations/import.html.slim +26 -0
- data/app/views/rails_i18n_manager/translations/index.html.slim +50 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +22 -0
- data/db/migrate/20221001001344_add_rails_i18n_manager_tables.rb +26 -0
- data/lib/rails_i18n_manager/config.rb +20 -0
- data/lib/rails_i18n_manager/engine.rb +42 -0
- data/lib/rails_i18n_manager/version.rb +3 -0
- data/lib/rails_i18n_manager.rb +60 -0
- data/public/rails_i18n_manager/application.css +67 -0
- data/public/rails_i18n_manager/application.js +37 -0
- data/public/rails_i18n_manager/favicon.ico +0 -0
- data/public/rails_i18n_manager/utility.css +99 -0
- metadata +292 -0
@@ -0,0 +1,223 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
class CustomFormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
|
4
|
+
ALLOWED_OPTIONS = [
|
5
|
+
:value,
|
6
|
+
:name,
|
7
|
+
:label,
|
8
|
+
:field_wrapper_html,
|
9
|
+
:label_wrapper_html,
|
10
|
+
:label_html,
|
11
|
+
:input_wrapper_html,
|
12
|
+
:input_html,
|
13
|
+
:required,
|
14
|
+
:required_text,
|
15
|
+
:help_text,
|
16
|
+
:errors,
|
17
|
+
:field_layout,
|
18
|
+
:view_mode,
|
19
|
+
|
20
|
+
### SELECT OPTIONS
|
21
|
+
:collection,
|
22
|
+
:selected,
|
23
|
+
:disabled,
|
24
|
+
:prompt,
|
25
|
+
:include_blank,
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
def error_notification
|
29
|
+
@template.render "rails_i18n_manager/form_builder/error_notification", {f: self}
|
30
|
+
end
|
31
|
+
|
32
|
+
def view_field(label:, value:, **options)
|
33
|
+
field(nil, type: :view, **options.merge(label: label, value: value, view_mode: true))
|
34
|
+
end
|
35
|
+
|
36
|
+
def field(method, type:, **options)
|
37
|
+
type = type.to_sym
|
38
|
+
|
39
|
+
options = _transform_options(options, method)
|
40
|
+
|
41
|
+
if options[:view_mode]
|
42
|
+
return _view_field(method, type, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
invalid_options = options.keys - ALLOWED_OPTIONS
|
46
|
+
if invalid_options.any?
|
47
|
+
|
48
|
+
raise "Invalid options provided: #{invalid_options.join(", ")}"
|
49
|
+
end
|
50
|
+
|
51
|
+
if [:select, :textarea].exclude?(type)
|
52
|
+
options[:input_html][:type] = type.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
case type
|
56
|
+
when :select
|
57
|
+
options[:collection] = _fetch_required_option(:collection, options)
|
58
|
+
|
59
|
+
options = _transform_select_options(options, method)
|
60
|
+
when :checkbox
|
61
|
+
options = _transform_checkbox_options(options, method)
|
62
|
+
else
|
63
|
+
if !options[:input_html].has_key?(:value)
|
64
|
+
options[:input_html][:value] = object.send(method)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
@template.render("rails_i18n_manager/form_builder/basic_field", {
|
69
|
+
f: self,
|
70
|
+
method: method,
|
71
|
+
type: type,
|
72
|
+
options: options,
|
73
|
+
})
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def _view_field(method, type, options)
|
79
|
+
options[:input_html][:class] ||= ""
|
80
|
+
options[:input_html][:class].concat(" form-control-plaintext").strip!
|
81
|
+
options[:input_html][:readonly] = true
|
82
|
+
options[:input_html][:type] = "text"
|
83
|
+
options[:input_html].delete(:name)
|
84
|
+
|
85
|
+
if !options[:input_html].has_key?(:value)
|
86
|
+
options[:input_html][:value] = _determine_display_value(method, type, options)
|
87
|
+
end
|
88
|
+
|
89
|
+
@template.render("rails_i18n_manager/form_builder/basic_field", {
|
90
|
+
f: self,
|
91
|
+
method: method,
|
92
|
+
type: :view,
|
93
|
+
options: options,
|
94
|
+
})
|
95
|
+
end
|
96
|
+
|
97
|
+
def _defaults
|
98
|
+
@_defaults ||= (@template.instance_variable_get(:@_custom_form_for_defaults) || {}).deep_symbolize_keys!
|
99
|
+
end
|
100
|
+
|
101
|
+
def _attr_presence_required?(attr)
|
102
|
+
if attr && object.respond_to?(attr)
|
103
|
+
(@object.try(:klass) || @object.class).validators_on(attr).any?{|x| x.kind.to_sym == :presence }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _fetch_required_option(key, options)
|
108
|
+
if !options.has_key?(key)
|
109
|
+
raise ArgumentError.new("Missing required option :#{key}")
|
110
|
+
end
|
111
|
+
options[key]
|
112
|
+
end
|
113
|
+
|
114
|
+
def _determine_display_value(method, type, options)
|
115
|
+
case type
|
116
|
+
when :checkbox
|
117
|
+
options[:input_html]&.has_key?(:checked) ? options[:input_html][:checked] : @object.send(method)
|
118
|
+
when :select
|
119
|
+
if options.has_key?(:selected)
|
120
|
+
val = options[:selected]
|
121
|
+
else
|
122
|
+
if options[:input_html].has_key?(:value)
|
123
|
+
val = options[:input_html].delete(:value)
|
124
|
+
else
|
125
|
+
val = object.send(method)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
selected_opt = options[:collection].detect { |opt|
|
130
|
+
val == (opt.is_a?(Array) ? opt[1] : opt)
|
131
|
+
}
|
132
|
+
|
133
|
+
selected_opt.is_a?(Array) ? selected_opt[0] : selected_opt
|
134
|
+
else
|
135
|
+
options[:input_html]&.has_key?(:value) ? options[:value] : @object.send(method)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def _transform_options(options, method)
|
140
|
+
options.deep_symbolize_keys!
|
141
|
+
|
142
|
+
options = _defaults.merge(options)
|
143
|
+
|
144
|
+
options[:label] = options.has_key?(:label) ? options[:label] : method.to_s.titleize
|
145
|
+
|
146
|
+
options[:field_wrapper_html] ||= {}
|
147
|
+
options[:label_wrapper_html] ||= {}
|
148
|
+
options[:label_html] ||= {}
|
149
|
+
options[:input_wrapper_html] ||= {}
|
150
|
+
options[:input_html] ||= {}
|
151
|
+
|
152
|
+
### Shortcuts for some input_html arguments
|
153
|
+
[:value, :name].each do |key|
|
154
|
+
if options.has_key?(key) && !options[:input_html].has_key?(key)
|
155
|
+
options[:input_html][key] = options[key]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
options[:field_wrapper_html][:class] ||= ""
|
160
|
+
options[:field_wrapper_html][:class].concat(" form-group").strip!
|
161
|
+
|
162
|
+
if !options.has_key?(:field_layout)
|
163
|
+
options[:field_layout] = :vertical
|
164
|
+
end
|
165
|
+
options[:field_layout] = options[:field_layout].to_sym
|
166
|
+
|
167
|
+
options[:required] = options.has_key?(:required) ? options[:required] : _attr_presence_required?(method)
|
168
|
+
|
169
|
+
options[:required_text] ||= "*"
|
170
|
+
|
171
|
+
options[:field_wrapper_html][:class].concat(" #{method}_field").strip!
|
172
|
+
|
173
|
+
if method && !options.has_key?(:errors)
|
174
|
+
options[:errors] = @object.errors[method]
|
175
|
+
end
|
176
|
+
|
177
|
+
if options[:errors].present?
|
178
|
+
options[:input_html][:class] ||= ""
|
179
|
+
options[:input_html][:class].concat(" is-invalid")
|
180
|
+
end
|
181
|
+
|
182
|
+
options
|
183
|
+
end
|
184
|
+
|
185
|
+
def _transform_select_options(options, method)
|
186
|
+
if !options.has_key?(:selected)
|
187
|
+
if options[:input_html].has_key?(:value)
|
188
|
+
options[:selected] = options[:input_html].delete(:value)
|
189
|
+
else
|
190
|
+
options[:selected] = object.send(method)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
if options[:disabled].is_a?(TrueClass) && !options[:input_html].has_key?(:disabled)
|
195
|
+
options.delete(:disabled)
|
196
|
+
options[:input_html][:disabled] = true
|
197
|
+
end
|
198
|
+
|
199
|
+
options
|
200
|
+
end
|
201
|
+
|
202
|
+
def _transform_checkbox_options(options, method)
|
203
|
+
if options[:input_html].has_key?(:value) && !options[:input_html].has_key?(:checked)
|
204
|
+
options[:input_html][:checked] = (object.send(method) == options[:input_html][:value])
|
205
|
+
elsif @object.class.respond_to?(:columns_hash) && @object.class.columns_hash[method]&.type == :boolean
|
206
|
+
if !options[:input_html].has_key?(:checked)
|
207
|
+
if options[:input_html].has_key?(:value)
|
208
|
+
options[:input_html][:checked] = (options[:input_html][:value] == true)
|
209
|
+
else
|
210
|
+
options[:input_html][:checked] = (object.send(method) == true)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if !options[:input_html].has_key?(:value)
|
215
|
+
options[:input_html][:value] = "1"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
options
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
class TranslationsImportJob < ApplicationJob
|
3
|
+
|
4
|
+
class ImportAbortedError < StandardError; end
|
5
|
+
|
6
|
+
def perform(translation_app_id:, import_file:, overwrite_existing: false, mark_inactive_translations: false)
|
7
|
+
app_record = TranslationApp.find(translation_app_id)
|
8
|
+
|
9
|
+
if import_file.end_with?(".json")
|
10
|
+
translations_hash = JSON.parse(File.read(import_file))
|
11
|
+
else
|
12
|
+
translations_hash = YAML.safe_load(File.read(import_file))
|
13
|
+
end
|
14
|
+
|
15
|
+
new_locales = translations_hash.keys - app_record.all_locales
|
16
|
+
|
17
|
+
if new_locales.any?
|
18
|
+
raise ImportAbortedError.new("Import aborted. Locale not listed in translation app: #{new_locales.join(', ')}")
|
19
|
+
end
|
20
|
+
|
21
|
+
all_keys = RailsI18nManager.fetch_flattened_dot_notation_keys(translations_hash)
|
22
|
+
|
23
|
+
key_records_by_key = app_record.translation_keys.includes(:translation_values).index_by(&:key)
|
24
|
+
|
25
|
+
all_keys.each do |key|
|
26
|
+
if key_records_by_key[key].nil?
|
27
|
+
key_records_by_key[key] = app_record.translation_keys.new(key: key)
|
28
|
+
key_records_by_key[key].save!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
translation_values_to_import = []
|
33
|
+
|
34
|
+
key_records_by_key.each do |key, key_record|
|
35
|
+
app_record.all_locales.each do |locale|
|
36
|
+
split_keys = [locale] + key.split(".").map{|x| x}
|
37
|
+
|
38
|
+
val = translations_hash.dig(*split_keys)
|
39
|
+
|
40
|
+
if val.present?
|
41
|
+
val_record = key_record.translation_values.detect{|x| x.locale == locale.to_s }
|
42
|
+
|
43
|
+
if val_record.nil?
|
44
|
+
translation_values_to_import << key_record.translation_values.new(locale: locale, translation: val)
|
45
|
+
elsif val_record.translation.blank? || (overwrite_existing && val_record.translation != val)
|
46
|
+
val_record.update!(translation: val)
|
47
|
+
next
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
### We use active_record-import for big speedup, set validate false if more speed required
|
54
|
+
TranslationValue.import(translation_values_to_import, validate: true)
|
55
|
+
|
56
|
+
if mark_inactive_translations
|
57
|
+
app_record.translation_keys
|
58
|
+
.where.not(key: all_keys)
|
59
|
+
.update_all(active: false)
|
60
|
+
|
61
|
+
app_record.translation_keys
|
62
|
+
.where(key: all_keys)
|
63
|
+
.update_all(active: true)
|
64
|
+
end
|
65
|
+
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
module Forms
|
3
|
+
class Base
|
4
|
+
include ActiveModel::Validations
|
5
|
+
|
6
|
+
def initialize(attrs={})
|
7
|
+
attrs ||= {}
|
8
|
+
|
9
|
+
attrs.each do |k,v|
|
10
|
+
self.send("#{k}=", v) ### Use send so that it checks that attr_accessor has already defined the method so its a valid attribute
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_key
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def model_name
|
19
|
+
sanitized_class_name = self.class.name.to_s.gsub("Forms::", '').gsub(/Form$/, '')
|
20
|
+
ActiveModel::Name.new(self, self.class.superclass, sanitized_class_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
module Forms
|
3
|
+
class TranslationFileForm < Base
|
4
|
+
|
5
|
+
attr_accessor :translation_app_id, :file, :overwrite_existing
|
6
|
+
attr_reader :overwrite_existing, :mark_inactive_translations
|
7
|
+
|
8
|
+
validates :translation_app_id, presence: {message: "Must select an App"}
|
9
|
+
validates :file, presence: true
|
10
|
+
validate :validate_file
|
11
|
+
|
12
|
+
def overwrite_existing=(val)
|
13
|
+
@overwrite_existing = ["1", "true", "t"].include?(val.to_s.downcase)
|
14
|
+
end
|
15
|
+
|
16
|
+
def mark_inactive_translations=(val)
|
17
|
+
@mark_inactive_translations = ["1", "true", "t"].include?(val.to_s.downcase)
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_file
|
21
|
+
if file.blank?
|
22
|
+
errors.add(:file, "Must upload a valid translation file.")
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
if [".yml", ".json"].exclude?(File.extname(file))
|
27
|
+
errors.add(:file, "Invalid file format. Must be yml or json file.")
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
if File.read(file).blank?
|
32
|
+
errors.add(:file, "Empty file provided.")
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
case File.extname(file)
|
37
|
+
when ".yml"
|
38
|
+
if !YAML.safe_load(File.read(file)).is_a?(Hash)
|
39
|
+
errors.add(:file, "Invalid yml file.")
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
when ".json"
|
44
|
+
begin
|
45
|
+
JSON.parse(File.read(file))
|
46
|
+
rescue JSON::ParserError
|
47
|
+
errors.add(:file, "Invalid json file.")
|
48
|
+
return
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
module GoogleTranslate
|
3
|
+
|
4
|
+
def self.translate(text, from:, to:)
|
5
|
+
api_key = RailsI18nManager.config.google_translate_api_key
|
6
|
+
|
7
|
+
if !supported_locales.include?(to.to_s) || Rails.env.test? || (api_key.blank? && Rails.env.development?)
|
8
|
+
return false
|
9
|
+
end
|
10
|
+
|
11
|
+
if text.include?("<") && text.include?(">")
|
12
|
+
### Dont translate any HTML strings
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
EasyTranslate.translate(text, from: from, to: to, key: api_key)
|
17
|
+
|
18
|
+
if translation
|
19
|
+
str = translation.to_s
|
20
|
+
|
21
|
+
if str.present?
|
22
|
+
### Replace single quote html entity with single quote character
|
23
|
+
str = str.gsub("'", "'")
|
24
|
+
|
25
|
+
if to.to_s == "es"
|
26
|
+
str = str.gsub("% {", " %{").strip
|
27
|
+
end
|
28
|
+
|
29
|
+
return str
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
### List retrieved from Google Translate (2022)
|
35
|
+
@@supported_locales = ["af", "am", "ar", "az", "be", "bg", "bn", "bs", "ca", "ceb", "co", "cs", "cy", "da", "de", "el", "en", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gd", "gl", "gu", "ha", "haw", "he", "hi", "hmn", "hr", "ht", "hu", "hy", "id", "ig", "is", "it", "iw", "ja", "jw", "ka", "kk", "km", "kn", "ko", "ku", "ky", "la", "lb", "lo", "lt", "lv", "mg", "mi", "mk", "ml", "mn", "mr", "ms", "mt", "my", "ne", "nl", "no", "ny", "or", "pa", "pl", "ps", "pt", "ro", "ru", "rw", "sd", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "tk", "tl", "tr", "tt", "ug", "uk", "ur", "uz", "vi", "xh", "yi", "yo", "zh", "zh-CN", "zh-TW", "zu"].freeze
|
36
|
+
mattr_reader :supported_locales
|
37
|
+
|
38
|
+
### FOR official client
|
39
|
+
# require "google/cloud/translate/v2" ### Offical Google Translate with API Key
|
40
|
+
# def self.client
|
41
|
+
# @@client ||= begin
|
42
|
+
# api_key = RailsI18nManager.config.google_translate_api_key
|
43
|
+
# if Rails.env.test? || (api_key.blank? && Rails.env.development?)
|
44
|
+
# ### Skip Client
|
45
|
+
# nil
|
46
|
+
# else
|
47
|
+
# Google::Cloud::Translate::V2.new(key: api_key)
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# def self.supported_locales
|
52
|
+
# @@suported_locales ||= begin
|
53
|
+
# if client
|
54
|
+
# @@supported_locales = client.languages.map{|x| x.code}
|
55
|
+
# else
|
56
|
+
# []
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
# def self.translate(text, from:, to:)
|
61
|
+
# if client
|
62
|
+
# begin
|
63
|
+
# translation = client.translate(text, from: from, to: to)
|
64
|
+
# rescue Google::Cloud::InvalidArgumentError
|
65
|
+
# ### Error usually caused by an unsupported locale
|
66
|
+
# return nil
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
class ApplicationRecord < ActiveRecord::Base
|
3
|
+
self.abstract_class = true
|
4
|
+
|
5
|
+
include ActiveSortOrder
|
6
|
+
|
7
|
+
scope :multi_search, ->(full_str){
|
8
|
+
if full_str.present?
|
9
|
+
rel = self
|
10
|
+
|
11
|
+
full_str.split(' ').each do |q|
|
12
|
+
rel = rel.search(q)
|
13
|
+
end
|
14
|
+
|
15
|
+
next rel
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module RailsI18nManager
|
2
|
+
class TranslationApp < ApplicationRecord
|
3
|
+
NAME = "Translated App".freeze
|
4
|
+
|
5
|
+
has_many :translation_keys, class_name: "RailsI18nManager::TranslationKey", dependent: :destroy
|
6
|
+
|
7
|
+
before_validation :clean_additional_locales
|
8
|
+
after_update :handle_removed_locales
|
9
|
+
after_update :handle_added_locales
|
10
|
+
|
11
|
+
validates :name, presence: true, uniqueness: {case_sensitive: false}
|
12
|
+
validates :default_locale, presence: true
|
13
|
+
validate :validate_additional_locales
|
14
|
+
|
15
|
+
scope :search, ->(str){
|
16
|
+
fields = [
|
17
|
+
"#{table_name}.name",
|
18
|
+
]
|
19
|
+
|
20
|
+
like = connection.adapter_name.downcase.to_s == "postgres" ? "ILIKE" : "LIKE"
|
21
|
+
|
22
|
+
sql_conditions = []
|
23
|
+
|
24
|
+
fields.each do |col|
|
25
|
+
sql_conditions << "(#{col} #{like} :search)"
|
26
|
+
end
|
27
|
+
|
28
|
+
self.where(sql_conditions.join(" OR "), search: "%#{str}%")
|
29
|
+
}
|
30
|
+
|
31
|
+
def additional_locales=(val)
|
32
|
+
if val.is_a?(Array)
|
33
|
+
val = val.map{|x| x.to_s.downcase.strip.presence }.compact.uniq.sort
|
34
|
+
val.delete(self.default_locale)
|
35
|
+
|
36
|
+
self[:additional_locales] = val.join(",")
|
37
|
+
else
|
38
|
+
self[:additional_locales] = val
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def additional_locales_array
|
43
|
+
additional_locales.to_s.split(",")
|
44
|
+
end
|
45
|
+
|
46
|
+
def all_locales
|
47
|
+
[self.default_locale] + additional_locales_array
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def validate_additional_locales
|
53
|
+
if additional_locales_changed?
|
54
|
+
invalid_locales = []
|
55
|
+
|
56
|
+
additional_locales_array.each do |locale|
|
57
|
+
if !RailsI18nManager.config.valid_locales.include?(locale)
|
58
|
+
invalid_locales << locale
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if invalid_locales.any?
|
63
|
+
self.errors.add(:additional_locales, "Invalid locales: #{invalid_locales.join(", ")}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def clean_additional_locales
|
69
|
+
if additional_locales_changed?
|
70
|
+
cleaned_array = additional_locales_array.map{|x| x.to_s.downcase.strip.presence }.compact.uniq.sort
|
71
|
+
cleaned_array.delete(self.default_locale)
|
72
|
+
|
73
|
+
self.additional_locales = cleaned_array.join(",")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_removed_locales
|
78
|
+
if previous_changes.has_key?("default_locale") || previous_changes.has_key?("additional_locales")
|
79
|
+
TranslationValue
|
80
|
+
.joins(:translation_key).where(TranslationKey.table_name => {translation_app_id: self.id})
|
81
|
+
.where.not(locale: all_locales)
|
82
|
+
.delete_all ### instead of destroy_all, use delete_all for speedup
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_added_locales
|
87
|
+
### ATTEMPTING TO JUST SKIP THIS
|
88
|
+
|
89
|
+
# ### For new locales, create TranslationValue records
|
90
|
+
# value_records_for_import = []
|
91
|
+
|
92
|
+
# translation_keys.includes(:translation_values).each do |key_record|
|
93
|
+
# additional_locales_array.each do |locale|
|
94
|
+
# val_record = key_record.translation_values.detect{|x| x.locale == locale }
|
95
|
+
|
96
|
+
# if val_record.nil?
|
97
|
+
# value_records_for_import << key_record.translation_values.new(locale: locale)
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
|
102
|
+
# ### We use active_record-import for big speedup also using validate: false for more speed
|
103
|
+
# TranslationValue.import(value_records_for_import, validate: false)
|
104
|
+
# end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|