releaf-i18n_database 0.2.1 → 1.0.3

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.
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