spree_mobility 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.hound.yml +21 -0
- data/.rspec +3 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +45 -0
- data/Appraisals +8 -0
- data/CONTRIBUTING.md +81 -0
- data/Gemfile +8 -0
- data/Guardfile +12 -0
- data/LICENSE.md +26 -0
- data/README.md +92 -0
- data/Rakefile +24 -0
- data/app/assets/javascripts/spree/backend/spree_mobility.js +2 -0
- data/app/assets/javascripts/spree/backend/taxon_tree_menu.js +49 -0
- data/app/assets/javascripts/spree/backend/translations.js +50 -0
- data/app/controllers/concerns/spree_mobility/controller_mobility_helper.rb +18 -0
- data/app/controllers/spree/admin/option_values_controller_decorator.rb +23 -0
- data/app/controllers/spree/admin/product_properties_controller_decorator.rb +22 -0
- data/app/controllers/spree/admin/shipping_methods_controller_decorator.rb +18 -0
- data/app/controllers/spree/admin/translations_controller.rb +53 -0
- data/app/controllers/spree/api/base_controller_decorator.rb +5 -0
- data/app/controllers/spree/base_controller_decorator.rb +5 -0
- data/app/helpers/spree_mobility/locale_helper.rb +11 -0
- data/app/models/concerns/spree_mobility/translatable.rb +63 -0
- data/app/overrides/spree/admin/option_types/_option_value_fields/add_translation.rb +10 -0
- data/app/overrides/spree/admin/option_types/index/add_translation.rb +8 -0
- data/app/overrides/spree/admin/product_properties/_product_property_fields/add_translation.rb +10 -0
- data/app/overrides/spree/admin/products/index/search_by_name_or_sku.rb +21 -0
- data/app/overrides/spree/admin/promotions/index/add_translation_link.rb +8 -0
- data/app/overrides/spree/admin/properties/index/add_translation.rb +8 -0
- data/app/overrides/spree/admin/shared/_product_tabs/add_translations.rb +10 -0
- data/app/overrides/spree/admin/shared/_translations/translation.rb +8 -0
- data/app/overrides/spree/admin/shared/sub_menu/_configuration/store_translations.rb +8 -0
- data/app/overrides/spree/admin/shipping_methods/index/add_translation.rb +8 -0
- data/app/overrides/spree/admin/taxonomies/_list/add_translations.rb +8 -0
- data/app/views/spree/admin/translations/_fields.html.erb +12 -0
- data/app/views/spree/admin/translations/_form.html.erb +13 -0
- data/app/views/spree/admin/translations/_form_fields.html.erb +38 -0
- data/app/views/spree/admin/translations/_settings.html.erb +22 -0
- data/app/views/spree/admin/translations/option_type.html.erb +9 -0
- data/app/views/spree/admin/translations/option_value.html.erb +25 -0
- data/app/views/spree/admin/translations/product.html.erb +7 -0
- data/app/views/spree/admin/translations/product_property.html.erb +23 -0
- data/app/views/spree/admin/translations/promotion.html.erb +9 -0
- data/app/views/spree/admin/translations/property.html.erb +9 -0
- data/app/views/spree/admin/translations/shipping_method.html.erb +9 -0
- data/app/views/spree/admin/translations/store.html.erb +25 -0
- data/app/views/spree/admin/translations/taxon.html.erb +25 -0
- data/app/views/spree/admin/translations/taxonomy.html.erb +9 -0
- data/bin/rails +7 -0
- data/config/initializers/enable_extensions.rb +27 -0
- data/config/initializers/form_builder_mobility_patch.rb +53 -0
- data/config/initializers/mobility.rb +118 -0
- data/config/initializers/spree_ransack.rb +31 -0
- data/config/locales/be.yml +6 -0
- data/config/locales/bg.yml +6 -0
- data/config/locales/en.yml +6 -0
- data/config/locales/it.yml +6 -0
- data/config/locales/ru.yml +6 -0
- data/config/locales/zh-TW.yml +6 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20220411041407_add_translations_to_main_models.rb +83 -0
- data/db/migrate/20220412095648_remove_null_constraints_from_spree_tables.rb +13 -0
- data/db/migrate/20220413095648_migrate_translation_data.rb +53 -0
- data/gemfiles/spree_4_2.gemfile +8 -0
- data/gemfiles/spree_master.gemfile +10 -0
- data/lib/generators/spree_mobility/install/install_generator.rb +23 -0
- data/lib/spree_mobility/configuration.rb +12 -0
- data/lib/spree_mobility/core_ext/mobility/backends/active_record/table/mobility_acts_as_paranoid_decorator.rb +31 -0
- data/lib/spree_mobility/core_ext/spree/option_type_decorator.rb +11 -0
- data/lib/spree_mobility/core_ext/spree/option_value_decorator.rb +30 -0
- data/lib/spree_mobility/core_ext/spree/product_decorator.rb +87 -0
- data/lib/spree_mobility/core_ext/spree/product_property_decorator.rb +6 -0
- data/lib/spree_mobility/core_ext/spree/product_scopes_with_mobility_decorator.rb +16 -0
- data/lib/spree_mobility/core_ext/spree/products/find_with_mobility_decorator.rb +12 -0
- data/lib/spree_mobility/core_ext/spree/promotion_decorator.rb +11 -0
- data/lib/spree_mobility/core_ext/spree/property_decorator.rb +26 -0
- data/lib/spree_mobility/core_ext/spree/shipping_method_decorator.rb +10 -0
- data/lib/spree_mobility/core_ext/spree/store_decorator.rb +12 -0
- data/lib/spree_mobility/core_ext/spree/taxon_decorator.rb +54 -0
- data/lib/spree_mobility/core_ext/spree/taxonomy_decorator.rb +29 -0
- data/lib/spree_mobility/core_ext/spree/variant_decorator.rb +19 -0
- data/lib/spree_mobility/engine.rb +33 -0
- data/lib/spree_mobility/fallbacks.rb +45 -0
- data/lib/spree_mobility/translation_query.rb +36 -0
- data/lib/spree_mobility/version.rb +18 -0
- data/lib/spree_mobility.rb +47 -0
- data/spec/features/admin/products_spec.rb +15 -0
- data/spec/features/admin/translations_spec.rb +259 -0
- data/spec/features/translations_spec.rb +35 -0
- data/spec/models/product_spec.rb +77 -0
- data/spec/models/taxon_spec.rb +16 -0
- data/spec/models/translated_models_spec.rb +33 -0
- data/spec/models/variant_spec.rb +33 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/i18n.rb +5 -0
- data/spec/support/matchers/be_a_thorough_translation_of_matcher.rb +21 -0
- data/spec/support/matchers/have_text_like.rb +3 -0
- data/spec/support/shared_contexts/translatable_context.rb +63 -0
- data/spree_mobility.gemspec +37 -0
- metadata +316 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module SpreeMobility::CoreExt::Spree::StoreDecorator
|
|
2
|
+
def self.prepended(base)
|
|
3
|
+
base.include SpreeMobility::Translatable
|
|
4
|
+
if ::ApplicationRecord.connected? && ::ApplicationRecord.connection.table_exists?(:spree_store_translations)
|
|
5
|
+
SpreeMobility.translates_for base, :name, :meta_description, :meta_keywords, :seo_title
|
|
6
|
+
|
|
7
|
+
base.translation_class.class_eval do
|
|
8
|
+
validates :name, presence: true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module SpreeMobility::CoreExt::Spree::TaxonDecorator
|
|
2
|
+
module TranslationMethods
|
|
3
|
+
def permalink_uniqueness_validation
|
|
4
|
+
return unless permalink.present?
|
|
5
|
+
return unless translated_model
|
|
6
|
+
check_scope =
|
|
7
|
+
::Spree::Taxon.
|
|
8
|
+
where.not(id: translated_model.id).
|
|
9
|
+
where(parent_id: translated_model.parent_id,
|
|
10
|
+
taxonomy_id: translated_model.taxonomy_id).
|
|
11
|
+
joins(:translations).
|
|
12
|
+
where(spree_taxon_translations: { locale: locale }).
|
|
13
|
+
where('LOWER(spree_taxon_translations.permalink) = LOWER(?)', permalink)
|
|
14
|
+
if check_scope.exists?
|
|
15
|
+
errors.add(:permalink, :taken, value: permalink)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def name_uniqueness_validation
|
|
20
|
+
return unless name.present?
|
|
21
|
+
return unless translated_model
|
|
22
|
+
check_scope =
|
|
23
|
+
::Spree::Taxon.
|
|
24
|
+
where.not(id: translated_model.id).
|
|
25
|
+
where(parent_id: translated_model.parent_id,
|
|
26
|
+
taxonomy_id: translated_model.taxonomy_id).
|
|
27
|
+
joins(:translations).
|
|
28
|
+
where(spree_taxon_translations: { locale: locale }).
|
|
29
|
+
where('LOWER(spree_taxon_translations.name) = LOWER(?)', name)
|
|
30
|
+
if check_scope.exists?
|
|
31
|
+
errors.add(:name, :taken, value: name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.prepended(base)
|
|
37
|
+
base.include SpreeMobility::Translatable
|
|
38
|
+
SpreeMobility.translates_for base, :name, :description, :meta_title,
|
|
39
|
+
:meta_description, :meta_keywords, :permalink
|
|
40
|
+
base.friendly_id :permalink, slug_column: :permalink, use: [:history, :mobility]
|
|
41
|
+
|
|
42
|
+
base.translation_class.class_eval do
|
|
43
|
+
include TranslationMethods
|
|
44
|
+
validates :name, presence: true
|
|
45
|
+
validate :name_uniqueness_validation
|
|
46
|
+
with_options length: { maximum: 255 }, allow_blank: true do
|
|
47
|
+
validates :meta_keywords
|
|
48
|
+
validates :meta_description
|
|
49
|
+
validates :meta_title
|
|
50
|
+
end
|
|
51
|
+
validate :permalink_uniqueness_validation
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module SpreeMobility::CoreExt::Spree::TaxonomyDecorator
|
|
2
|
+
module TranslationMethods
|
|
3
|
+
def name_uniqueness_validation
|
|
4
|
+
return unless name.present?
|
|
5
|
+
return unless translated_model
|
|
6
|
+
check_scope =
|
|
7
|
+
::Spree::Taxonomy.
|
|
8
|
+
where.not(id: translated_model.id).
|
|
9
|
+
where(store_id: translated_model.store_id).
|
|
10
|
+
joins(:translations).
|
|
11
|
+
where(spree_taxonomy_translations: { locale: locale }).
|
|
12
|
+
where('LOWER(spree_taxonomy_translations.name) = LOWER(?)', name)
|
|
13
|
+
if check_scope.exists?
|
|
14
|
+
errors.add(:name, :taken, value: name)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.prepended(base)
|
|
20
|
+
base.include SpreeMobility::Translatable
|
|
21
|
+
SpreeMobility.translates_for base, :name
|
|
22
|
+
|
|
23
|
+
base.translation_class.class_eval do
|
|
24
|
+
include TranslationMethods
|
|
25
|
+
validates :name, presence: true
|
|
26
|
+
validate :name_uniqueness_validation
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SpreeMobility::CoreExt::Spree
|
|
2
|
+
module VariantDecorator
|
|
3
|
+
module ClassMethods
|
|
4
|
+
def product_name_or_sku_cont(query)
|
|
5
|
+
helper =
|
|
6
|
+
SpreeMobility::TranslationQuery.new(
|
|
7
|
+
::Spree::Product.mobility_backend_class(:name))
|
|
8
|
+
|
|
9
|
+
helper.add_joins(self.joins(:product)).
|
|
10
|
+
where(
|
|
11
|
+
"(LOWER(#{helper.col_name(:name)}) LIKE LOWER(:query)) OR (LOWER(#{::Spree::Variant.table_name}.sku) LIKE LOWER(:query))", query: "%#{query}%").distinct
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def search_by_product_name_or_sku(query)
|
|
15
|
+
product_name_or_sku_cont(query)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'mobility'
|
|
2
|
+
require 'friendly_id/mobility'
|
|
3
|
+
|
|
4
|
+
module SpreeMobility
|
|
5
|
+
class Engine < Rails::Engine
|
|
6
|
+
engine_name 'spree_mobility'
|
|
7
|
+
|
|
8
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
|
9
|
+
|
|
10
|
+
initializer "spree_mobility.environment", before: :load_config_initializers do |app|
|
|
11
|
+
SpreeMobility::Config = SpreeMobility::Configuration.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
initializer "spree_mobility.permitted_attributes", before: :load_config_initializers do |app|
|
|
15
|
+
taxon_attributes = { translations_attributes: [:id, :locale, :name, :description, :permalink, :meta_description, :meta_keywords, :meta_title] }
|
|
16
|
+
Spree::PermittedAttributes.taxon_attributes << taxon_attributes
|
|
17
|
+
|
|
18
|
+
option_value_attributes = { translations_attributes: [:id, :locale, :name, :presentation] }
|
|
19
|
+
Spree::PermittedAttributes.option_value_attributes << option_value_attributes
|
|
20
|
+
|
|
21
|
+
store_attributes = { translations_attributes: [:id, :locale, :name, :meta_description, :meta_keywords, :seo_title] }
|
|
22
|
+
Spree::PermittedAttributes.store_attributes << store_attributes
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.activate
|
|
26
|
+
Dir.glob(File.join(root, "app/**/*_decorator*.rb")) do |c|
|
|
27
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
config.to_prepare &method(:activate).to_proc
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module SpreeMobility
|
|
2
|
+
module Fallbacks
|
|
3
|
+
# Prevents the app from breaking when a translation is not present on the
|
|
4
|
+
# default locale. It should search for translations in all supported
|
|
5
|
+
# locales
|
|
6
|
+
#
|
|
7
|
+
# It needs to build a proper key value hash for every locale. So that a locale
|
|
8
|
+
# always fallbacks to itself first before looking at the default and then
|
|
9
|
+
# to any other. e.g
|
|
10
|
+
#
|
|
11
|
+
# supported_locales = [:es, :de, :en]
|
|
12
|
+
#
|
|
13
|
+
# # right
|
|
14
|
+
# { en: [:en, :de, :es], es: [:es, :en, :de] .. }
|
|
15
|
+
#
|
|
16
|
+
# # wrong, spanish locale would fallback to english first
|
|
17
|
+
# { en: [:en, :es], es: [:en, :es] }
|
|
18
|
+
#
|
|
19
|
+
# # wrong, spanish locale would fallback to german first instead of :en (default)
|
|
20
|
+
# { en: [:en, :de, :es], es: [:es, :de, :en] .. }
|
|
21
|
+
#
|
|
22
|
+
def self.config!
|
|
23
|
+
supported = if Spree::Store.respond_to?(:available_locales) && Spree::Store.available_locales.any?
|
|
24
|
+
Spree::Store.available_locales
|
|
25
|
+
else
|
|
26
|
+
Config.supported_locales
|
|
27
|
+
end
|
|
28
|
+
default = I18n.default_locale.to_s
|
|
29
|
+
|
|
30
|
+
fallbacks_map = supported.inject({}) do |fallbacks, locale|
|
|
31
|
+
if locale == default
|
|
32
|
+
fallbacks.merge(locale => (supported-[locale]).flatten)
|
|
33
|
+
else
|
|
34
|
+
fallbacks.merge(locale => [default].push(supported-[locale, default]).flatten)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Mobility.configure do
|
|
39
|
+
plugins do
|
|
40
|
+
fallbacks fallbacks_map
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module SpreeMobility
|
|
2
|
+
class TranslationQuery
|
|
3
|
+
def initialize(mobility_backend_class)
|
|
4
|
+
@mobility_backend_class = mobility_backend_class
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def add_joins(search_scope)
|
|
8
|
+
fallback_locales.each do |locale|
|
|
9
|
+
search_scope =
|
|
10
|
+
@mobility_backend_class.
|
|
11
|
+
send(:join_translations, search_scope, locale, ::Arel::Nodes::OuterJoin)
|
|
12
|
+
end
|
|
13
|
+
search_scope
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def col_name(attr)
|
|
17
|
+
select_columns = []
|
|
18
|
+
fallback_locales.each do |locale|
|
|
19
|
+
select_columns << "#{table_alias(locale)}.#{attr}"
|
|
20
|
+
end
|
|
21
|
+
"COALESCE(#{select_columns.join(',')})"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def fallback_locales
|
|
27
|
+
# Cache this result, since Mobility.locale will not change
|
|
28
|
+
# during this class's lifetime
|
|
29
|
+
@fallback_locales ||= SpreeMobility.locale_with_fallbacks
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def table_alias(locale)
|
|
33
|
+
@mobility_backend_class.table_alias(locale)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SpreeMobility
|
|
2
|
+
module_function
|
|
3
|
+
|
|
4
|
+
# Returns the version of the currently loaded SpreeMobility as a
|
|
5
|
+
# <tt>Gem::Version</tt>.
|
|
6
|
+
def version
|
|
7
|
+
Gem::Version.new VERSION::STRING
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module VERSION
|
|
11
|
+
MAJOR = 1
|
|
12
|
+
MINOR = 0
|
|
13
|
+
TINY = 0
|
|
14
|
+
PRE = nil # 'beta'
|
|
15
|
+
|
|
16
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'spree_i18n'
|
|
2
|
+
require 'spree_mobility/engine'
|
|
3
|
+
require 'spree_mobility/version'
|
|
4
|
+
require 'spree_mobility/fallbacks'
|
|
5
|
+
require 'spree_mobility/translation_query'
|
|
6
|
+
require 'deface'
|
|
7
|
+
|
|
8
|
+
module SpreeMobility
|
|
9
|
+
def self.prepend_once(to_klass, klass)
|
|
10
|
+
to_klass.prepend(klass) unless to_klass.ancestors.include?(klass)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.clear_validations_for(klass, *attrs)
|
|
14
|
+
attrs.each do |attr|
|
|
15
|
+
klass.validators_on(attr).each { |val| val.attributes.delete(attr) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.translates_for(klass, *attrs)
|
|
20
|
+
klass.translates(*attrs)
|
|
21
|
+
klass.accepts_nested_attributes_for :translations
|
|
22
|
+
klass.whitelisted_ransackable_associations ||= []
|
|
23
|
+
klass.whitelisted_ransackable_associations << 'translations'
|
|
24
|
+
clear_validations_for(klass, *attrs)
|
|
25
|
+
|
|
26
|
+
# used for preloading only current locale and its fallbacks
|
|
27
|
+
translations_assoc = klass.reflect_on_association(:translations)
|
|
28
|
+
klass.has_many :active_translations,
|
|
29
|
+
-> { where(locale: SpreeMobility.locale_with_fallbacks) },
|
|
30
|
+
class_name: translations_assoc.class_name,
|
|
31
|
+
inverse_of: translations_assoc.inverse_of.name,
|
|
32
|
+
foreign_key: translations_assoc.foreign_key
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.locale_with_fallbacks
|
|
36
|
+
result = [::Mobility.locale]
|
|
37
|
+
begin
|
|
38
|
+
# At the moment the easiest way to access Mobility fallbacks properly
|
|
39
|
+
# is through a translated model's attribute
|
|
40
|
+
backend = ::Spree::Product.mobility_backend_class(:name)
|
|
41
|
+
result.concat(backend.fallbacks[::Mobility.locale])
|
|
42
|
+
result.uniq!
|
|
43
|
+
rescue KeyError # backend not found
|
|
44
|
+
end
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
RSpec.feature "Products", :js do
|
|
2
|
+
stub_authorization!
|
|
3
|
+
given!(:product) { create(:product) }
|
|
4
|
+
given!(:store) { create(:store) }
|
|
5
|
+
|
|
6
|
+
# Regression Spec: https://github.com/spree/spree_i18n/issues/386
|
|
7
|
+
context "cloning" do
|
|
8
|
+
xscenario "doesnt blow up" do
|
|
9
|
+
visit spree.admin_products_path
|
|
10
|
+
click_icon :clone
|
|
11
|
+
|
|
12
|
+
expect(page).to have_text_like 'has been cloned'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
RSpec.feature "Translations", :js do
|
|
2
|
+
stub_authorization!
|
|
3
|
+
|
|
4
|
+
given(:language) { Spree.t(:this_file_language, scope: 'i18n', locale: 'pt-BR') }
|
|
5
|
+
given!(:store) { create(:store) }
|
|
6
|
+
|
|
7
|
+
background do
|
|
8
|
+
[:en, :'pt-BR'].each do |locale|
|
|
9
|
+
create(:store, default_locale: locale)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
context "products" do
|
|
14
|
+
given(:product) { create(:product) }
|
|
15
|
+
|
|
16
|
+
context "fills in translations fields" do
|
|
17
|
+
scenario "saves translated attributes properly" do
|
|
18
|
+
visit spree.admin_product_path(product)
|
|
19
|
+
click_on "Translations"
|
|
20
|
+
|
|
21
|
+
within("#attr_fields .name.en") { fill_in_name "Pearl Jam" }
|
|
22
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "Geleia de perola" }
|
|
23
|
+
click_on "Update"
|
|
24
|
+
|
|
25
|
+
visit spree.admin_translations_path('products', product.id)
|
|
26
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
27
|
+
|
|
28
|
+
expect(page).to have_selector("input[value='Geleia de perola']")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "product properties" do
|
|
33
|
+
given!(:product_property) { create(:product_property, value: "red") }
|
|
34
|
+
|
|
35
|
+
xscenario "saves translated attributes properly" do
|
|
36
|
+
visit spree.admin_product_product_properties_path(product_property.product)
|
|
37
|
+
within_row(1) { click_icon :translate }
|
|
38
|
+
|
|
39
|
+
within("#attr_fields .value.pt-BR") { fill_in_name "vermelho" }
|
|
40
|
+
click_on "Update"
|
|
41
|
+
visit spree.admin_product_product_properties_path(product_property.product)
|
|
42
|
+
|
|
43
|
+
expect(page).to have_selector("input[value=vermelho]")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "option types" do
|
|
48
|
+
given!(:option_type) { create(:option_value).option_type }
|
|
49
|
+
|
|
50
|
+
scenario "saves translated attributes properly" do
|
|
51
|
+
visit spree.admin_option_types_path
|
|
52
|
+
within_row(1) { click_icon :translate }
|
|
53
|
+
|
|
54
|
+
within("#attr_fields .name.en") { fill_in_name "shirt sizes" }
|
|
55
|
+
within("#attr_list") { click_on "Presentation" }
|
|
56
|
+
within("#attr_fields .presentation.en") { fill_in_name "size" }
|
|
57
|
+
within("#attr_fields .presentation.pt-BR") { fill_in_name "tamanho" }
|
|
58
|
+
click_on "Update"
|
|
59
|
+
visit spree.admin_translations_path('option_types', option_type.id)
|
|
60
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
61
|
+
click_on 'Presentation'
|
|
62
|
+
|
|
63
|
+
expect(page).to have_selector("input[value=tamanho]")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Regression test for issue #354
|
|
67
|
+
scenario "successfully creates an option type and go to its edit page" do
|
|
68
|
+
visit spree.admin_option_types_path
|
|
69
|
+
click_link "New Option Type"
|
|
70
|
+
fill_in "Name", with: "Shirt Size"
|
|
71
|
+
fill_in "Presentation", with: "Sizes"
|
|
72
|
+
click_button "Create"
|
|
73
|
+
|
|
74
|
+
expect(page).to have_text_like 'has been successfully created'
|
|
75
|
+
expect(page).to have_text_like 'Option Values'
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "option values" do
|
|
80
|
+
given!(:option_type) { create(:option_value).option_type }
|
|
81
|
+
|
|
82
|
+
scenario "saves translated attributes properly" do
|
|
83
|
+
visit spree.admin_option_types_path
|
|
84
|
+
within_row(1) { click_icon :translate }
|
|
85
|
+
|
|
86
|
+
within("#attr_fields .name.en") { fill_in_name "big" }
|
|
87
|
+
within("#attr_list") { click_on "Presentation" }
|
|
88
|
+
within("#attr_fields .presentation.en") { fill_in_name "big" }
|
|
89
|
+
within("#attr_fields .presentation.pt-BR") { fill_in_name "grande" }
|
|
90
|
+
click_on "Update"
|
|
91
|
+
|
|
92
|
+
visit spree.admin_translations_path('option_types', option_type.id)
|
|
93
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
94
|
+
click_on 'Presentation'
|
|
95
|
+
|
|
96
|
+
expect(page).to have_selector("input[value=grande]")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
context "properties" do
|
|
101
|
+
given!(:property) { create(:property) }
|
|
102
|
+
|
|
103
|
+
scenario "saves translated attributes properly" do
|
|
104
|
+
visit spree.admin_properties_path
|
|
105
|
+
within_row(1) { click_icon :translate }
|
|
106
|
+
|
|
107
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "Modelo" }
|
|
108
|
+
within("#attr_list") { click_on "Presentation" }
|
|
109
|
+
within("#attr_fields .presentation.en") { fill_in_name "Model" }
|
|
110
|
+
within("#attr_fields .presentation.pt-BR") { fill_in_name "Modelo" }
|
|
111
|
+
click_on "Update"
|
|
112
|
+
visit spree.admin_translations_path('properties', property.id)
|
|
113
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
114
|
+
|
|
115
|
+
expect(page).to have_selector("input[value=Modelo]")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
context "promotions" do
|
|
121
|
+
given!(:promotion) { create(:promotion) }
|
|
122
|
+
|
|
123
|
+
scenario "saves translated attributes properly" do
|
|
124
|
+
visit spree.admin_promotions_path
|
|
125
|
+
within_row(1) { click_icon :translate }
|
|
126
|
+
|
|
127
|
+
within("#attr_fields .name.en") { fill_in_name "All free" }
|
|
128
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "Salve salve" }
|
|
129
|
+
click_on "Update"
|
|
130
|
+
visit spree.admin_translations_path('promotions', promotion.id)
|
|
131
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
132
|
+
expect(page).to have_selector("input[value='Salve salve']")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "render edit route properly" do
|
|
136
|
+
visit spree.admin_promotions_path
|
|
137
|
+
within_row(1) { click_icon :translate }
|
|
138
|
+
click_on 'Cancel'
|
|
139
|
+
|
|
140
|
+
expect(page).to have_css('.content-header')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
context "taxonomies" do
|
|
145
|
+
given!(:taxonomy) { create(:taxonomy) }
|
|
146
|
+
|
|
147
|
+
scenario "saves translated attributes properly" do
|
|
148
|
+
visit spree.admin_taxonomies_path
|
|
149
|
+
within_row(1) { click_icon :translate }
|
|
150
|
+
|
|
151
|
+
within("#attr_fields .name.en") { fill_in_name "Guitars" }
|
|
152
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "Guitarras" }
|
|
153
|
+
click_on "Update"
|
|
154
|
+
|
|
155
|
+
visit spree.admin_translations_path('taxonomies', taxonomy.id)
|
|
156
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
157
|
+
|
|
158
|
+
expect(page).to have_selector("input[value=Guitarras]")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'taxons' do
|
|
163
|
+
given!(:taxonomy) { create(:taxonomy) }
|
|
164
|
+
given!(:taxon) { create(:taxon, taxonomy: taxonomy, parent_id: taxonomy.root.id) }
|
|
165
|
+
|
|
166
|
+
scenario "saves translated attributes properly" do
|
|
167
|
+
visit spree.admin_translations_path('taxons', taxon.id)
|
|
168
|
+
|
|
169
|
+
within("#attr_fields .name.en") { fill_in_name "Acoustic" }
|
|
170
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "Acusticas" }
|
|
171
|
+
click_on "Update"
|
|
172
|
+
|
|
173
|
+
visit spree.admin_translations_path('taxons', taxon.id)
|
|
174
|
+
|
|
175
|
+
# ensure we're not duplicating translated records on database
|
|
176
|
+
expect {
|
|
177
|
+
click_on "Update"
|
|
178
|
+
}.not_to change { taxon.translations.count }
|
|
179
|
+
|
|
180
|
+
# ensure taxon is in root or it will not be visible
|
|
181
|
+
expect(taxonomy.root.children.count).to be(1)
|
|
182
|
+
|
|
183
|
+
visit spree.admin_translations_path('taxons', taxon.id)
|
|
184
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
185
|
+
|
|
186
|
+
expect(page).to have_selector("input[value=Acusticas]")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
context "store" do
|
|
191
|
+
scenario 'saves translated attributes properly' do
|
|
192
|
+
visit spree.admin_translations_path('stores', store.id)
|
|
193
|
+
|
|
194
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "nome store" }
|
|
195
|
+
|
|
196
|
+
click_on "Update"
|
|
197
|
+
|
|
198
|
+
visit spree.admin_translations_path('stores', store.id)
|
|
199
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
200
|
+
|
|
201
|
+
expect(page).to have_selector("input[value='nome store']")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context "shipping methods" do
|
|
206
|
+
given(:shipping_category) { create(:shipping_category) }
|
|
207
|
+
given!(:shipping_method) { create(:shipping_method, shipping_categories:[shipping_category]) }
|
|
208
|
+
|
|
209
|
+
scenario 'saves translated attributes properly' do
|
|
210
|
+
visit spree.admin_translations_path('shipping_methods', shipping_method.id)
|
|
211
|
+
|
|
212
|
+
within("#attr_fields .name.en") { fill_in_name "Urgent elivery" }
|
|
213
|
+
within("#attr_fields .name.pt-BR") { fill_in_name "Entrega urgente" }
|
|
214
|
+
click_on "Update"
|
|
215
|
+
|
|
216
|
+
visit spree.admin_translations_path('shipping_methods', shipping_method.id)
|
|
217
|
+
select2 'Português (pt-BR)', from: 'Select locale'
|
|
218
|
+
expect(page).to have_selector("input[value='Entrega urgente']")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it "render edit route properly" do
|
|
222
|
+
visit spree.admin_shipping_methods_path
|
|
223
|
+
within_row(1) { click_icon :translate }
|
|
224
|
+
click_on 'Cancel'
|
|
225
|
+
|
|
226
|
+
expect(page).to have_css('.content-header')
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context "permalink routing" do
|
|
231
|
+
given(:language) { Spree.t(:this_file_language, scope: 'i18n', locale: 'de') }
|
|
232
|
+
given(:product) { create(:product) }
|
|
233
|
+
|
|
234
|
+
scenario "finds the right product with permalink in a not active language" do
|
|
235
|
+
[:en, :de].each do |locale|
|
|
236
|
+
create(:store, default_locale: locale)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
visit spree.admin_product_path(product)
|
|
240
|
+
click_on "Translations"
|
|
241
|
+
click_on "Slug"
|
|
242
|
+
within("#attr_fields .slug.en") { fill_in_name "en_link" }
|
|
243
|
+
within("#attr_fields .slug.de") { fill_in_name "de_link" }
|
|
244
|
+
click_on "Update"
|
|
245
|
+
|
|
246
|
+
visit spree.product_path 'en_link'
|
|
247
|
+
expect(page).to have_text_like 'Product'
|
|
248
|
+
|
|
249
|
+
visit spree.product_path 'de_link'
|
|
250
|
+
expect(page).to have_text_like 'Product'
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def fill_in_name(value)
|
|
257
|
+
fill_in first("input[type='text']")["name"], with: value
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
RSpec.feature "Translations" do
|
|
3
|
+
before do
|
|
4
|
+
Spree::Store.update_all(default_locale: 'pt-BR')
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
context 'product' do
|
|
8
|
+
let!(:store) { create(:store, default: true, default_locale: 'pt-BR', supported_locales: 'pt-BR', url:'http://www.example.com') }
|
|
9
|
+
let!(:product) do
|
|
10
|
+
create(:product,
|
|
11
|
+
name: 'Antimatter',
|
|
12
|
+
meta_title: 'Antimatter meta_title',
|
|
13
|
+
meta_description: 'Antimatter meta_description',
|
|
14
|
+
translations: [
|
|
15
|
+
Spree::Product::Translation.new(
|
|
16
|
+
locale: 'pt-BR',
|
|
17
|
+
name: 'Antimatéria',
|
|
18
|
+
meta_title: 'Antimatéria meta_title',
|
|
19
|
+
meta_description: 'Antimatéria meta_description'
|
|
20
|
+
)
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
scenario 'displays translated product page', js: true do
|
|
26
|
+
visit '/products/antimatter'
|
|
27
|
+
expect(page.title).to have_content('Antimatéria')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
scenario 'displays translated products list', js: true do
|
|
31
|
+
visit '/products'
|
|
32
|
+
expect(page).to have_content('Antimatéria')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
RSpec.describe Product, type: :model do
|
|
5
|
+
let(:product) { create(:product) }
|
|
6
|
+
let(:taxon) { create(:taxon) }
|
|
7
|
+
|
|
8
|
+
# Regression test for #309
|
|
9
|
+
xit "duplicates translations" do
|
|
10
|
+
original_count = product.translations.count
|
|
11
|
+
new_product = product.duplicate
|
|
12
|
+
expect(new_product.translations).not_to be_blank
|
|
13
|
+
expect(product.translations.count).to eq original_count
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Regression test for #433
|
|
17
|
+
it "allow saving a product with taxons" do
|
|
18
|
+
product.taxons << taxon
|
|
19
|
+
expect(product.taxons).to include(taxon)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "handle translation in ransack" do
|
|
23
|
+
result = described_class.ransack(name_cont: product.name[0..2]).result
|
|
24
|
+
expect(result.first).to eq product
|
|
25
|
+
|
|
26
|
+
result = described_class.search(name_cont: product.name[0..2]).result
|
|
27
|
+
expect(result.first).to eq product
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Regression tests for #466
|
|
31
|
+
describe ".like_any" do
|
|
32
|
+
context "allow searching products through their translations" do
|
|
33
|
+
before do
|
|
34
|
+
I18n.locale = :"zh-CN"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "with name" do
|
|
38
|
+
product.translations.create locale: "zh-CN",
|
|
39
|
+
name: "创意马克杯",
|
|
40
|
+
description: "<p>一流工程师设计制造手工艺品</p>",
|
|
41
|
+
meta_description: '顶尖工艺设计',
|
|
42
|
+
meta_keywords: '工艺品'
|
|
43
|
+
|
|
44
|
+
expect(Product.like_any([:name], ['创意'])).to include(product)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "with name or description" do
|
|
48
|
+
product.translations.create locale: "zh-CN",
|
|
49
|
+
name: "创意马克杯",
|
|
50
|
+
description: "<p>一流工程师设计制造手工艺品</p>",
|
|
51
|
+
meta_description: '顶尖工艺设计',
|
|
52
|
+
meta_keywords: '工艺品'
|
|
53
|
+
|
|
54
|
+
expect(Product.like_any([:name, :description], ['手工艺品'])).to include(product)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "soft-deletion" do
|
|
60
|
+
subject do
|
|
61
|
+
product.destroy
|
|
62
|
+
product.reload
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "keeps the translation on deletion" do
|
|
66
|
+
subject
|
|
67
|
+
expect(product.translations).not_to be_empty
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "changes the slug on the translation to allow reuse of original slug" do
|
|
71
|
+
expect do
|
|
72
|
+
subject
|
|
73
|
+
end.to change { product.slug }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|