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
@@ -8,7 +8,7 @@ module Releaf::I18nDatabase
8
8
  end
9
9
 
10
10
  def self.filter_only_blank_translations(collection)
11
- blank_where_collection = Releaf::I18nDatabase::Translation
11
+ blank_where_collection = Releaf::I18nDatabase::I18nEntry
12
12
  search_columns.each do |column|
13
13
  blank_where_collection = blank_where_collection.where(column.eq('').or(column.eq(nil)))
14
14
  end
@@ -30,7 +30,7 @@ module Releaf::I18nDatabase
30
30
  end
31
31
 
32
32
  def self.search_columns
33
- [Releaf::I18nDatabase::Translation.arel_table[:key]] + locale_tables.map{|locale, table| table[:localization] }
33
+ [Releaf::I18nDatabase::I18nEntry.arel_table[:key]] + locale_tables.map{|_locale, table| table[:text] }
34
34
  end
35
35
 
36
36
  def self.escape_search_string(string)
@@ -39,7 +39,7 @@ module Releaf::I18nDatabase
39
39
 
40
40
  def self.locale_tables
41
41
  Releaf.application.config.all_locales.inject({}) do|h, locale|
42
- h.update(locale => Releaf::I18nDatabase::TranslationData.arel_table.alias("#{locale}_data"))
42
+ h.update(locale => Releaf::I18nDatabase::I18nEntryTranslation.arel_table.alias("#{locale}_data"))
43
43
  end
44
44
  end
45
45
 
@@ -49,17 +49,17 @@ module Releaf::I18nDatabase
49
49
 
50
50
  def self.localization_include_joins
51
51
  locale_tables.map do |locale, table|
52
- "LEFT JOIN #{table.relation.name} AS #{table.name} ON #{locale}_data.translation_id = releaf_translations.id AND #{locale}_data.lang = '#{locale}'"
52
+ "LEFT JOIN #{table.relation.name} AS #{table.name} ON #{locale}_data.i18n_entry_id = releaf_i18n_entries.id AND #{locale}_data.locale = '#{locale}'"
53
53
  end
54
54
  end
55
55
 
56
56
  def self.localization_include_selects
57
- (['releaf_translations.*'] + localization_include_locales_columns).join(', ')
57
+ (['releaf_i18n_entries.*'] + localization_include_locales_columns).join(', ')
58
58
  end
59
59
 
60
60
  def self.localization_include_locales_columns
61
61
  locale_tables.map do |locale, table|
62
- ["#{table.name}.localization AS #{locale}_localization", "#{table.name}.id AS #{locale}_localization_id"]
62
+ ["#{table.name}.text AS #{locale}_localization", "#{table.name}.id AS #{locale}_localization_id"]
63
63
  end.flatten
64
64
  end
65
65
  end
@@ -0,0 +1,21 @@
1
+ module Releaf::I18nDatabase
2
+ class I18nEntry < ActiveRecord::Base
3
+ self.table_name = "releaf_i18n_entries"
4
+
5
+ validates_presence_of :key
6
+ validates_uniqueness_of :key
7
+ validates_length_of :key, maximum: 255
8
+
9
+ has_many :i18n_entry_translation, dependent: :destroy,
10
+ class_name: 'Releaf::I18nDatabase::I18nEntryTranslation', inverse_of: :i18n_entry
11
+ accepts_nested_attributes_for :i18n_entry_translation, allow_destroy: true
12
+
13
+ def locale_value(locale)
14
+ find_or_initialize_translation(locale).text
15
+ end
16
+
17
+ def find_or_initialize_translation(locale)
18
+ i18n_entry_translation.find{ |translation| translation.locale == locale.to_s } || i18n_entry_translation.build(locale: locale)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Releaf::I18nDatabase
2
+ class I18nEntryTranslation < ActiveRecord::Base
3
+ self.table_name = "releaf_i18n_entry_translations"
4
+
5
+ validates_presence_of :i18n_entry, :locale
6
+ validates_uniqueness_of :i18n_entry_id, scope: :locale
7
+ validates_length_of :locale, maximum: 5
8
+
9
+ belongs_to :i18n_entry, inverse_of: :i18n_entry_translation
10
+ end
11
+ end
@@ -13,7 +13,7 @@
13
13
  %td.translationCell{data: {locale: locale}}
14
14
  %input{type: "text", class: "text", name: "translations[][localizations][#{locale}]"}
15
15
  %td.delete-column.only-icon
16
- = releaf_button(nil, "times", class: %w(danger remove-nested-item), title: t("Remove item", scope: controller_scope_name))
16
+ = releaf_button(nil, "times", class: %w(danger remove-nested-item), title: t("Remove", scope: controller_scope_name))
17
17
  %section.nested{data: {name: "translations", releaf: {template: html_escape(template_html)}}}
18
18
  %table.table
19
19
  %thead
@@ -34,7 +34,7 @@
34
34
  %td.translationCell{data: {locale: locale}}
35
35
  %input{type: "text", class: "text", name: "translations[][localizations][#{locale}]", value: translation.locale_value(locale) }
36
36
  %td.delete-column.only-icon
37
- = releaf_button(nil, "times", class: %w(danger remove-nested-item), title: t("Remove item", scope: controller_scope_name))
37
+ = releaf_button(nil, "times", class: %w(danger remove-nested-item), title: t("Remove", scope: controller_scope_name))
38
38
  %div.tools
39
39
  = releaf_button(t('Add item', :scope => 'admin.releaf_translations'), "plus", class: "add-nested-item")
40
40
  = hidden_field_tag :existing_translations, @translation_ids_to_destroy.join(",")
@@ -1,5 +1,16 @@
1
+ require 'axlsx_rails'
1
2
  require 'twitter_cldr'
2
3
  require 'i18n'
3
- require 'releaf/i18n_database/engine'
4
- require 'releaf/i18n_database/humanize_missing_translations'
5
- require 'releaf/i18n_database/backend'
4
+ require 'roo'
5
+
6
+ module Releaf::I18nDatabase
7
+ require 'releaf/i18n_database/engine'
8
+ require 'releaf/i18n_database/configuration'
9
+ require 'releaf/i18n_database/humanize_missing_translations'
10
+ require 'releaf/i18n_database/backend'
11
+
12
+ def self.components
13
+ [Releaf::I18nDatabase::Backend, Releaf::I18nDatabase::HumanizeMissingTranslations]
14
+ end
15
+ end
16
+
@@ -1,168 +1,93 @@
1
1
  require 'i18n/backend/base'
2
- # TODO convert to arel
2
+
3
3
  module Releaf
4
4
  module I18nDatabase
5
5
  class Backend
6
+
6
7
  include ::I18n::Backend::Base, ::I18n::Backend::Flatten
7
- CACHE = {updated_at: nil, translations: {}, missing: {}}
8
8
  UPDATED_AT_KEY = 'releaf.i18n_database.translations.updated_at'
9
+ DEFAULT_CONFIG = {
10
+ translation_auto_creation: true,
11
+ translation_auto_creation_patterns: [/.*/],
12
+ translation_auto_creation_exclusion_patterns: [/^attributes\./]
13
+ }
14
+ attr_accessor :translations_cache
9
15
 
10
- def reload_cache
11
- CACHE[:translations] = translations || {}
12
- CACHE[:missing] = {}
13
- CACHE[:updated_at] = self.class.translations_updated_at
14
- end
15
-
16
- def reload_cache?
17
- CACHE[:updated_at] != self.class.translations_updated_at
18
- end
19
-
20
- def self.translations_updated_at
21
- Releaf::Settings[UPDATED_AT_KEY]
22
- end
23
-
24
- def self.translations_updated_at= value
25
- Releaf::Settings[UPDATED_AT_KEY] = value
16
+ def self.initialize_component
17
+ I18n.backend = I18n::Backend::Chain.new(new, I18n.backend)
26
18
  end
27
19
 
28
- def store_translations locale, data, options = {}
29
- new_hash = {}
30
- new_hash[locale] = data
31
-
32
- CACHE[:translations].deep_merge!(new_hash)
33
- CACHE[:missing] = {}
20
+ def self.locales_pluralizations
21
+ Releaf.application.config.all_locales.map do|locale|
22
+ TwitterCldr::Formatters::Plurals::Rules.all_for(locale) if TwitterCldr.supported_locale?(locale)
23
+ end.flatten.uniq.compact
34
24
  end
35
25
 
36
- protected
37
-
38
- # Return all non-empty localizations
39
- def localization_data
40
- TranslationData.where("localization <> ''").
41
- joins("LEFT JOIN releaf_translations ON releaf_translations.id = translation_id").
42
- pluck("LOWER(CONCAT(lang, '.', releaf_translations.key)) AS translation_key", "localization").
43
- to_h
26
+ def self.configure_component
27
+ Releaf.application.config.add_configuration(
28
+ Releaf::I18nDatabase::Configuration.new(DEFAULT_CONFIG)
29
+ )
44
30
  end
45
31
 
46
- # Return translation hash for each releaf locales
47
- def translations
48
- localization_cache = localization_data
49
-
50
- Translation.order(:key).pluck("LOWER(releaf_translations.key)").map do |translation_key|
51
- key_hash(translation_key, localization_cache)
52
- end.inject(&:deep_merge)
32
+ def self.reset_cache
33
+ backend_instance.translations_cache = nil
53
34
  end
54
35
 
55
- def cache_lookup keys, locale, options, first_lookup
56
- result = keys.inject(CACHE[:translations]) { |h, key| h.is_a?(Hash) && h.try(:[], key.downcase.to_sym) }
57
-
58
- # when non-first match, non-pluralized and hash - return nil
59
- if !first_lookup && result.is_a?(Hash) && !options.has_key?(:count)
60
- result = nil
61
- # return nil as we don't have valid pluralized translation
62
- elsif result.is_a?(Hash) && options.has_key?(:count) && !valid_pluralized_result?(result, locale, options[:count])
63
- result = nil
36
+ def self.backend_instance
37
+ if I18n.backend.is_a? I18n::Backend::Chain
38
+ I18n.backend.backends.find{|b| b.is_a?(Releaf::I18nDatabase::Backend) }
39
+ elsif I18n.backend.is_a? Releaf::I18nDatabase::Backend
40
+ I18n.backend
64
41
  end
65
-
66
- result
67
42
  end
68
43
 
69
- def valid_pluralized_result? result, locale, count
70
- valid = false
71
-
72
- if TwitterCldr.supported_locale?(locale)
73
- rule = TwitterCldr::Formatters::Plurals::Rules.rule_for(count, locale)
74
- valid = result.has_key? rule
44
+ def self.draw_component_routes router
45
+ router.namespace :releaf, path: nil do
46
+ router.namespace :i18n_database, path: nil do
47
+ router.resources :translations, only: [:index] do
48
+ router.collection do
49
+ router.get :edit
50
+ router.post :update
51
+ router.get :export
52
+ router.post :import
53
+ end
54
+ end
55
+ end
75
56
  end
76
-
77
- valid
78
57
  end
79
58
 
80
- # Lookup translation from database
81
- def lookup(locale, key, scope = [], options = {})
82
- # reload cache if cache timestamp differs from last translations update
83
- reload_cache if reload_cache?
84
-
85
- key = normalize_flat_keys(locale, key, scope, options[:separator])
86
- locale_key = "#{locale}.#{key}"
87
-
88
- # do not process further if key already marked as missing
89
- return nil if CACHE[:missing].has_key? locale_key
90
-
91
- chain = locale_key.split('.')
92
- chain_initial_length = chain.length
93
- inherit_scopes = options.fetch(:inherit_scopes, true)
94
-
95
- while (chain.length > 1) do
96
- result = cache_lookup(chain, locale, options, chain_initial_length == chain.length)
97
- return result if result.present?
98
- break if inherit_scopes == false
99
-
100
- # remove second last value
101
- chain.delete_at(chain.length - 2)
59
+ def translations
60
+ if translations_cache && !translations_cache.expired?
61
+ translations_cache
62
+ else
63
+ self.translations_cache = Releaf::I18nDatabase::TranslationsStore.new
102
64
  end
103
-
104
- # mark translation as missing
105
- CACHE[:missing][locale_key] = true
106
- create_missing_translation(locale, key, options) if create_missing_translation?(options)
107
-
108
- return nil
109
65
  end
110
66
 
111
- def default(locale, object, subject, options = {})
112
- if options[:create_default] == false
113
- options = options.reject { |key, value| key == :create_default }
114
- options[:create_missing] = false
115
- end
116
- super
67
+ def self.translations_updated_at
68
+ Releaf::Settings[UPDATED_AT_KEY]
117
69
  end
118
70
 
119
- def get_all_pluralizations
120
- keys = []
121
-
122
- ::Releaf.application.config.all_locales.each do|locale|
123
- if TwitterCldr.supported_locale? locale
124
- keys += TwitterCldr::Formatters::Plurals::Rules.all_for(locale)
125
- end
126
- end
127
-
128
- keys.uniq
71
+ def self.translations_updated_at= value
72
+ Releaf::Settings[UPDATED_AT_KEY] = value
129
73
  end
130
74
 
131
- def create_missing_translation?(options)
132
- Releaf::I18nDatabase.create_missing_translations == true && options[:create_missing] != false
133
- end
75
+ def store_translations(locale, data, options = {})
76
+ # pass to simple backend
134
77
 
135
- def create_missing_translation(locale, key, options)
136
- begin
137
- if options.has_key?(:count) && options[:create_plurals] == true
138
- get_all_pluralizations.each do|pluralization|
139
- Translation.create(key: "#{key}.#{pluralization}")
140
- end
141
- else
142
- Translation.create(key: key)
143
- end
144
- rescue ActiveRecord::RecordNotUnique
145
- end
78
+ I18n.backend.backends.last.store_translations(locale, data, options)
146
79
  end
147
80
 
148
- private
149
-
150
- def key_hash key, localization_cache
151
- hash = {}
81
+ # Lookup translation from database
82
+ def lookup(locale, key, scope = [], options = {})
83
+ key = normalize_flat_keys(locale, key, scope, options[:separator])
152
84
 
153
- ::Releaf.application.config.all_locales.each do |locale|
154
- localized_key = "#{locale}.#{key}"
155
- locale_hash = locale_hash(localized_key, localization_cache[localized_key])
156
- hash.merge! locale_hash
157
- end
85
+ return if translations.missing?(locale, key)
158
86
 
159
- hash
160
- end
87
+ result = translations.lookup(locale, key, options)
88
+ translations.missing(locale, key, options) if result.nil?
161
89
 
162
- def locale_hash localized_key, localization
163
- localized_key.to_s.split(".").reverse.inject(localization) do |value, key|
164
- {key.to_sym => value}
165
- end
90
+ result
166
91
  end
167
92
  end
168
93
  end
@@ -0,0 +1,8 @@
1
+ module Releaf::I18nDatabase
2
+ class Configuration
3
+ include Virtus.model(strict: true)
4
+ attribute :translation_auto_creation, Boolean
5
+ attribute :translation_auto_creation_patterns, Array
6
+ attribute :translation_auto_creation_exclusion_patterns, Array
7
+ end
8
+ end
@@ -1,36 +1,7 @@
1
- require 'axlsx_rails'
2
-
3
1
  module Releaf::I18nDatabase
4
- require 'releaf/i18n_database/builders_autoload'
5
- mattr_accessor :create_missing_translations
6
- @@create_missing_translations = true
7
-
8
2
  class Engine < ::Rails::Engine
9
3
  initializer 'precompile', group: :all do |app|
10
- app.config.assets.precompile += %w(releaf/controllers/releaf/i18n_database/*)
11
- end
12
- end
13
-
14
- def self.components
15
- [Releaf::I18nDatabase::HumanizeMissingTranslations]
16
- end
17
-
18
- def self.initialize_component
19
- I18n.backend = Releaf::I18nDatabase::Backend.new
20
- end
21
-
22
- def self.draw_component_routes router
23
- router.namespace :releaf, path: nil do
24
- router.namespace :i18n_database, path: nil do
25
- router.resources :translations, only: [:index] do
26
- router.collection do
27
- router.get :edit
28
- router.post :update
29
- router.get :export
30
- router.post :import
31
- end
32
- end
33
- end
4
+ app.config.assets.precompile += %w(controllers/releaf/i18n_database/*)
34
5
  end
35
6
  end
36
7
  end
@@ -1,7 +1,7 @@
1
1
  module Releaf::I18nDatabase
2
2
  module HumanizeMissingTranslations
3
3
  def call(exception, locale, key, options)
4
- if exception.is_a?(I18n::MissingTranslation)
4
+ if key.present? && exception.is_a?(I18n::MissingTranslation)
5
5
  key.to_s.split('.').last.humanize
6
6
  else
7
7
  super
Binary file
@@ -30,7 +30,7 @@ describe Releaf::I18nDatabase::Translations::BuildersCommon, type: :class do
30
30
 
31
31
  describe "#export_button" do
32
32
  it "returns export button" do
33
- allow(subject).to receive(:t).with("export").and_return("exp")
33
+ allow(subject).to receive(:t).with("Export").and_return("exp")
34
34
  allow(subject).to receive(:action_url).with(:export, format: :xlsx).and_return("_exp_url_")
35
35
  allow(subject).to receive(:button).with("exp", "download", class: "secondary", href: "_exp_url_").and_return("btn")
36
36
  expect(subject.export_button).to eq('btn')
@@ -79,8 +79,8 @@ describe Releaf::I18nDatabase::Translations::EditBuilder, type: :class do
79
79
  describe "#back_to_index_button" do
80
80
  it "returns localized value for given resource and column(locale)" do
81
81
  allow(subject).to receive(:t).with("Back to list").and_return("back")
82
- allow(subject).to receive(:action_url).with(:index).and_return("index_url")
83
- allow(subject).to receive(:button).with("back", "caret-left", class: "secondary", href: "index_url").and_return("index_btn")
82
+ allow(subject).to receive(:action_url).with(:index).and_return("index_path")
83
+ allow(subject).to receive(:button).with("back", "caret-left", class: "secondary", href: "index_path").and_return("index_btn")
84
84
  expect(subject.back_to_index_button).to eq("index_btn")
85
85
  end
86
86
  end
@@ -3,7 +3,7 @@ require "rails_helper"
3
3
  describe Releaf::I18nDatabase::Translations::TableBuilder, type: :class do
4
4
  class TableBuilderTestHelper < ActionView::Base; end
5
5
  let(:template){ TableBuilderTestHelper.new }
6
- let(:resource_class){ Releaf::I18nDatabase::Translation }
6
+ let(:resource_class){ Releaf::I18nDatabase::I18nEntry }
7
7
  let(:subject){ described_class.new([], resource_class, template, {}) }
8
8
 
9
9
  before do
@@ -14,20 +14,16 @@ describe Releaf::I18nDatabase::TranslationsController do
14
14
  end
15
15
 
16
16
  before build_translations: true do
17
- @t1 = FactoryGirl.create(:translation, key: 'test.key1')
18
- @t2 = FactoryGirl.create(:translation, key: 'great.stuff')
19
- @t3 = FactoryGirl.create(:translation, key: 'geek.stuff')
20
-
21
- @t1_en = FactoryGirl.create(:translation_data, lang: 'en', localization: 'testa atslēga', translation_id: @t1.id)
22
-
23
- @t2_en = FactoryGirl.create(:translation_data, lang: 'en', localization: 'awesome stuff', translation_id: @t2.id)
24
- @t2_lv = FactoryGirl.create(:translation_data, lang: 'lv', localization: 'lieliska manta', translation_id: @t2.id)
25
-
26
- @t3_en = FactoryGirl.create(:translation_data, lang: 'en', localization: 'geek stuff', translation_id: @t3.id)
27
- @t3_lv = FactoryGirl.create(:translation_data, lang: 'lv', localization: 'nūģu lieta', translation_id: @t3.id)
17
+ @t1 = Releaf::I18nDatabase::I18nEntry.create(key: 'test.key1')
18
+ @t2 = Releaf::I18nDatabase::I18nEntry.create(key: 'great.stuff')
19
+ @t3 = Releaf::I18nDatabase::I18nEntry.create(key: 'geek.stuff')
20
+ @t1.i18n_entry_translation.create(locale: 'en', text: 'testa atslēga')
21
+ @t2.i18n_entry_translation.create(locale: 'en', text: 'awesome stuff')
22
+ @t2.i18n_entry_translation.create(locale: 'lv', text: 'lieliska manta')
23
+ @t3.i18n_entry_translation.create(locale: 'en', text: 'geek stuff')
24
+ @t3.i18n_entry_translation.create(locale: 'lv', text: 'nūģu lieta')
28
25
  end
29
26
 
30
-
31
27
  describe "GET #index", build_translations: true do
32
28
  context "when not searching" do
33
29
  it "renders all translations" do
@@ -124,13 +120,8 @@ describe Releaf::I18nDatabase::TranslationsController do
124
120
  describe "#import" do
125
121
  context "when file uploaded" do
126
122
  before do
127
- file = fixture_file_upload(File.expand_path('../../fixtures/translations_import.xlsx', __dir__), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
128
- class << file
129
- # The reader method is present in a real invocation,
130
- # but missing from the fixture object for some reason (Rails 3.1.1)
131
- attr_reader :tempfile
132
- end
133
-
123
+ file = fixture_file_upload(File.expand_path('../../fixtures/translations_import.xlsx', __dir__),
124
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
134
125
  post :import, import_file: file
135
126
  end
136
127