dugway 1.2.0 → 1.3.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/README.md +32 -7
  4. data/dugway.gemspec +11 -9
  5. data/lib/dugway/cli/server.rb +67 -7
  6. data/lib/dugway/liquid/drops/artists_drop.rb +12 -0
  7. data/lib/dugway/liquid/drops/base_drop.rb +27 -2
  8. data/lib/dugway/liquid/drops/categories_drop.rb +12 -0
  9. data/lib/dugway/liquid/drops/pages_drop.rb +30 -2
  10. data/lib/dugway/liquid/drops/product_drop.rb +11 -0
  11. data/lib/dugway/liquid/drops/products_drop.rb +39 -0
  12. data/lib/dugway/liquid/drops/theme_drop.rb +29 -6
  13. data/lib/dugway/liquid/filters/core_filters.rb +169 -7
  14. data/lib/dugway/liquid/filters/font_filters.rb +1 -0
  15. data/lib/dugway/liquid/filters/util_filters.rb +1 -10
  16. data/lib/dugway/liquid/tags/get.rb +6 -6
  17. data/lib/dugway/liquid/tags/paginate.rb +61 -11
  18. data/lib/dugway/store.rb +39 -1
  19. data/lib/dugway/theme.rb +72 -33
  20. data/lib/dugway/version.rb +1 -1
  21. data/lib/dugway.rb +24 -1
  22. data/locales/storefront.de.yml +2 -0
  23. data/locales/storefront.en-CA.yml +2 -0
  24. data/locales/storefront.en-GB.yml +2 -0
  25. data/locales/storefront.en-US.yml +2 -0
  26. data/locales/storefront.es-ES.yml +2 -0
  27. data/locales/storefront.es-MX.yml +2 -0
  28. data/locales/storefront.fr-CA.yml +2 -0
  29. data/locales/storefront.fr-FR.yml +2 -0
  30. data/locales/storefront.id.yml +2 -0
  31. data/locales/storefront.it.yml +2 -0
  32. data/locales/storefront.ja.yml +2 -0
  33. data/locales/storefront.ko.yml +2 -0
  34. data/locales/storefront.nl.yml +2 -0
  35. data/locales/storefront.pl.yml +2 -0
  36. data/locales/storefront.pt-BR.yml +2 -0
  37. data/locales/storefront.pt-PT.yml +2 -0
  38. data/locales/storefront.ro.yml +2 -0
  39. data/locales/storefront.sv.yml +2 -0
  40. data/locales/storefront.tr.yml +2 -0
  41. data/locales/storefront.zh-CN.yml +2 -0
  42. data/locales/storefront.zh-TW.yml +2 -0
  43. data/mise.toml +2 -0
  44. data/spec/spec_helper.rb +3 -0
  45. data/spec/units/dugway/liquid/drops/pages_drop_spec.rb +186 -7
  46. data/spec/units/dugway/liquid/drops/product_drop_spec.rb +17 -0
  47. data/spec/units/dugway/liquid/filters/core_filters_spec.rb +301 -3
  48. data/spec/units/dugway/store_spec.rb +18 -0
  49. data/spec/units/dugway/theme_spec.rb +246 -0
  50. metadata +54 -25
@@ -1,15 +1,6 @@
1
1
  module Dugway
2
2
  module Filters
3
3
  module UtilFilters
4
- private
5
-
6
- # Registered Vars
7
-
8
- def currency
9
- @context.registers[:currency]
10
- end
11
-
12
- # Mimics
13
4
 
14
5
  DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",", :precision => 2, :significant => false, :strip_insignificant_zeros => false }
15
6
 
@@ -126,7 +117,7 @@ module Dugway
126
117
 
127
118
  def content_tag(type, content, options = {})
128
119
  result = tag(type, options, true)
129
- result += content
120
+ result += content.to_s
130
121
  result += "</#{type}>"
131
122
  end
132
123
 
@@ -5,16 +5,16 @@ module Dugway
5
5
 
6
6
  def initialize(tag_name, markup, tokens)
7
7
  if markup =~ Syntax
8
- @number_to_get = $1.present? ? $2 : nil
9
- @variable_name = $3
10
- @collection_name = $4
8
+ @number_to_get = Regexp.last_match(1).present? ? Regexp.last_match(2) : nil
9
+ @variable_name = Regexp.last_match(3)
10
+ @collection_name = Regexp.last_match(4)
11
11
 
12
12
  @attributes = {}
13
13
  markup.scan(Liquid::TagAttributes) { |key, value|
14
14
  @attributes[key] = value
15
15
  }
16
16
  else
17
- raise SyntaxError.new("Syntax Error in tag 'get' - Valid syntax: get [number] [items] from [collection] order:[order]")
17
+ raise SyntaxError, "Syntax Error in tag 'get' - Valid syntax: get [number] [items] from [collection] order:[order]"
18
18
  end
19
19
 
20
20
  super
@@ -48,9 +48,9 @@ module Dugway
48
48
 
49
49
  context[@variable_name] = context[@collection_name]
50
50
 
51
- raise ArgumentError.new("Cannot get array '#{ @collection_name }'. Not found.") if context[@variable_name].total_entries.nil?
51
+ raise ArgumentError, "Cannot get array '#{@collection_name}'. Not found." if context[@variable_name].total_entries.nil?
52
52
 
53
- render_all @nodelist, context
53
+ super(context)
54
54
  end
55
55
  end
56
56
  end
@@ -5,9 +5,9 @@ module Dugway
5
5
 
6
6
  def initialize(tag_name, markup, tokens)
7
7
  if markup =~ Syntax
8
- @variable_name = $1
9
- @collection_name = $2
10
- @per_page = $3.present? ? $4 : nil
8
+ @variable_name = Regexp.last_match(1)
9
+ @collection_name = Regexp.last_match(2)
10
+ @per_page = Regexp.last_match(3).present? ? Regexp.last_match(4) : nil
11
11
 
12
12
  @attributes = { 'inner_window' => 3, 'outer_window' => 1 }
13
13
 
@@ -17,7 +17,7 @@ module Dugway
17
17
 
18
18
  @limit = @attributes['limit']
19
19
  else
20
- raise SyntaxError.new("Syntax Error in tag 'paginate' - Valid syntax: paginate [variable] from [collection] by [number]")
20
+ raise SyntaxError, "Syntax Error in tag 'paginate' - Valid syntax: paginate [variable] from [collection] by [number]"
21
21
  end
22
22
 
23
23
  super
@@ -29,6 +29,7 @@ module Dugway
29
29
  @per_page = context[@per_page].present? ? context[@per_page] : (@per_page.present? ? @per_page.to_i : nil)
30
30
  @order = context[@attributes['order']].present? ? context[@attributes['order']] : @attributes['order']
31
31
 
32
+
32
33
  context.stack do
33
34
  context['internal'] = {
34
35
  'per_page' => @per_page,
@@ -37,21 +38,45 @@ module Dugway
37
38
  'limit' => @limit
38
39
  }
39
40
 
40
- collection = context[@collection_name]
41
+ # Handle nested property access like "products.current"
42
+ collection = resolve_collection_path(context, @collection_name)
43
+
41
44
  context[@variable_name] = collection
42
- current_page = collection.current_page
45
+
46
+ begin
47
+ current_page = collection.current_page
48
+ rescue StandardError => e
49
+ puts "Warning: Failed to get current_page: #{e.message}" if ENV['DEBUG']
50
+ current_page = 1
51
+ end
52
+
53
+ begin
54
+ page_size = collection.per_page
55
+ offset = collection.offset
56
+ rescue StandardError => e
57
+ puts "Warning: Failed to get pagination info: #{e.message}" if ENV['DEBUG']
58
+ page_size = 10
59
+ offset = 0
60
+ end
43
61
 
44
62
  pagination = {
45
- 'page_size' => collection.per_page,
63
+ 'page_size' => page_size,
46
64
  'current_page' => current_page,
47
- 'current_offset' => collection.offset
65
+ 'current_offset' => offset
48
66
  }
49
67
 
50
68
  context['paginate'] = pagination
51
69
 
52
- collection_size = collection.total_entries
70
+ begin
71
+ collection_size = collection.total_entries
72
+ rescue StandardError => e
73
+ puts "Warning: Failed to get total_entries: #{e.message}" if ENV['DEBUG']
74
+ collection_size = collection.respond_to?(:size) ? collection.size : 0
75
+ end
53
76
 
54
- raise ArgumentError.new("Cannot paginate array '#{ @collection_name }'. Not found.") if collection_size.nil?
77
+ if collection_size.nil? || collection_size == 0
78
+ return ""
79
+ end
55
80
 
56
81
  page_count = collection.total_pages
57
82
 
@@ -103,12 +128,37 @@ module Dugway
103
128
  }
104
129
  end
105
130
 
106
- render_all @nodelist, context
131
+ super(context)
107
132
  end
108
133
  end
109
134
 
110
135
  private
111
136
 
137
+ def resolve_collection_path(context, path)
138
+ return context[path] unless path.include?('.')
139
+
140
+ parts = path.split('.')
141
+ collection = context[parts.shift]
142
+
143
+ parts.each do |part|
144
+ break unless collection
145
+
146
+ if collection.respond_to?(part)
147
+ collection = collection.public_send(part)
148
+ elsif collection.respond_to?(:liquid_method_missing)
149
+ collection = collection.liquid_method_missing(part)
150
+ else
151
+ collection = nil
152
+ break
153
+ end
154
+ end
155
+
156
+ collection || []
157
+ rescue StandardError => e
158
+ puts "Warning: Failed to resolve collection '#{path}': #{e.message}" if ENV['DEBUG']
159
+ []
160
+ end
161
+
112
162
  def no_link(title)
113
163
  { 'title' => title.to_s, 'is_link' => false }
114
164
  end
data/lib/dugway/store.rb CHANGED
@@ -36,7 +36,7 @@ module Dugway
36
36
  end
37
37
 
38
38
  def pages
39
- @pages ||= theme_pages + custom_pages
39
+ @pages ||= theme_pages + custom_pages + external_pages + required_pages
40
40
  end
41
41
 
42
42
  def page(permalink)
@@ -136,6 +136,44 @@ module Dugway
136
136
  Dugway.options.dig(:store, :instant_checkout) || false
137
137
  end
138
138
 
139
+ def subscribe_url
140
+ @store_options[:subscribe_url] || account['subscribe_url']
141
+ end
142
+
143
+ def price_suffix
144
+ @store_options[:price_suffix]
145
+ end
146
+
147
+ def external_pages
148
+ @external_pages ||= begin
149
+ pages = []
150
+ if subscribe_url && !subscribe_url.empty?
151
+ pages << {
152
+ 'name' => 'Subscribe',
153
+ 'permalink' => 'subscribe',
154
+ 'url' => subscribe_url,
155
+ 'category' => 'external'
156
+ }
157
+ end
158
+ pages
159
+ end
160
+ end
161
+
162
+ def required_pages
163
+ @required_pages ||= begin
164
+ required_pages_config = @store_options[:required_pages] || account['required_pages'] || []
165
+ required_pages_config.map do |page_config|
166
+ {
167
+ 'name' => page_config['name'],
168
+ 'permalink' => page_config['permalink'],
169
+ 'url' => page_config['url'],
170
+ 'category' => page_config['category'] || 'custom',
171
+ 'required' => true
172
+ }
173
+ end
174
+ end
175
+ end
176
+
139
177
  private
140
178
 
141
179
  def get(path)
data/lib/dugway/theme.rb CHANGED
@@ -294,7 +294,10 @@ module Dugway
294
294
  def validate_options_requires
295
295
  return unless settings['options']
296
296
  all_variables = settings['options'].map { |o| o['variable'] }
297
- valid_operators = %w(eq neq gt lt gte lte present)
297
+
298
+ # Include variables from images and image_sets settings
299
+ all_variables.concat(settings['images'].map { |i| i['variable'] }) if settings['images']
300
+ all_variables.concat(settings['image_sets'].map { |i| i['variable'] }) if settings['image_sets']
298
301
 
299
302
  settings['options'].each do |option|
300
303
  next unless option['requires']
@@ -316,45 +319,81 @@ module Dugway
316
319
 
317
320
  # Process each rule in the array
318
321
  option['requires'].each do |rule|
319
- next if rule == 'inventory'
320
-
321
- # Extract setting name and optional operator/value
322
- parts = rule.split(/\s+/)
323
- setting_name = parts[0]
324
- operator = parts[1]
325
- value = parts[2]
326
-
327
- if setting_name.start_with?('feature:')
328
- # Validate feature flag format
329
- unless setting_name =~ /^feature:[a-z_]+$/
330
- @errors << "Option '#{option['variable']}' has invalid feature flag format"
331
- next
332
- end
322
+ validate_requires_rule(rule, option['variable'], all_variables)
323
+ end
324
+ end
325
+ end
326
+
327
+ # Validates individual requires rule - handles both string rules and object rules (like 'any')
328
+ def validate_requires_rule(rule, option_variable, all_variables)
329
+ # Handle object-based rules (like {"any": [...]})
330
+ if rule.is_a?(Hash)
331
+ if rule.has_key?('any')
332
+ validate_any_condition(rule['any'], option_variable, all_variables)
333
+ else
334
+ @errors << "Option '#{option_variable}' has unsupported requires object. Only 'any' is currently supported."
335
+ end
336
+ return
337
+ end
333
338
 
334
- # Only validate operator/value if they exist
335
- if operator
336
- is_valid_operator = ['eq', 'neq'].include?(operator)
339
+ # Handle string-based rules (existing logic)
340
+ return if rule == 'inventory'
337
341
 
338
- @errors << "Option '#{option['variable']}' has invalid operator '#{operator}'. Feature flags can only use `eq` or `neq`." unless is_valid_operator
342
+ valid_operators = %w(eq neq gt lt gte lte present)
339
343
 
340
- is_valid_value = ['visible'].include?(value)
344
+ # Extract setting name and optional operator/value
345
+ parts = rule.split(/\s+/)
346
+ setting_name = parts[0]
347
+ operator = parts[1]
348
+ value = parts[2]
349
+
350
+ if setting_name.start_with?('feature:')
351
+ # Validate feature flag format
352
+ unless setting_name =~ /^feature:[a-z_]+$/
353
+ @errors << "Option '#{option_variable}' has invalid feature flag format"
354
+ return
355
+ end
341
356
 
342
- @errors << "Option '#{option['variable']}' has invalid comparison value '#{value}'. Feature flags can only check for `visible`." unless is_valid_value
343
- end
344
- next
345
- end
357
+ # Only validate operator/value if they exist
358
+ if operator
359
+ is_valid_operator = ['eq', 'neq'].include?(operator)
346
360
 
347
- # --- Non-feature rule validation ---
348
- unless all_variables.include?(setting_name)
349
- @errors << "Option '#{option['variable']}' requires unknown setting '#{setting_name}'"
350
- next # Skip further checks for this rule if setting is unknown
351
- end
361
+ @errors << "Option '#{option_variable}' has invalid operator '#{operator}'. Feature flags can only use `eq` or `neq`." unless is_valid_operator
352
362
 
353
- # Validate operator if present
354
- if operator && !valid_operators.include?(operator)
355
- @errors << "Option '#{option['variable']}' has invalid operator '#{operator}'. Allowed operators are: #{valid_operators.join(', ')}."
356
- end
363
+ is_valid_value = ['visible'].include?(value)
364
+
365
+ @errors << "Option '#{option_variable}' has invalid comparison value '#{value}'. Feature flags can only check for `visible`." unless is_valid_value
357
366
  end
367
+ return
368
+ end
369
+
370
+ # --- Non-feature rule validation ---
371
+ unless all_variables.include?(setting_name)
372
+ @errors << "Option '#{option_variable}' requires unknown setting '#{setting_name}'"
373
+ return # Skip further checks for this rule if setting is unknown
374
+ end
375
+
376
+ # Validate operator if present
377
+ if operator && !valid_operators.include?(operator)
378
+ @errors << "Option '#{option_variable}' has invalid operator '#{operator}'. Allowed operators are: #{valid_operators.join(', ')}."
379
+ end
380
+ end
381
+
382
+ # Validates 'any' condition - ensures it's an array and validates each nested rule
383
+ def validate_any_condition(any_rules, option_variable, all_variables)
384
+ unless any_rules.is_a?(Array)
385
+ @errors << "Option '#{option_variable}' has invalid 'any' condition - must be an array of rules"
386
+ return
387
+ end
388
+
389
+ if any_rules.empty?
390
+ @errors << "Option '#{option_variable}' has empty 'any' condition - must contain at least one rule"
391
+ return
392
+ end
393
+
394
+ # Validate each rule within the 'any' condition
395
+ any_rules.each do |nested_rule|
396
+ validate_requires_rule(nested_rule, option_variable, all_variables)
358
397
  end
359
398
  end
360
399
 
@@ -1,3 +1,3 @@
1
1
  module Dugway
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.0"
3
3
  end
data/lib/dugway.rb CHANGED
@@ -11,6 +11,7 @@ end
11
11
 
12
12
  require 'active_support/all'
13
13
  require 'i18n'
14
+ require 'i18n/backend/fallbacks'
14
15
  require 'bigcartel-currency-locales'
15
16
  require 'bigcartel/theme/fonts'
16
17
 
@@ -38,6 +39,14 @@ end
38
39
  # Set available locales. Provide a default if no locale files are found (e.g., during initial setup or in case of an error).
39
40
  I18n.config.available_locales = available_locales.empty? ? [:'en-US', :en] : available_locales
40
41
 
42
+ # Enable fallbacks for I18n
43
+ # This allows sv-SE to fallback to sv, en-GB to en, etc.
44
+ I18n::Backend::Simple.include(I18n::Backend::Fallbacks)
45
+
46
+ # Disable enforce_available_locales to allow invalid locales like 'eu'
47
+ # We'll handle them gracefully with fallbacks
48
+ I18n.enforce_available_locales = false
49
+
41
50
  # Reload I18n to apply changes and load the translations from the files.
42
51
  I18n.backend.reload!
43
52
 
@@ -63,7 +72,21 @@ module Dugway
63
72
 
64
73
  BigCartel::CurrencyLocales.insert
65
74
  I18n.default_locale = 'en-US'
66
- I18n.locale = Dugway.store.locale
75
+
76
+ # Set locale with fallback handling for invalid or missing locales
77
+ begin
78
+ requested_locale = Dugway.store.locale
79
+ if requested_locale
80
+ # Try to set the locale - if it's completely invalid (like 'eu'),
81
+ # this will fall back to default_locale due to enforce_available_locales = false
82
+ I18n.locale = requested_locale
83
+ else
84
+ I18n.locale = I18n.default_locale
85
+ end
86
+ rescue I18n::InvalidLocale
87
+ # If somehow we still get an invalid locale error, fall back to default
88
+ I18n.locale = I18n.default_locale
89
+ end
67
90
 
68
91
  Rack::Builder.app do
69
92
  use Rack::Session::Cookie, :secret => 'stopwarningmeaboutnothavingasecret'
@@ -59,6 +59,8 @@ de:
59
59
  select: "Wählen"
60
60
  select_variant: "Option wählen"
61
61
  sold_out: "Ausverkauft"
62
+ variant: "Option"
63
+ variants: "Optionen"
62
64
  cart:
63
65
  checkout: "Zur Kasse"
64
66
  continue_shopping: "Weiter einkaufen"
@@ -59,6 +59,8 @@ en-CA:
59
59
  select: "Select"
60
60
  select_variant: "Select option"
61
61
  sold_out: "Sold out"
62
+ variant: "Option"
63
+ variants: "Options"
62
64
  cart:
63
65
  checkout: "Checkout"
64
66
  continue_shopping: "Continue shopping"
@@ -59,6 +59,8 @@ en-GB:
59
59
  select: "Select"
60
60
  select_variant: "Select option"
61
61
  sold_out: "Sold out"
62
+ variant: "Option"
63
+ variants: "Options"
62
64
  cart:
63
65
  checkout: "Checkout"
64
66
  continue_shopping: "Continue shopping"
@@ -59,6 +59,8 @@ en-US:
59
59
  select: "Select"
60
60
  select_variant: "Select option"
61
61
  sold_out: "Sold out"
62
+ variant: "Option"
63
+ variants: "Options"
62
64
  cart:
63
65
  checkout: "Checkout"
64
66
  continue_shopping: "Continue shopping"
@@ -59,6 +59,8 @@ es-ES:
59
59
  select: "Selecciona"
60
60
  select_variant: "Seleccionar opción"
61
61
  sold_out: "Agotado"
62
+ variant: "Opción"
63
+ variants: "Opciones"
62
64
  cart:
63
65
  checkout: "Comprar"
64
66
  continue_shopping: "Continuar comprando"
@@ -59,6 +59,8 @@ es-MX:
59
59
  select: "Selecciona"
60
60
  select_variant: "Seleccionar opción"
61
61
  sold_out: "Agotado"
62
+ variant: "Opción"
63
+ variants: "Opciones"
62
64
  cart:
63
65
  checkout: "Pagar"
64
66
  continue_shopping: "Continuar comprando"
@@ -59,6 +59,8 @@ fr-CA:
59
59
  select: "Sélectionner"
60
60
  select_variant: "Choisir une option"
61
61
  sold_out: "Épuisé"
62
+ variant: "Option"
63
+ variants: "Options"
62
64
  cart:
63
65
  checkout: "Commander"
64
66
  continue_shopping: "Continuer à magasiner"
@@ -59,6 +59,8 @@ fr-FR:
59
59
  select: "Sélectionner"
60
60
  select_variant: "Choisir une option"
61
61
  sold_out: "Épuisé"
62
+ variant: "Option"
63
+ variants: "Options"
62
64
  cart:
63
65
  checkout: "Commander"
64
66
  continue_shopping: "Continuer les achats"
@@ -59,6 +59,8 @@ id:
59
59
  select: "Pilih"
60
60
  select_variant: "Pilih varian"
61
61
  sold_out: "Habis terjual"
62
+ variant: "Varian"
63
+ variants: "Varian"
62
64
  cart:
63
65
  checkout: "Pembayaran"
64
66
  continue_shopping: "Lanjutkan belanja"
@@ -59,6 +59,8 @@ it:
59
59
  select: "Seleziona"
60
60
  select_variant: "Seleziona opzione"
61
61
  sold_out: "Esaurito"
62
+ variant: "Opzione"
63
+ variants: "Opzioni"
62
64
  cart:
63
65
  checkout: "Cassa"
64
66
  continue_shopping: "Continua lo shopping"
@@ -59,6 +59,8 @@ ja:
59
59
  select: "選択"
60
60
  select_variant: "詳細を選択"
61
61
  sold_out: "売り切れ"
62
+ variant: "詳細"
63
+ variants: "詳細"
62
64
  cart:
63
65
  checkout: "レジに進む"
64
66
  continue_shopping: "買い物を続ける"
@@ -59,6 +59,8 @@ ko:
59
59
  select: "선택"
60
60
  select_variant: "옵션 선택"
61
61
  sold_out: "품절"
62
+ variant: "옵션"
63
+ variants: "옵션"
62
64
  cart:
63
65
  checkout: "결제하기"
64
66
  continue_shopping: "계속 쇼핑하기"
@@ -59,6 +59,8 @@ nl:
59
59
  select: "Selecteren"
60
60
  select_variant: "Kies optie"
61
61
  sold_out: "Uitverkocht"
62
+ variant: "Optie"
63
+ variants: "Opties"
62
64
  cart:
63
65
  checkout: "Afrekenen"
64
66
  continue_shopping: "Verder winkelen"
@@ -59,6 +59,8 @@ pl:
59
59
  select: "Wybierz"
60
60
  select_variant: "Wybierz opcję"
61
61
  sold_out: "Wyprzedane"
62
+ variant: "Opcja"
63
+ variants: "Opcje"
62
64
  cart:
63
65
  checkout: "Do kasy"
64
66
  continue_shopping: "Kontynuuj zakupy"
@@ -59,6 +59,8 @@ pt-BR:
59
59
  select: "Selecione"
60
60
  select_variant: "Selecionar opção"
61
61
  sold_out: "Esgotado"
62
+ variant: "Opção"
63
+ variants: "Opções"
62
64
  cart:
63
65
  checkout: "Comprar"
64
66
  continue_shopping: "Continuar comprando"
@@ -59,6 +59,8 @@ pt-PT:
59
59
  select: "Selecione"
60
60
  select_variant: "Selecionar opção"
61
61
  sold_out: "Esgotado"
62
+ variant: "Opção"
63
+ variants: "Opções"
62
64
  cart:
63
65
  checkout: "Comprar"
64
66
  continue_shopping: "Continuar a comprar"
@@ -59,6 +59,8 @@ ro:
59
59
  select: "Selectează"
60
60
  select_variant: "Alege opțiunea"
61
61
  sold_out: "Epuizat"
62
+ variant: "Opțiune"
63
+ variants: "Opțiuni"
62
64
  cart:
63
65
  checkout: "Comandă"
64
66
  continue_shopping: "Continuă cumpărăturile"
@@ -59,6 +59,8 @@ sv:
59
59
  select: "Välj"
60
60
  select_variant: "Välj alternativ"
61
61
  sold_out: "Slutsåld"
62
+ variant: "Alternativ"
63
+ variants: "Alternativ"
62
64
  cart:
63
65
  checkout: "Till kassan"
64
66
  continue_shopping: "Fortsätt handla"
@@ -59,6 +59,8 @@ tr:
59
59
  select: "Seç"
60
60
  select_variant: "Seçenek seç"
61
61
  sold_out: "Tükendi"
62
+ variant: "Seçenek"
63
+ variants: "Seçenekler"
62
64
  cart:
63
65
  checkout: "Ödeme"
64
66
  continue_shopping: "Alışverişe devam et"
@@ -59,6 +59,8 @@ zh-CN:
59
59
  select: "选择"
60
60
  select_variant: "选择规格"
61
61
  sold_out: "已售罄"
62
+ variant: "规格"
63
+ variants: "规格"
62
64
  cart:
63
65
  checkout: "结账"
64
66
  continue_shopping: "继续购物"
@@ -59,6 +59,8 @@ zh-TW:
59
59
  select: "選擇"
60
60
  select_variant: "選擇規格"
61
61
  sold_out: "已售罄"
62
+ variant: "規格"
63
+ variants: "規格"
62
64
  cart:
63
65
  checkout: "結帳"
64
66
  continue_shopping: "繼續購物"
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "2.7.8"
data/spec/spec_helper.rb CHANGED
@@ -10,6 +10,9 @@ RSpec.configure do |config|
10
10
  fixture_path = File.join(Dir.pwd, 'spec', 'fixtures')
11
11
 
12
12
  config.before(:each) do
13
+ # Reset cart state between tests
14
+ Dugway.instance_variable_set(:@cart, nil)
15
+
13
16
  # Stub Dugway.logger to write to /dev/null before each test
14
17
  # This prevents log file creation/modification by the test suite.
15
18
  allow(Dugway).to receive(:logger).and_return(Logger.new(File::NULL))