spree_mobility 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|