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
@@ -0,0 +1,13 @@
1
+ require "rails_helper"
2
+
3
+ describe Releaf::I18nDatabase::Configuration do
4
+ subject{ described_class.new(translation_auto_creation: true,
5
+ translation_auto_creation_patterns: [1, 2],
6
+ translation_auto_creation_exclusion_patterns: [3, 4]) }
7
+
8
+ it do
9
+ is_expected.to have_attributes(translation_auto_creation: true)
10
+ is_expected.to have_attributes(translation_auto_creation_patterns: [1, 2])
11
+ is_expected.to have_attributes(translation_auto_creation_exclusion_patterns: [3, 4])
12
+ end
13
+ end
@@ -2,12 +2,18 @@ require "rails_helper"
2
2
 
3
3
  describe Releaf::I18nDatabase::HumanizeMissingTranslations do
4
4
  describe ".call" do
5
- context "when exception is I18n::MissingTranslation" do
5
+ context "when key is present and exception is I18n::MissingTranslation" do
6
6
  it "humanizes missing translations" do
7
7
  expect(I18n.t("some.missing translation")).to eq("Missing translation")
8
8
  end
9
9
  end
10
10
 
11
+ context "when key is not present and exception is I18n::MissingTranslation" do
12
+ it "does not intercept it" do
13
+ expect(I18n.t(nil)).to eq("translation missing: en.no key")
14
+ end
15
+ end
16
+
11
17
  context "when exception is not I18n::MissingTranslation" do
12
18
  it "does not intercept it" do
13
19
  allow(I18n::Backend::Transliterator).to receive(:get).and_raise(I18n::ArgumentError)
@@ -0,0 +1,151 @@
1
+ require "rails_helper"
2
+
3
+ describe Releaf::I18nDatabase::ParseSpreadsheetTranslations do
4
+ let(:translation){ Releaf::I18nDatabase::I18nEntry.new }
5
+ let(:fixture_path){ File.expand_path('../../../fixtures/translations_import.xlsx', __dir__) }
6
+ let(:error_message){ "Don't know how to open file #{fixture_path}" }
7
+ subject{ described_class.new(file_path: fixture_path, extension: "xlsx") }
8
+
9
+ describe "#call" do
10
+ it "returns translations" do
11
+ allow(subject).to receive(:translations).and_return("x")
12
+ expect(subject.call).to eq("x")
13
+ end
14
+ end
15
+
16
+ describe "#rows" do
17
+ let(:spreadsheet){ Roo::Spreadsheet.open(fixture_path) }
18
+
19
+ before do
20
+ allow(subject).to receive(:spreadsheet).and_return(spreadsheet)
21
+ end
22
+
23
+ it "returns speadsheet casted to array" do
24
+ allow(spreadsheet).to receive(:to_a).and_return([1, 2])
25
+ expect(subject.rows).to eq([1, 2])
26
+ end
27
+
28
+ it "caches casted array" do
29
+ expect(spreadsheet).to receive(:to_a).and_return([1, 2]).once
30
+ subject.rows
31
+ subject.rows
32
+ end
33
+ end
34
+
35
+ describe "#data_rows" do
36
+ it "returns all rows except first and one with empty first value (key)" do
37
+ allow(subject).to receive(:rows).and_return([[1, 2, 3], [:a, 2, 3], [nil, 2, 4], ["", 4, 1], [6, 2, ""]])
38
+ expect(subject.data_rows).to eq([[:a, 2, 3], [6, 2, ""]])
39
+ end
40
+ end
41
+
42
+ describe "#locales" do
43
+ it "returns all non blank values from first row" do
44
+ allow(subject).to receive(:rows).and_return([["lv", "", "de", nil, "en"], [:a, 2, 3], [6, 2, ""]])
45
+ expect(subject.locales).to eq(["lv", "de", "en"])
46
+ end
47
+
48
+ it "caches resolved values" do
49
+ expect(subject).to receive(:rows).and_return([["lv", "", "de", nil, "en"], [:a, 2, 3], [6, 2, ""]]).once
50
+ subject.locales
51
+ subject.locales
52
+ end
53
+ end
54
+
55
+ describe "#spreadsheet" do
56
+ it "returns Roo spreadsheet instance" do
57
+ allow(Roo::Spreadsheet).to receive(:open).with(fixture_path, extension: "xlsx", file_warning: :ignore).and_return("instance")
58
+ expect(subject.spreadsheet).to eq("instance")
59
+ end
60
+
61
+
62
+ context "when opening the file raises an unsupported content error" do
63
+ it "raises Releaf::TranslationsImporter::UnsupportedFileFormatError" do
64
+ error = ArgumentError.new("error message")
65
+ allow(Roo::Spreadsheet).to receive(:open).and_raise( error )
66
+ expect(subject).to receive(:file_format_error?).with( "ArgumentError", "error message").and_return true
67
+ expect{ subject.spreadsheet }.to raise_error(described_class::UnsupportedFileFormatError)
68
+ end
69
+ end
70
+
71
+ context "when opening the file raises any other error" do
72
+ it "raises Releaf::TranslationsImporter::UnsupportedFileFormatError" do
73
+ error = ArgumentError.new("error message")
74
+ allow(Roo::Spreadsheet).to receive(:open).and_raise( error )
75
+ expect(subject).to receive(:file_format_error?).with( "ArgumentError", "error message").and_return false
76
+ expect{ subject.spreadsheet }.to raise_error( error )
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ describe "#file_format_error?" do
83
+
84
+ context "when given error is an ArgumentError" do
85
+ context "when the message contains 'Don't know how to open file'" do
86
+ it "returns true" do
87
+ expect(subject.file_format_error?("ArgumentError", error_message)).to be true
88
+ end
89
+ end
90
+ context "when the message is different" do
91
+ it "returns false" do
92
+ expect(subject.file_format_error?("ArgumentError", "error message")).to be false
93
+ end
94
+ end
95
+ end
96
+
97
+ context "when the error is a Zip::ZipError" do
98
+ it "returns true" do
99
+ expect(subject.file_format_error?("Zip::ZipError", "error message")).to be true
100
+ end
101
+ end
102
+
103
+ context "when the error is a Ole::Storage::FormatError" do
104
+ it "returns true" do
105
+ expect(subject.file_format_error?("Ole::Storage::FormatError", "error message")).to be true
106
+ end
107
+ end
108
+
109
+
110
+ end
111
+
112
+
113
+ describe "#translations" do
114
+ it "returns array of processed `Releaf::I18nDatabase::Translation` instances" do
115
+ allow(subject).to receive(:data_rows).and_return([[1, 2, 3], [:a, nil, 3], [6, 2, ""]])
116
+ allow(subject).to receive(:translation_instance).with(1, ["2", "3"]).and_return("t1")
117
+ allow(subject).to receive(:translation_instance).with(:a, ["", "3"]).and_return("t2")
118
+ allow(subject).to receive(:translation_instance).with(6, ["2", ""]).and_return("t3")
119
+ expect(subject.translations).to eq(["t1", "t2", "t3"])
120
+ end
121
+ end
122
+
123
+ describe "#translation_instance" do
124
+ it "returns first or initialized translation for given key and given localizations built" do
125
+ where_scope = Releaf::I18nDatabase::I18nEntry.where(key: ["sommmmquery"])
126
+ allow(Releaf::I18nDatabase::I18nEntry).to receive(:where).with(key: "as.ld").and_return(where_scope)
127
+ allow(where_scope).to receive(:first_or_initialize).and_return(translation)
128
+
129
+ expect(subject).to receive(:maintain_translation_locales).with(translation, ["x", "y"])
130
+ expect(subject.translation_instance("as.ld", ["x", "y"])).to eq(translation)
131
+ end
132
+ end
133
+
134
+ describe "#maintain_translation_locales" do
135
+ before do
136
+ allow(subject).to receive(:locales).and_return(["en", "de", "ge", "lv"])
137
+ translation.i18n_entry_translation.build(locale: "de", text: "po")
138
+ translation.i18n_entry_translation.build(locale: "ge", text: "x")
139
+ end
140
+
141
+ it "builds translation data for all missing locales" do
142
+ expect{ subject.maintain_translation_locales(translation, ["ent", "det", "get", "lvt"]) }
143
+ .to change{ translation.i18n_entry_translation.map{|td| td.locale } }.from(["de", "ge"]).to(["de", "ge", "en", "lv"])
144
+ end
145
+
146
+ it "overwrites existing translation data localizations only if new localizations is not empty" do
147
+ expect{ subject.maintain_translation_locales(translation, ["ent", "", "get", "lvt"]) }
148
+ .to change{ translation.i18n_entry_translation.map{|td| [td.locale, td.text] } }.to([["de", "po"], ["ge", "get"], ["en", "ent"], ["lv", "lvt"]])
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,548 @@
1
+ require "rails_helper"
2
+
3
+ describe Releaf::I18nDatabase::TranslationsStore do
4
+ describe "#initialize" do
5
+ it "assigns updated at from `Releaf::I18nDatabase::Backend` last updated at timestamp" do
6
+ allow(Releaf::I18nDatabase::Backend).to receive(:translations_updated_at).and_return("x")
7
+ expect(subject.updated_at).to eq("x")
8
+ end
9
+
10
+ it "assigns empty hash to missing keys" do
11
+ expect(subject.missing_keys).to eq({})
12
+ end
13
+ end
14
+
15
+ describe "#config" do
16
+ it "returns Releaf.application.config" do
17
+ allow(Releaf.application).to receive(:config).and_return(:x)
18
+ expect(subject.config).to eq(:x)
19
+ end
20
+ end
21
+
22
+ describe "#expired?" do
23
+ context "when last translation update differs from last cache load" do
24
+ it "returns true" do
25
+ allow(Releaf::I18nDatabase::Backend).to receive(:translations_updated_at).and_return(1)
26
+ subject.updated_at = 2
27
+ expect(subject.expired?).to be true
28
+ end
29
+ end
30
+
31
+ context "when last translation update does not differ from last cache load" do
32
+ it "returns false" do
33
+ allow(Releaf::I18nDatabase::Backend).to receive(:translations_updated_at).and_return(1)
34
+ subject.updated_at = 1
35
+ expect(subject.expired?).to be false
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "#exist?" do
41
+ context "when given key exists within stored keys hash" do
42
+ it "returns true" do
43
+ allow(subject).to receive(:stored_keys).and_return("asd.xxx" => true)
44
+ expect(subject.exist?("asd.xxx")).to be true
45
+ end
46
+ end
47
+
48
+ context "when given key does not exist within keys hash" do
49
+ it "returns false" do
50
+ allow(subject).to receive(:stored_keys).and_return("asd.xxyy" => true)
51
+ expect(subject.exist?("asd.xxx")).to be false
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "#lookup" do
57
+ it "returns first valid and returnable result" do
58
+ allow(subject).to receive(:dig_valid_translation)
59
+ .with(:ru, ["admin", "menu", "translations"], true, count: 23).and_return("x1")
60
+ allow(subject).to receive(:returnable_result?)
61
+ .with("x1", count: 23).and_return(false)
62
+
63
+ allow(subject).to receive(:dig_valid_translation)
64
+ .with(:ru, ["admin", "translations"], false, count: 23).and_return("x2")
65
+ allow(subject).to receive(:returnable_result?)
66
+ .with("x2", count: 23).and_return(true)
67
+
68
+ expect(subject.lookup(:ru, "admin.menu.translations", count: 23)).to eq("x2")
69
+ end
70
+
71
+ it "iterates throught all translation scopes until last key" do
72
+ expect(subject).to receive(:dig_valid_translation)
73
+ .with(:ru, ["admin", "menu", "another", "translations"], true, count: 23).and_return(nil)
74
+ expect(subject).to receive(:dig_valid_translation)
75
+ .with(:ru, ["admin", "menu", "translations"], false, count: 23).and_return(nil)
76
+ expect(subject).to receive(:dig_valid_translation)
77
+ .with(:ru, ["admin", "translations"], false, count: 23).and_return(nil)
78
+ expect(subject).to receive(:dig_valid_translation)
79
+ .with(:ru, ["translations"], false, count: 23).and_return("x1")
80
+
81
+ subject.lookup(:ru, "admin.menu.another.translations", count: 23)
82
+ end
83
+
84
+ context "when no matches found" do
85
+ it "returns nil" do
86
+ allow(subject).to receive(:dig_valid_translation).and_return(nil)
87
+ expect(subject.lookup(:ru, "admin.menu.translations", count: 23)).to be nil
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#dig_translation" do
93
+ before do
94
+ allow(subject).to receive(:stored_translations).and_return(
95
+ lv: {
96
+ admin: {
97
+ some_scope: {
98
+ save: "Saglabāt"
99
+ }
100
+ }
101
+ },
102
+ lt: {
103
+ public: {
104
+ another_scope: {
105
+ email: "E-pastas"
106
+ }
107
+ }
108
+ }
109
+ )
110
+ end
111
+
112
+ it "dig given locale prefixed keys hash against stored translations and returns matched value (String or Hash)" do
113
+ expect(subject.dig_translation(:lv, [:admin, :some_scope, :save])).to eq("Saglabāt")
114
+ expect(subject.dig_translation(:lt, [:public, :another_scope, :email])).to eq("E-pastas")
115
+ expect(subject.dig_translation(:lt, [:public, :another_scope])).to eq(email: "E-pastas")
116
+ end
117
+
118
+ context "when no match found" do
119
+ it "returns nil" do
120
+ expect(subject.dig_translation(:lt, [:admin, :some_scope, :save])).to be nil
121
+ expect(subject.dig_translation(:lt, [:public, :email])).to be nil
122
+ expect(subject.dig_translation(:ge, [:public, :email])).to be nil
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "#dig_valid_translation" do
128
+ before do
129
+ allow(subject).to receive(:dig_translation).with(:de, [:admin, :save]).and_return("translated_value")
130
+ end
131
+
132
+ context "when digged translation has valid result" do
133
+ it "returns digged translation result" do
134
+ allow(subject).to receive(:invalid_result?).with(:de, "translated_value", true, a: :b).and_return(false)
135
+ expect(subject.dig_valid_translation(:de, [:admin, :save], true, a: :b)).to eq("translated_value")
136
+ end
137
+ end
138
+
139
+ context "when digged translation has invalid result" do
140
+ it "returns nil" do
141
+ allow(subject).to receive(:invalid_result?).with(:de, "translated_value", true, a: :b).and_return(true)
142
+ expect(subject.dig_valid_translation(:de, [:admin, :save], true, a: :b)).to be nil
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "#invalid_result?" do
148
+ before do
149
+ allow(subject).to receive(:invalid_nonpluralized_result?).with("xx", true, a: :b).and_return(false)
150
+ allow(subject).to receive(:invalid_pluralized_result?).with(:ge, "xx", a: :b).and_return(false)
151
+ end
152
+
153
+ context "when invalid nonplurarized result" do
154
+ it "returns true" do
155
+ allow(subject).to receive(:invalid_nonpluralized_result?).with("xx", true, a: :b).and_return(true)
156
+ expect(subject.invalid_result?(:ge, "xx", true, a: :b)).to be true
157
+ end
158
+ end
159
+
160
+ context "when invalid plurarized result" do
161
+ it "returns true" do
162
+ allow(subject).to receive(:invalid_pluralized_result?).with(:ge, "xx", a: :b).and_return(true)
163
+ expect(subject.invalid_result?(:ge, "xx", true, a: :b)).to be true
164
+ end
165
+ end
166
+
167
+ context "when no invalid nonplurarized or pluralized result" do
168
+ it "returns false" do
169
+ expect(subject.invalid_result?(:ge, "xx", true, a: :b)).to be false
170
+ end
171
+ end
172
+ end
173
+
174
+ describe "#invalid_nonpluralized_result?" do
175
+ context "when hash result, not first lookup and options without count" do
176
+ it "returns true" do
177
+ expect(subject.invalid_nonpluralized_result?({a: :b}, false, scope: "xxx")).to be true
178
+ end
179
+ end
180
+
181
+ context "when hash result, first lookup and options without count" do
182
+ it "returns false" do
183
+ expect(subject.invalid_nonpluralized_result?({a: :b}, true, scope: "xxx")).to be false
184
+ end
185
+ end
186
+
187
+ context "when hash result, not first lookup and options with count" do
188
+ it "returns false" do
189
+ expect(subject.invalid_nonpluralized_result?({a: :b}, false, count: 43, scope: "xxx")).to be false
190
+ end
191
+ end
192
+
193
+ context "when non hash result, not first lookup and options without count" do
194
+ it "returns false" do
195
+ expect(subject.invalid_nonpluralized_result?("x", false, scope: "xxx")).to be false
196
+ end
197
+ end
198
+ end
199
+
200
+ describe "#invalid_pluralized_result?" do
201
+ before do
202
+ allow(subject).to receive(:valid_pluralized_result?).with(:ge, 12, a: :b).and_return(false)
203
+ end
204
+
205
+ context "when hash result, options has count and invalid pluralized result" do
206
+ it "returns true" do
207
+ expect(subject.invalid_pluralized_result?(:ge, {a: :b}, {count: 12})).to be true
208
+ end
209
+ end
210
+
211
+ context "when non hash result, options has count and invalid pluralized result" do
212
+ it "returns false" do
213
+ expect(subject.invalid_pluralized_result?(:ge, "X", {count: 12})).to be false
214
+ end
215
+ end
216
+
217
+ context "when hash result, options has no count value and invalid pluralized result" do
218
+ it "returns false" do
219
+ expect(subject.invalid_pluralized_result?(:ge, {a: :b}, {scope: "xx.xx"})).to be false
220
+ end
221
+ end
222
+
223
+ context "when hash result, options has count and valid pluralized result" do
224
+ it "returns false" do
225
+ allow(subject).to receive(:valid_pluralized_result?).with(:ge, 12, a: :b).and_return(true)
226
+ expect(subject.invalid_pluralized_result?(:ge, {a: :b}, {count: 12})).to be false
227
+ end
228
+ end
229
+ end
230
+
231
+ describe "#valid_pluralized_result?" do
232
+ before do
233
+ allow(TwitterCldr::Formatters::Plurals::Rules).to receive(:rule_for)
234
+ .with(2, :lv).and_return(:few)
235
+ end
236
+
237
+ context "when unsupported locale given" do
238
+ it "return false" do
239
+ allow(TwitterCldr).to receive(:supported_locale?).with(:lv).and_return(false)
240
+ expect(subject.valid_pluralized_result?(:lv, 2, few: "x", many: "y")).to be false
241
+ end
242
+ end
243
+
244
+ context "when given hash contains valid result for given locale and count" do
245
+ before do
246
+ allow(TwitterCldr).to receive(:supported_locale?).with(:lv).and_return(true)
247
+ end
248
+
249
+ context "when given hash contains valid result for given locale and count" do
250
+ it "return true" do
251
+ expect(subject.valid_pluralized_result?(:lv, 2, few: "x", many: "y")).to be true
252
+ end
253
+ end
254
+
255
+ context "when given hash contains valid result for given locale and count" do
256
+ it "return true" do
257
+ expect(subject.valid_pluralized_result?(:lv, 2, one: "x", many: "y")).to be false
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ describe "#returnable_result?" do
264
+ context "when given result is not blank" do
265
+ it "returns true" do
266
+ expect(subject.returnable_result?("x", a: "b")).to be true
267
+ end
268
+ end
269
+
270
+ context "when `inherit_scopes` option value is boolean `false`" do
271
+ it "returns true" do
272
+ expect(subject.returnable_result?(nil, inherit_scopes: false)).to be true
273
+ end
274
+ end
275
+
276
+ context "when given result is blank and `inherit_scopes` option value is not boolean `false`" do
277
+ it "returns false" do
278
+ expect(subject.returnable_result?(nil, inherit_scopes: true)).to be false
279
+ expect(subject.returnable_result?(nil, a: "b")).to be false
280
+ end
281
+ end
282
+ end
283
+
284
+ describe "#localization_data" do
285
+ it "returns hash with all non empty translations with locale prefixed key and localization as value" do
286
+ i18n_entry_1 = Releaf::I18nDatabase::I18nEntry.create(key: "some.food")
287
+ i18n_entry_1.i18n_entry_translation.create(locale: "lv", text: "suņi")
288
+ i18n_entry_1.i18n_entry_translation.create(locale: "en", text: "dogs")
289
+
290
+ i18n_entry_2 = Releaf::I18nDatabase::I18nEntry.create(key: "some.good")
291
+ i18n_entry_2.i18n_entry_translation.create(locale: "lv", text: "xx")
292
+ i18n_entry_2.i18n_entry_translation.create(locale: "en", text: "")
293
+
294
+ expect(subject.localization_data).to eq("lv.some.food" => "suņi", "en.some.food" => "dogs",
295
+ "lv.some.good" => "xx")
296
+ end
297
+
298
+ it "caches built hash" do
299
+ expect(described_class.cached_instance_methods).to include(:localization_data)
300
+ end
301
+ end
302
+
303
+ describe "#stored_keys" do
304
+ it "returns hash with existing translation keys" do
305
+ Releaf::I18nDatabase::I18nEntry.create(key: "some.food")
306
+ Releaf::I18nDatabase::I18nEntry.create(key: "some.good")
307
+ expect(subject.stored_keys).to eq("some.food" => true, "some.good" => true)
308
+ end
309
+
310
+ it "caches built hash" do
311
+ expect(described_class.cached_instance_methods).to include(:stored_keys)
312
+ end
313
+ end
314
+
315
+ describe "#stored_translations" do
316
+ it "returns deep merged hash from all key translation hashes" do
317
+ allow(subject).to receive(:stored_keys).and_return("some.food" => true, "some.good" => true)
318
+ allow(subject).to receive(:key_hash).with("some.food").and_return(lv: {a: "a1"}, en: {d: {e: "ee"}})
319
+ allow(subject).to receive(:key_hash).with("some.good").and_return(lv: {b: "b2"}, en: {d: {u: "ll"}})
320
+ expect(subject.stored_translations).to eq(lv: {a: "a1", b: "b2"}, en: {d: {e: "ee", u: "ll"}})
321
+ end
322
+
323
+ context "when no keys exists" do
324
+ it "returns empty hash" do
325
+ allow(subject).to receive(:stored_keys).and_return({})
326
+ expect(subject.stored_translations).to eq({})
327
+ end
328
+ end
329
+
330
+ it "caches built hash" do
331
+ expect(described_class.cached_instance_methods).to include(:stored_translations)
332
+ end
333
+ end
334
+
335
+ describe "#key_hash" do
336
+ it "build stored translation hash with keys and translated values for given key" do
337
+ allow(subject.config).to receive(:all_locales).and_return([:ge, :de])
338
+ allow(subject).to receive(:localization_data).and_return(
339
+ "lv.admin.releaf_i18n_database_translations.Save" => "zc",
340
+ "ge.admin.Save" => "asdasd",
341
+ "de.admin.releaf_i18n_database_translations.Save" => "Seiv",
342
+ "de.admin.releaf_i18n_database_translations.Cancel" => "dCancel",
343
+ "ge.admin.releaf_i18n_database_translations.Cancel" => "gCancel",
344
+ )
345
+
346
+ expect(subject.key_hash("admin.releaf_i18n_database_translations.Save")).to eq(
347
+ ge: {
348
+ admin: {
349
+ releaf_i18n_database_translations: {
350
+ Save: nil
351
+ }
352
+ }
353
+ },
354
+ de: {
355
+ admin: {
356
+ releaf_i18n_database_translations: {
357
+ Save: "Seiv"
358
+ }
359
+ }
360
+ }
361
+ )
362
+
363
+ expect(subject.key_hash("admin.releaf_i18n_database_translations.Cancel")).to eq(
364
+ ge: {
365
+ admin: {
366
+ releaf_i18n_database_translations: {
367
+ Cancel: "gCancel"
368
+ }
369
+ }
370
+ },
371
+ de: {
372
+ admin: {
373
+ releaf_i18n_database_translations: {
374
+ Cancel: "dCancel"
375
+ }
376
+ }
377
+ }
378
+ )
379
+ end
380
+ end
381
+
382
+ describe "#missing?" do
383
+ context "when given locale and key combination exists within missing keys hash" do
384
+ it "returns true" do
385
+ allow(subject).to receive(:missing_keys).and_return("lv.asd.xxx" => true)
386
+ expect(subject.missing?(:lv, "asd.xxx")).to be true
387
+ end
388
+ end
389
+
390
+ context "when given locale and key combination does not exist missing keys hash" do
391
+ it "returns false" do
392
+ allow(subject).to receive(:missing_keys).and_return("en.asd.xxx" => true)
393
+ expect(subject.missing?(:lv, "asd.xxx")).to be false
394
+ end
395
+ end
396
+ end
397
+
398
+ describe "#missing" do
399
+ it "adds given locale and key combination as missing" do
400
+ expect{ subject.missing(:de, "as.pasd", a: "x") }.to change { subject.missing_keys["de.as.pasd"] }
401
+ .from(nil).to(true)
402
+ end
403
+
404
+ context "when missing translation creation is available for given key and options" do
405
+ it "creates missing translation" do
406
+ allow(subject).to receive(:auto_create?).with("ps.asda", a: "x").and_return(true)
407
+ expect(subject).to receive(:auto_create).with("ps.asda", a: "x")
408
+ subject.missing(:de, "ps.asda", a: "x")
409
+ end
410
+ end
411
+
412
+ context "when missing translation creation is not available for given key and options" do
413
+ it "does not create missing translation" do
414
+ allow(subject).to receive(:auto_create?).with("ps.asda", a: "x").and_return(false)
415
+ expect(subject).to_not receive(:auto_create)
416
+ subject.missing(:de, "ps.asda", a: "x")
417
+ end
418
+ end
419
+ end
420
+
421
+ describe "#auto_create?" do
422
+ before do
423
+ allow(subject.config.i18n_database ).to receive(:translation_auto_creation).and_return(true)
424
+ allow(subject).to receive(:stored_keys).and_return("xxxome.save" => "xxxome.save")
425
+ allow(subject).to receive(:auto_creation_inclusion?).with("some.save").and_return(true)
426
+ allow(subject).to receive(:auto_creation_exception?).with("some.save").and_return(false)
427
+ end
428
+
429
+ context "when missing translation creation is enabled globally by i18n config and not disabled by `auto_create` option" do
430
+ it "returns true" do
431
+ expect(subject.auto_create?("some.save", {})).to be true
432
+ expect(subject.auto_create?("some.save", auto_create: true)).to be true
433
+ expect(subject.auto_create?("some.save", auto_create: nil)).to be true
434
+ end
435
+ end
436
+
437
+ context "when no auto creation inclusion" do
438
+ it "returns false" do
439
+ allow(subject).to receive(:auto_creation_inclusion?).with("some.save").and_return(false)
440
+ expect(subject.auto_create?("some.save", {})).to be false
441
+ end
442
+ end
443
+
444
+ context "when auto creation exception" do
445
+ it "returns false" do
446
+ allow(subject).to receive(:auto_creation_exception?).with("some.save").and_return(true)
447
+ expect(subject.auto_create?("some.save", {})).to be false
448
+ end
449
+ end
450
+
451
+ context "when missing translation creation is disabled globally by i18n config" do
452
+ it "returns false" do
453
+ allow(subject.config.i18n_database ).to receive(:translation_auto_creation).and_return(false)
454
+ expect(subject.auto_create?("some.save", {})).to be false
455
+ end
456
+ end
457
+
458
+ context "when missing translation creation is disabled by `auto_create` option" do
459
+ it "returns false" do
460
+ expect(subject.auto_create?("some.save", auto_create: false)).to be false
461
+ end
462
+ end
463
+
464
+ context "when key already exists within stored keys hash" do
465
+ it "returns false" do
466
+ allow(subject).to receive(:stored_keys).and_return("some.save" => "some.save")
467
+ expect(subject.auto_create?("some.save", {})).to be false
468
+ end
469
+ end
470
+ end
471
+
472
+ describe "#auto_creation_inclusion?" do
473
+ context "when given key matches any auto creation pattern" do
474
+ it "returns true" do
475
+ allow(subject.config.i18n_database ).to receive(:translation_auto_creation_patterns).and_return([/^another\./, /^some\./])
476
+ expect(subject.auto_creation_inclusion?("some.save")).to be true
477
+ end
478
+ end
479
+
480
+ context "when given key matches no auto creation pattern" do
481
+ it "returns false" do
482
+ allow(subject.config.i18n_database ).to receive(:translation_auto_creation_patterns).and_return([/^another\./, /^foo\./])
483
+ expect(subject.auto_creation_inclusion?("some.save")).to be false
484
+ end
485
+ end
486
+ end
487
+
488
+ describe "#auto_creation_exception?" do
489
+ context "when given key matches any auto creation exception pattern" do
490
+ it "returns true" do
491
+ allow(subject.config.i18n_database ).to receive(:translation_auto_creation_exclusion_patterns).and_return([/^another\./, /^some\./])
492
+ expect(subject.auto_creation_exception?("some.save")).to be true
493
+ end
494
+ end
495
+
496
+ context "when given key matches no auto creation exception pattern" do
497
+ it "returns false" do
498
+ allow(subject.config.i18n_database ).to receive(:translation_auto_creation_exclusion_patterns).and_return([/^another\./, /^foo\./])
499
+ expect(subject.auto_creation_exception?("some.save")).to be false
500
+ end
501
+ end
502
+ end
503
+
504
+ describe "#auto_create" do
505
+ before do
506
+ allow(Releaf::I18nDatabase::Backend).to receive(:locales_pluralizations).and_return([:one, :many, :other])
507
+ end
508
+
509
+ context "when pluralizable translation given" do
510
+ it "creates translation for each pluralization form" do
511
+ allow(subject).to receive(:pluralizable_translation?).with(a: "b").and_return(true)
512
+ expect(Releaf::I18nDatabase::I18nEntry).to receive(:create).with(key: "aasd.oihgja.sd.one")
513
+ expect(Releaf::I18nDatabase::I18nEntry).to receive(:create).with(key: "aasd.oihgja.sd.many")
514
+ expect(Releaf::I18nDatabase::I18nEntry).to receive(:create).with(key: "aasd.oihgja.sd.other")
515
+ subject.auto_create("aasd.oihgja.sd", a: "b")
516
+ end
517
+ end
518
+
519
+ context "when non pluralizable translation given" do
520
+ it "creates translation" do
521
+ allow(subject).to receive(:pluralizable_translation?).with(a: "b").and_return(false)
522
+ expect(Releaf::I18nDatabase::I18nEntry).to receive(:create).with(key: "aasd.oihgja.sd")
523
+ subject.auto_create("aasd.oihgja.sd", a: "b")
524
+ end
525
+ end
526
+ end
527
+
528
+ describe "#pluralizable_translation?" do
529
+ context "when given options has count key and `create_plurals` key is true" do
530
+ it "returns true" do
531
+ expect(subject.pluralizable_translation?(count: 3, create_plurals: true)).to be true
532
+ end
533
+ end
534
+
535
+ context "when given options has count key and `create_plurals` key is not true" do
536
+ it "returns false" do
537
+ expect(subject.pluralizable_translation?(count: 3, create_plurals: false)).to be false
538
+ expect(subject.pluralizable_translation?(count: 3)).to be false
539
+ end
540
+ end
541
+
542
+ context "when given options has no count key and `create_plurals` key is true" do
543
+ it "returns false" do
544
+ expect(subject.pluralizable_translation?(create_plurals: true)).to be false
545
+ end
546
+ end
547
+ end
548
+ end