releaf-i18n_database 0.2.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -21
  3. data/app/assets/javascripts/{releaf/controllers → controllers}/releaf/i18n_database/translations.js +0 -0
  4. data/app/assets/stylesheets/{releaf/controllers → controllers}/releaf/i18n_database/translations.scss +0 -0
  5. data/app/builders/releaf/i18n_database/translations/builders_common.rb +1 -1
  6. data/app/builders/releaf/i18n_database/translations/index_builder.rb +1 -1
  7. data/app/controllers/releaf/i18n_database/translations_controller.rb +121 -127
  8. data/app/lib/releaf/i18n_database/parse_spreadsheet_translations.rb +62 -0
  9. data/app/lib/releaf/i18n_database/translations_store.rb +149 -0
  10. data/app/lib/releaf/i18n_database/translations_utilities.rb +6 -6
  11. data/app/models/releaf/i18n_database/i18n_entry.rb +21 -0
  12. data/app/models/releaf/i18n_database/i18n_entry_translation.rb +11 -0
  13. data/app/views/releaf/i18n_database/translations/_form_fields.haml +2 -2
  14. data/lib/releaf-i18n_database.rb +14 -3
  15. data/lib/releaf/i18n_database/backend.rb +56 -131
  16. data/lib/releaf/i18n_database/configuration.rb +8 -0
  17. data/lib/releaf/i18n_database/engine.rb +1 -30
  18. data/lib/releaf/i18n_database/humanize_missing_translations.rb +1 -1
  19. data/misc/translations.xlsx +0 -0
  20. data/spec/builders/translations/builder_common_spec.rb +1 -1
  21. data/spec/builders/translations/edit_builder_spec.rb +2 -2
  22. data/spec/builders/translations/table_builder_spec.rb +1 -1
  23. data/spec/controllers/i18n_backend/translations_controller_spec.rb +10 -19
  24. data/spec/features/translations_spec.rb +235 -16
  25. data/spec/fixtures/invalid.xls +3 -0
  26. data/spec/fixtures/invalid.xlsx +3 -0
  27. data/spec/lib/releaf/i18n_database/backend_spec.rb +192 -0
  28. data/spec/lib/releaf/i18n_database/configuration_spec.rb +13 -0
  29. data/spec/lib/{i18n_database → releaf/i18n_database}/humanize_missing_translations_spec.rb +7 -1
  30. data/spec/lib/releaf/i18n_database/parse_spreadsheet_translations_spec.rb +151 -0
  31. data/spec/lib/releaf/i18n_database/translations_store_spec.rb +548 -0
  32. data/spec/lib/{i18n_database → releaf/i18n_database}/translations_utilities_spec.rb +39 -39
  33. data/spec/models/i18n_database/i18n_entry_spec.rb +50 -0
  34. data/spec/models/i18n_database/i18n_entry_translation_spec.rb +9 -0
  35. metadata +45 -30
  36. data/app/lib/releaf/i18n_database/translations_importer.rb +0 -72
  37. data/app/models/releaf/i18n_database/translation.rb +0 -17
  38. data/app/models/releaf/i18n_database/translation_data.rb +0 -11
  39. data/lib/releaf/i18n_database/builders_autoload.rb +0 -10
  40. data/releaf-i18n_database.gemspec +0 -21
  41. data/spec/lib/i18n_database/backend_spec.rb +0 -337
  42. data/spec/lib/i18n_database/translations_importer_spec.rb +0 -17
  43. data/spec/models/i18n_database/translation_data_spec.rb +0 -13
  44. data/spec/models/i18n_database/translation_spec.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1708060372539566d0f74c1d83d592b6052fab44
4
- data.tar.gz: 26aa76b2f65753dddb9e0b85cfc3a2e4449daf30
3
+ metadata.gz: 9c95500c88e31844f3bb04328fae6744220abb94
4
+ data.tar.gz: 6bfb4f23bf7bab4f737ff2cfde0fc0055e4eb301
5
5
  SHA512:
6
- metadata.gz: a1c4f3c879bdecf8b93032975e29b2b178121d51d705478bf1c3e72acf9d04eb091e001f603af442597fddef113d01e8cf71ca9ac884713096be8722ff6a39fa
7
- data.tar.gz: 086aed43840796336776f6f23a09b472a90b1c0909290649bc26aa88f7a0c541e4a1a129201f3c5c4cfde52bc1cfe7bdf1a2eb04c4cb67e09dad8246c0e767d8
6
+ metadata.gz: 68ca2b2d9c701db9daadfabf4a80f659253ac0b2c75bac4ea3c5aad372a57c000129deeba3ed8a95590605845bc2220bd358fcb0cfe58dd627a74d166ad30443
7
+ data.tar.gz: bcda02afedc7bec4e3921f97603929e30c0a6b74ef675f18a55b943a41dd8fb11a4c31912dff49b5faedc15772c43d1a151b699d140bf39b44f51df6149e42f4
data/LICENSE CHANGED
@@ -1,24 +1,22 @@
1
1
  Copyright (c) 2012, CubeSystems <info@cubesystems.lv>
2
- All rights reserved.
3
2
 
4
- Redistribution and use in source and binary forms, with or without
5
- modification, are permitted provided that the following conditions are met:
6
- * Redistributions of source code must retain the above copyright
7
- notice, this list of conditions and the following disclaimer.
8
- * Redistributions in binary form must reproduce the above copyright
9
- notice, this list of conditions and the following disclaimer in the
10
- documentation and/or other materials provided with the distribution.
11
- * Neither the name of the CubeSystems nor the names of its contributors may
12
- be used to endorse or promote products derived from this software without
13
- specific prior written permission.
3
+ MIT License
14
4
 
15
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
- DISCLAIMED. IN NO EVENT SHALL CubeSystems BE LIABLE FOR ANY
19
- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -7,7 +7,7 @@ module Releaf::I18nDatabase::Translations
7
7
 
8
8
  def export_button
9
9
  url = action_url(:export, format: :xlsx)
10
- button(t("export"), "download", class: "secondary", href: url)
10
+ button(t("Export"), "download", class: "secondary", href: url)
11
11
  end
12
12
  end
13
13
  end
@@ -24,7 +24,7 @@ module Releaf::I18nDatabase::Translations
24
24
  end
25
25
 
26
26
  def import_button
27
- button(t("import"), "upload", name: "import", class: "secondary")
27
+ button(t("Import"), "upload", name: "import", class: "secondary")
28
28
  end
29
29
 
30
30
  def import_form
@@ -1,171 +1,165 @@
1
- module Releaf::I18nDatabase
2
- class TranslationsController < ::Releaf::BaseController
3
- def self.resource_class
4
- Releaf::I18nDatabase::Translation
5
- end
1
+ class Releaf::I18nDatabase::TranslationsController < ::Releaf::ActionController
2
+ def self.resource_class
3
+ Releaf::I18nDatabase::I18nEntry
4
+ end
6
5
 
7
- def edit
8
- @collection = resources
9
- search(params[:search])
10
- end
6
+ def edit
7
+ @collection = resources
8
+ search(params[:search])
9
+ end
11
10
 
12
- def export
13
- @collection = resources
14
- search(params[:search])
11
+ def export
12
+ @collection = resources
13
+ search(params[:search])
15
14
 
16
- respond_to do |format|
17
- format.xlsx do
18
- response.headers['Content-Disposition'] = "attachment; filename=\"#{export_file_name}\""
19
- end
15
+ respond_to do |format|
16
+ format.xlsx do
17
+ response.headers['Content-Disposition'] = "attachment; filename=\"#{export_file_name}\""
20
18
  end
21
19
  end
20
+ end
22
21
 
23
- def build_breadcrumbs
24
- super
22
+ def build_breadcrumbs
23
+ super
25
24
 
26
- if %w[edit update].include?(params[:action]) && !params.has_key?(:import)
27
- @breadcrumbs << { name: I18n.t("edit translations", scope: controller_scope_name), url: url_for(action: :edit, search: params[:search]) }
28
- end
25
+ if %w[edit update].include?(params[:action]) && !params.has_key?(:import)
26
+ @breadcrumbs << { name: I18n.t("Edit translations", scope: controller_scope_name), url: url_for(action: :edit, search: params[:search]) }
29
27
  end
28
+ end
29
+
30
+ def update
31
+ @collection = []
32
+ @translations_to_save = []
33
+
34
+ valid = build_updatables(params[:translations])
35
+ import_view if params.has_key?(:import)
30
36
 
31
- def update
32
- @collection = []
33
- @translations_to_save = []
34
-
35
- valid = build_updatables(params[:translations])
36
- import_view if params.has_key?(:import)
37
-
38
- respond_to do |format|
39
- format.html do
40
- if valid
41
- process_updatables
42
- update_response_success
43
- else
44
- render_notification false, now: true
45
- render action: :edit
46
- end
37
+ respond_to do |format|
38
+ format.html do
39
+ if valid
40
+ process_updatables
41
+ update_response_success
42
+ else
43
+ render_notification false, now: true
44
+ render action: :edit
47
45
  end
48
46
  end
49
47
  end
48
+ end
50
49
 
51
- def resources
52
- Releaf::I18nDatabase::TranslationsUtilities.include_localizations(super).order(:key)
53
- end
50
+ def resources
51
+ Releaf::I18nDatabase::TranslationsUtilities.include_localizations(super).order(:key)
52
+ end
54
53
 
55
- # overwrite search here
56
- def search(lookup_string)
57
- @collection = Releaf::I18nDatabase::TranslationsUtilities.search(@collection, lookup_string, params[:only_blank].present?)
58
- end
54
+ # overwrite search here
55
+ def search(lookup_string)
56
+ @collection = Releaf::I18nDatabase::TranslationsUtilities.search(@collection, lookup_string, params[:only_blank].present?)
57
+ end
59
58
 
60
- def import
61
- if File.exists?(import_file_path) && !import_file_extension.blank?
62
- begin
63
- @collection = Releaf::I18nDatabase::TranslationsImporter.new(import_file_path, import_file_extension).parsed_output
64
- import_view
65
- render :edit
66
- rescue Releaf::I18nDatabase::TranslationsImporter::UnsupportedFileFormatError
67
- flash["error"] = { "id" => "resource_status", "message" => I18n.t("Unsupported file format", scope: notice_scope_name) }
68
- redirect_to action: :index
69
- end
70
- else
59
+ def import
60
+ if File.exist?(import_file_path)
61
+ begin
62
+ @collection = Releaf::I18nDatabase::ParseSpreadsheetTranslations.call(file_path: import_file_path, extension: import_file_extension)
63
+ import_view
64
+ render :edit
65
+ rescue Releaf::I18nDatabase::ParseSpreadsheetTranslations::UnsupportedFileFormatError
66
+ flash["error"] = { "id" => "resource_status", "message" => I18n.t("Unsupported file format", scope: notice_scope_name) }
71
67
  redirect_to action: :index
72
68
  end
69
+ else
70
+ redirect_to action: :index
73
71
  end
72
+ end
74
73
 
75
- def import_view
76
- @import = true
77
- @breadcrumbs << { name: I18n.t("import", scope: controller_scope_name) }
78
- end
79
-
80
- def setup
81
- super
82
- @features = {
83
- index: true
84
- }
85
- end
74
+ def import_view
75
+ @import = true
76
+ @breadcrumbs << { name: I18n.t("import", scope: controller_scope_name) }
77
+ end
86
78
 
87
- def action_views
88
- super.merge(import: :edit)
89
- end
79
+ def features
80
+ [:index, :search]
81
+ end
90
82
 
91
- def action_features
92
- {index: :index}.with_indifferent_access
93
- end
83
+ def action_views
84
+ super.merge(import: :edit)
85
+ end
94
86
 
95
- private
87
+ def action_features
88
+ {index: :index}.with_indifferent_access
89
+ end
96
90
 
97
- def process_updatables
98
- resource_class.where(id: @translation_ids_to_destroy).destroy_all unless @translation_ids_to_destroy.empty?
99
- @translations_to_save.map(&:save!)
100
- Releaf::I18nDatabase::Backend.translations_updated_at = Time.now
101
- end
91
+ private
102
92
 
103
- def build_updatables translations_params
104
- valid = true
105
- @translation_ids_to_destroy = params.fetch(:existing_translations, "").split(",")
93
+ def process_updatables
94
+ resource_class.where(id: @translation_ids_to_destroy).destroy_all unless @translation_ids_to_destroy.empty?
95
+ @translations_to_save.map(&:save!)
96
+ Releaf::I18nDatabase::Backend.translations_updated_at = Time.now
97
+ end
106
98
 
107
- translations_params ||= []
108
- translations_params.each do |values|
109
- translation = load_translation(values["key"], values["localizations"])
99
+ def build_updatables translations_params
100
+ valid = true
101
+ @translation_ids_to_destroy = params.fetch(:existing_translations, "").split(",")
110
102
 
111
- if translation.valid?
112
- @translations_to_save << translation
113
- @translation_ids_to_destroy.delete(translation.id.to_s)
114
- else
115
- valid = false
116
- end
103
+ translations_params ||= []
104
+ translations_params.each do |values|
105
+ translation = load_translation(values["key"], values["localizations"])
117
106
 
118
- @collection << translation
107
+ if translation.valid?
108
+ @translations_to_save << translation
109
+ @translation_ids_to_destroy.delete(translation.id.to_s)
110
+ else
111
+ valid = false
119
112
  end
120
113
 
121
- valid
114
+ @collection << translation
122
115
  end
123
116
 
124
- def load_translation(key, localizations)
125
- translation = Releaf::I18nDatabase::Translation.where(key: key).first_or_initialize
126
- translation.key = key
117
+ valid
118
+ end
127
119
 
128
- localizations.each_pair do |locale, localization|
129
- load_translation_data(translation, locale, localization)
130
- end
120
+ def load_translation(key, localizations)
121
+ translation = Releaf::I18nDatabase::I18nEntry.where(key: key).first_or_initialize
122
+ translation.key = key
131
123
 
132
- translation
124
+ localizations.each_pair do |locale, localization|
125
+ load_translation_data(translation, locale, localization)
133
126
  end
134
127
 
135
- def load_translation_data(translation, locale, localization)
136
- translation_data = translation.translation_data.find{ |x| x.lang == locale }
137
- # replace existing locale value only if new one is not blank
138
- if translation_data
139
- translation_data.localization = localization
140
- # always assign value for new locale
141
- elsif translation_data.nil?
142
- translation_data = translation.translation_data.build(lang: locale, localization: localization)
143
- end
128
+ translation
129
+ end
144
130
 
145
- translation_data
146
- end
131
+ def load_translation_data(translation, locale, localization)
132
+ translation_data = translation.find_or_initialize_translation(locale)
147
133
 
148
- def update_response_success
149
- if @import
150
- msg = 'successfuly imported %{count} translations'
151
- flash["success"] = { "id" => "resource_status", "message" => I18n.t(msg, default: msg, count: @translations_to_save.size , scope: notice_scope_name) }
152
- redirect_to action: :index
153
- else
154
- render_notification true
155
- redirect_to({action: :edit}.merge(request.query_parameters))
156
- end
134
+ if localization.present?
135
+ translation_data.text = localization
136
+ else
137
+ translation_data.mark_for_destruction
157
138
  end
158
139
 
159
- def export_file_name
160
- "#{Rails.application.class.parent_name.underscore}_translations_#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}.xlsx"
161
- end
140
+ translation_data
141
+ end
162
142
 
163
- def import_file_path
164
- params[:import_file].try(:tempfile).try(:path).to_s
143
+ def update_response_success
144
+ if @import
145
+ msg = 'successfuly imported %{count} translations'
146
+ flash["success"] = { "id" => "resource_status", "message" => I18n.t(msg, default: msg, count: @translations_to_save.size , scope: notice_scope_name) }
147
+ redirect_to action: :index
148
+ else
149
+ render_notification true
150
+ redirect_to({action: :edit}.merge(request.query_parameters))
165
151
  end
152
+ end
166
153
 
167
- def import_file_extension
168
- File.extname(params[:import_file].original_filename).gsub(".", "")
169
- end
154
+ def export_file_name
155
+ "#{Rails.application.class.parent_name.underscore}_translations_#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}.xlsx"
156
+ end
157
+
158
+ def import_file_path
159
+ params[:import_file].try(:tempfile).try(:path).to_s
160
+ end
161
+
162
+ def import_file_extension
163
+ File.extname(params[:import_file].original_filename).tr(".", "")
170
164
  end
171
165
  end
@@ -0,0 +1,62 @@
1
+ module Releaf::I18nDatabase
2
+ class ParseSpreadsheetTranslations
3
+ class UnsupportedFileFormatError < StandardError; end
4
+ include Releaf::Service
5
+ attribute :file_path, String
6
+ attribute :extension, String
7
+
8
+ def call
9
+ translations
10
+ end
11
+
12
+ def rows
13
+ @rows ||= spreadsheet.to_a
14
+ end
15
+
16
+ def data_rows
17
+ rows[1..-1].reject{|row| row.first.blank? }
18
+ end
19
+
20
+ def locales
21
+ @locales ||= rows.first.reject(&:blank?)
22
+ end
23
+
24
+ def spreadsheet
25
+ begin
26
+ Roo::Spreadsheet.open(file_path, extension: extension, file_warning: :ignore)
27
+ rescue StandardError => e
28
+ if file_format_error?(e.class.name, e.message)
29
+ raise UnsupportedFileFormatError
30
+ else
31
+ raise
32
+ end
33
+ end
34
+ end
35
+
36
+ def file_format_error?(error_class_name, error_message)
37
+ return true if ['Zip::ZipError','Ole::Storage::FormatError' ].include?(error_class_name)
38
+ error_class_name == 'ArgumentError' && error_message.match("Don't know how to open file").present?
39
+ end
40
+
41
+ def translations
42
+ data_rows.map do |row|
43
+ translation_instance(row.first, row[1..-1].map(&:to_s))
44
+ end
45
+ end
46
+
47
+ def translation_instance(key, localizations)
48
+ translation = Releaf::I18nDatabase::I18nEntry.where(key: key).first_or_initialize
49
+ maintain_translation_locales(translation, localizations)
50
+
51
+ translation
52
+ end
53
+
54
+ def maintain_translation_locales(translation, localizations)
55
+ locales.each_with_index do|locale, i|
56
+ locale_translation = translation.i18n_entry_translation.find{|item| item.locale == locale }
57
+ locale_translation ||= translation.i18n_entry_translation.build(locale: locale, text: "")
58
+ locale_translation.text = localizations[i] if localizations[i].present?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,149 @@
1
+ class Releaf::I18nDatabase::TranslationsStore
2
+ include Releaf::InstanceCache
3
+ attr_accessor :updated_at, :missing_keys
4
+
5
+ def initialize
6
+ self.updated_at = Releaf::I18nDatabase::Backend.translations_updated_at
7
+ self.missing_keys = {}
8
+ end
9
+
10
+ def expired?
11
+ updated_at != Releaf::I18nDatabase::Backend.translations_updated_at
12
+ end
13
+
14
+ def exist?(key)
15
+ stored_keys.key? key
16
+ end
17
+
18
+ def lookup(locale, key, options)
19
+ translation_keys = key.split('.')
20
+
21
+ (1..translation_keys.length).each do|i|
22
+ result = dig_valid_translation(locale, translation_keys, i == 1, options)
23
+ return result if returnable_result?(result, options)
24
+ # remove second last value (going up to scope chain)
25
+ translation_keys.delete_at(translation_keys.length - 2)
26
+ end
27
+
28
+ nil
29
+ end
30
+
31
+ def dig_translation(locale, translation_keys)
32
+ translation_keys.inject(stored_translations[locale.to_sym]) do |h, key|
33
+ h[key.to_sym] if h.is_a?(Hash)
34
+ end
35
+ end
36
+
37
+ def dig_valid_translation(locale, translation_keys, first_lookup, options)
38
+ result = dig_translation(locale, translation_keys)
39
+ if invalid_result?(locale, result, first_lookup, options)
40
+ nil
41
+ else
42
+ result
43
+ end
44
+ end
45
+
46
+ def invalid_result?(locale, result, first_lookup, options)
47
+ invalid_nonpluralized_result?(result, first_lookup, options) || invalid_pluralized_result?(locale, result, options)
48
+ end
49
+
50
+ def invalid_nonpluralized_result?(result, first_lookup, options)
51
+ result.is_a?(Hash) && !first_lookup && !options.has_key?(:count)
52
+ end
53
+
54
+ def invalid_pluralized_result?(locale, result, options)
55
+ result.is_a?(Hash) && options.has_key?(:count) && !valid_pluralized_result?(locale, options[:count], result)
56
+ end
57
+
58
+ def valid_pluralized_result?(locale, count, result)
59
+ return false unless TwitterCldr.supported_locale?(locale)
60
+ result.key?(TwitterCldr::Formatters::Plurals::Rules.rule_for(count, locale))
61
+ end
62
+
63
+ def returnable_result?(result, options)
64
+ result.present? || options.fetch(:inherit_scopes, true) == false
65
+ end
66
+
67
+ def localization_data
68
+ Releaf::I18nDatabase::I18nEntryTranslation
69
+ .joins(:i18n_entry)
70
+ .where.not(text: '')
71
+ .pluck("CONCAT(locale, '.', releaf_i18n_entries.key) AS translation_key", "text")
72
+ .to_h
73
+ end
74
+
75
+ def stored_keys
76
+ Releaf::I18nDatabase::I18nEntry.pluck(:key).inject({}) do|h, key|
77
+ h.update(key => true)
78
+ end
79
+ end
80
+
81
+ def stored_translations
82
+ stored_keys.map do |key, _|
83
+ key_hash(key)
84
+ end.inject(&:deep_merge) || {}
85
+ end
86
+
87
+ def key_hash(key)
88
+ config.all_locales.inject({}) do |h, locale|
89
+ localized_key = "#{locale}.#{key}"
90
+ locale_hash = key_locale_hash(localized_key, localization_data[localized_key])
91
+ h.merge(locale_hash)
92
+ end
93
+ end
94
+
95
+ def config
96
+ Releaf.application.config
97
+ end
98
+
99
+ def missing?(locale, key)
100
+ missing_keys.key? "#{locale}.#{key}"
101
+ end
102
+
103
+ def missing(locale, key, options)
104
+ # mark translation as missing
105
+ missing_keys["#{locale}.#{key}"] = true
106
+ auto_create(key, options) if auto_create?(key, options)
107
+ end
108
+
109
+ def auto_create?(key, options)
110
+ return false unless config.i18n_database.translation_auto_creation
111
+ return false if options[:auto_create] == false
112
+ return false unless auto_creation_inclusion?(key)
113
+ return false if auto_creation_exception?(key)
114
+ return false if stored_keys.key?(key)
115
+ true
116
+ end
117
+
118
+ def auto_creation_inclusion?(key)
119
+ config.i18n_database.translation_auto_creation_patterns.find{|pattern| key.match(pattern) }.present?
120
+ end
121
+
122
+ def auto_creation_exception?(key)
123
+ config.i18n_database.translation_auto_creation_exclusion_patterns.find{|pattern| key.match(pattern) }.present?
124
+ end
125
+
126
+ def auto_create(key, options)
127
+ if pluralizable_translation?(options)
128
+ Releaf::I18nDatabase::Backend.locales_pluralizations.each do|pluralization|
129
+ Releaf::I18nDatabase::I18nEntry.create(key: "#{key}.#{pluralization}")
130
+ end
131
+ else
132
+ Releaf::I18nDatabase::I18nEntry.create(key: key)
133
+ end
134
+ end
135
+
136
+ def pluralizable_translation?(options)
137
+ options.has_key?(:count) && options[:create_plurals] == true
138
+ end
139
+
140
+ cache_instance_methods :stored_translations, :stored_keys, :localization_data
141
+
142
+ private
143
+
144
+ def key_locale_hash(localized_key, localization)
145
+ localized_key.split(".").reverse.inject(localization) do |value, key|
146
+ {key.to_sym => value}
147
+ end
148
+ end
149
+ end