dugway 1.1.0 → 1.2.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.bundle/config +2 -0
  3. data/.gitignore +1 -0
  4. data/lib/dugway/application.rb +5 -3
  5. data/lib/dugway/assets/big_cartel_logo.svg +4 -0
  6. data/lib/dugway/cli/build.rb +7 -1
  7. data/lib/dugway/cli/server.rb +2 -2
  8. data/lib/dugway/cli/templates/source/settings.json +8 -0
  9. data/lib/dugway/cli/validate.rb +9 -2
  10. data/lib/dugway/controller.rb +5 -1
  11. data/lib/dugway/liquid/drops/account_drop.rb +4 -0
  12. data/lib/dugway/liquid/drops/features_drop.rb +144 -0
  13. data/lib/dugway/liquid/drops/theme_drop.rb +23 -0
  14. data/lib/dugway/liquid/drops/translations_drop.rb +122 -0
  15. data/lib/dugway/liquifier.rb +44 -8
  16. data/lib/dugway/store.rb +7 -2
  17. data/lib/dugway/theme.rb +107 -10
  18. data/lib/dugway/version.rb +1 -1
  19. data/lib/dugway.rb +31 -1
  20. data/locales/storefront.de.yml +79 -0
  21. data/locales/storefront.en-CA.yml +79 -0
  22. data/locales/storefront.en-GB.yml +79 -0
  23. data/locales/storefront.en-US.yml +79 -0
  24. data/locales/storefront.es-ES.yml +79 -0
  25. data/locales/storefront.es-MX.yml +79 -0
  26. data/locales/storefront.fr-CA.yml +79 -0
  27. data/locales/storefront.fr-FR.yml +79 -0
  28. data/locales/storefront.id.yml +79 -0
  29. data/locales/storefront.it.yml +79 -0
  30. data/locales/storefront.ja.yml +79 -0
  31. data/locales/storefront.ko.yml +79 -0
  32. data/locales/storefront.nl.yml +79 -0
  33. data/locales/storefront.pl.yml +79 -0
  34. data/locales/storefront.pt-BR.yml +79 -0
  35. data/locales/storefront.pt-PT.yml +79 -0
  36. data/locales/storefront.ro.yml +79 -0
  37. data/locales/storefront.sv.yml +79 -0
  38. data/locales/storefront.tr.yml +79 -0
  39. data/locales/storefront.zh-CN.yml +79 -0
  40. data/locales/storefront.zh-TW.yml +79 -0
  41. data/log/dugway.log +1 -0
  42. data/spec/features/page_rendering_spec.rb +4 -4
  43. data/spec/fixtures/theme/layout.html +2 -0
  44. data/spec/fixtures/theme/settings.json +6 -0
  45. data/spec/spec_helper.rb +4 -0
  46. data/spec/units/dugway/liquid/drops/features_drop_spec.rb +182 -0
  47. data/spec/units/dugway/liquid/drops/theme_drop_spec.rb +45 -0
  48. data/spec/units/dugway/liquid/drops/translations_drop_spec.rb +292 -0
  49. data/spec/units/dugway/store_spec.rb +37 -0
  50. data/spec/units/dugway/theme_spec.rb +297 -1
  51. metadata +32 -2
@@ -0,0 +1,122 @@
1
+ require 'i18n'
2
+
3
+ module Dugway
4
+ module Drops
5
+ class TranslationsDrop < BaseDrop
6
+ class MissingTranslationError < StandardError; end
7
+
8
+ # @param settings [Hash] Theme customization values (resolved settings).
9
+ # @param definitions [Hash] Raw theme setting definitions (from settings.json).
10
+ def initialize(settings, definitions = {})
11
+ @settings = settings || {}
12
+ @definitions = definitions || {} # Store definitions
13
+ # Read settings using both symbol and string keys for flexibility.
14
+ @translation_mode = (@settings[:translation_mode] || @settings['translation_mode'])&.to_s
15
+ # Use explicit theme setting for locale if present, otherwise default to current I18n.locale
16
+ @translation_locale = (@settings[:translation_locale] || @settings['translation_locale'])&.to_s.presence || I18n.locale.to_s
17
+ super()
18
+ end
19
+
20
+ # Liquid access: `{{ translations['key.name'] }}`
21
+ # Returns the translation string (which could be an empty string),
22
+ # or a "[MISSING TRANSLATION: ...]" string if the key is not found or its value is nil.
23
+ #
24
+ # @param key [String, Symbol] The translation key.
25
+ # @return [String, nil] The translation or missing translation indicator.
26
+ def [](key)
27
+ key_str = key.to_s
28
+ translation = find_translation(key_str)
29
+
30
+ if translation.nil?
31
+ # Key is missing, or explicitly nil in manual mode - treat both as missing.
32
+ log_missing_translation(key_str)
33
+ return missing_translation_message(key_str)
34
+ end
35
+
36
+ # Key found, return its value.
37
+ translation
38
+ end
39
+
40
+ private
41
+
42
+ def manual_mode?
43
+ @translation_mode == 'manual'
44
+ end
45
+
46
+ # Looks up translation using I18n.
47
+ # Returns nil if the key is not found or the locale is invalid.
48
+ def lookup_automatic_translation(key)
49
+ locale_to_use = @translation_locale
50
+ # `default: nil` prevents I18n from raising an error on missing keys.
51
+ I18n.t("storefront.#{key}", locale: locale_to_use, default: nil)
52
+ rescue I18n::InvalidLocale
53
+ nil # Treat invalid locale as a missing translation.
54
+ end
55
+
56
+ # Converts a canonical key ('a.b.c') to the theme setting key format (:a_b_c_tr_text).
57
+ def derive_manual_setting_key(key)
58
+ "#{key.tr('.', '_')}_tr_text".to_sym
59
+ end
60
+
61
+ # Finds the translation based on the current mode.
62
+ # In manual mode, respects the 'allow_blank' setting definition.
63
+ # Returns the translation string, or nil if not found or disallowed blank.
64
+ def find_translation(key_str)
65
+ if manual_mode?
66
+ manual_setting_key_sym = derive_manual_setting_key(key_str)
67
+ manual_setting_key_str = manual_setting_key_sym.to_s
68
+ custom_value = nil
69
+ setting_exists = false
70
+
71
+ # Check both symbol and string keys for manual overrides.
72
+ if @settings.key?(manual_setting_key_sym)
73
+ custom_value = @settings[manual_setting_key_sym]
74
+ setting_exists = true
75
+ elsif @settings.key?(manual_setting_key_str)
76
+ custom_value = @settings[manual_setting_key_str]
77
+ setting_exists = true
78
+ end
79
+
80
+ if setting_exists
81
+ # Find the corresponding definition to check allow_blank
82
+ # Assuming manual translations are defined similarly to 'options'
83
+ definition = find_setting_definition(manual_setting_key_str) || {}
84
+ allow_blank = definition['allow_blank'].to_s == 'true'
85
+
86
+ # Return custom value if it's present, or if it's blank but allowed
87
+ return custom_value if custom_value.present? || (allow_blank && custom_value == '')
88
+
89
+ # If we reach here, the value is blank but blank is not allowed.
90
+ # Fall through to automatic lookup.
91
+ end
92
+
93
+ # If setting key didn't exist, fall through to automatic lookup.
94
+
95
+ end # End manual_mode? check
96
+
97
+ # Automatic mode or fallback from manual mode
98
+ lookup_automatic_translation(key_str)
99
+ end
100
+
101
+ # Helper to find a setting definition by its variable name across common sections.
102
+ def find_setting_definition(variable_name)
103
+ # Look primarily in 'options' as manual translations often mimic them.
104
+ # Add other potential sections if needed in the future.
105
+ (@definitions['options'] || []).find { |d| d['variable'] == variable_name }
106
+ end
107
+
108
+ # Logs a warning message for a missing translation.
109
+ def log_missing_translation(key_str)
110
+ mode = manual_mode? ? 'manual' : 'automatic'
111
+ locale_info = manual_mode? ? '' : " locale='#{@translation_locale}'"
112
+ message = "Missing translation: key='#{key_str}'#{locale_info} mode='#{mode}'"
113
+ Dugway.logger.warn(message)
114
+ end
115
+
116
+ # Returns the standard string indicating a missing translation.
117
+ def missing_translation_message(key_str)
118
+ "[MISSING TRANSLATION: #{key_str}]"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -16,6 +16,7 @@ module Dugway
16
16
  class Liquifier
17
17
  def initialize(request)
18
18
  @request = request
19
+ load_theme_locales
19
20
  end
20
21
 
21
22
  def render(content, variables={})
@@ -41,7 +42,7 @@ module Dugway
41
42
 
42
43
  def self.render_styles(css)
43
44
  Liquid::Template.parse(css).render!(
44
- { 'theme' => Drops::ThemeDrop.new(Dugway.theme.customization) },
45
+ { 'theme' => Drops::ThemeDrop.new(Dugway.theme.customization, Dugway.theme.settings) },
45
46
  :registers => { :settings => Dugway.theme.settings }
46
47
  )
47
48
  end
@@ -68,7 +69,8 @@ module Dugway
68
69
  {
69
70
  'store' => Drops::AccountDrop.new(store.account),
70
71
  'cart' => Drops::CartDrop.new(cart),
71
- 'theme' => Drops::ThemeDrop.new(theme.customization),
72
+ # Pass both customization and definitions to ThemeDrop
73
+ 'theme' => Drops::ThemeDrop.new(theme.customization, theme.settings),
72
74
  'pages' => Drops::PagesDrop.new(store.pages.map { |p| Drops::PageDrop.new(p) }),
73
75
  'categories' => Drops::CategoriesDrop.new(store.categories.map { |c| Drops::CategoryDrop.new(c) }),
74
76
  'artists' => Drops::ArtistsDrop.new(store.artists.map { |a| Drops::ArtistDrop.new(a) }),
@@ -77,6 +79,10 @@ module Dugway
77
79
  'head_content' => [window_bigcartel_script, head_content].join,
78
80
  'bigcartel_credit' => bigcartel_credit,
79
81
  'powered_by_big_cartel' => powered_by_big_cartel,
82
+ 'big_cartel_credit_logo' => big_cartel_credit_logo,
83
+ # Pass both customization and definitions to TranslationsDrop
84
+ 'translations' => Drops::TranslationsDrop.new(theme.customization, theme.settings),
85
+ 't' => Drops::TranslationsDrop.new(theme.customization, theme.settings),
80
86
  }
81
87
  end
82
88
 
@@ -109,17 +115,47 @@ module Dugway
109
115
  "<script>#{script}</script>"
110
116
  end
111
117
 
118
+ def read_svg_asset(filename)
119
+ File.read(File.join(File.dirname(__FILE__), 'assets', filename))
120
+ end
121
+
112
122
  def bigcartel_credit
113
123
  '<a href="http://bigcartel.com/" title="Start your own store at Big Cartel now">Online Store by Big Cartel</a>'
114
124
  end
115
125
 
116
126
  def powered_by_big_cartel
117
- %(<a class="bigcartel-credit" href="https://www.bigcartel.com/?utm_source=bigcartel&utm_medium=storefront&utm_campaign=123" title="Powered by Big Cartel">
118
- <span class="bigcartel-credit__text" aria-hidden="true">Powered by</span>
119
- <svg aria-hidden="true" class="bigcartel-credit__lockup" xmlns="http://www.w3.org/2000/svg" viewBox="1.99 2.05 124.96 24.95">
120
- <path d="M46.18 4A1.91 1.91 0 1 1 50 4a1.91 1.91 0 1 1-3.81 0Zm78.76 14.35a.81.81 0 0 1-.25-.69V2.23h-4.52v2.68h1.58V18c0 2.14 1.11 3.28 3.2 3.28a6.56 6.56 0 0 0 2-.42v-2.78c-1.28.51-1.8.38-2.01.23Zm-75.27.05h1.43V21h-5.79v-2.64h1.43v-8h-1.61V7.7h4.54Zm-11.09-11a5.81 5.81 0 0 0-4.36 1.87V2.23H29.7v2.68h1.62v12.71a.81.81 0 0 1-.25.69c-.43.33-1.5 0-2.06-.23v2.76a6.59 6.59 0 0 0 2.05.42 2.92 2.92 0 0 0 2.74-1.32 6.86 6.86 0 0 0 4.27 1.34 6.66 6.66 0 0 0 6.86-7c-.01-4.06-2.68-6.97-6.35-6.97ZM38 18.57c-2.15 0-3.72-1.75-3.72-4.17s1.55-4.32 3.78-4.32a3.75 3.75 0 0 1 3.75 4.1c.02 2.55-1.58 4.39-3.81 4.39Zm68.86-.49v2.76a7.52 7.52 0 0 1-2 .42c-2.09 0-3.2-1.14-3.2-3.28V5.36l2.93-1.92V7.7h3.81l-1.91 2.68h-1.9v7.24a.77.77 0 0 0 .26.69c.53.4 2.03-.23 2.03-.23ZM58 7.31c-3.88 0-6.69 3-6.69 7.11s2.66 6.87 6.33 6.87A6.14 6.14 0 0 0 62 19.45v1.42c0 2.72-2.4 3.45-3.83 3.45a5.22 5.22 0 0 1-3.12-1.06l-2.36 1.83A7.78 7.78 0 0 0 58 27c3.21 0 6.95-1.63 6.95-6.21v-6.38A6.84 6.84 0 0 0 58 7.31Zm.12 11.29c-2.19 0-3.72-1.74-3.72-4.23s1.6-4.21 3.8-4.21a3.94 3.94 0 0 1 3.8 4.21 4 4 0 0 1-3.85 4.23ZM120.6 15c0-5.05-3.45-7.69-6.85-7.69a6.76 6.76 0 0 0-6.58 7.06 7.13 7.13 0 0 0 12.69 4.39l-2.22-1.71a4.24 4.24 0 0 1-3.44 1.69 3.86 3.86 0 0 1-3.94-3.11h10.28a3.09 3.09 0 0 0 .06-.63Zm-10.35-2.08a3.65 3.65 0 0 1 3.56-3.11 3.77 3.77 0 0 1 3.77 3.11ZM94.92 10V7.7h-4v2.68h1.62v8h-2.35a.83.83 0 0 1-.61-.19.91.91 0 0 1-.19-.64v-5.77c0-1.31-.65-4.47-5.52-4.47a7.85 7.85 0 0 0-4.14 1.18l1.17 2.23a5 5 0 0 1 3-.78 3.26 3.26 0 0 1 1.76.49 2.08 2.08 0 0 1 .81 1.78v1.25a6.58 6.58 0 0 0-3.21-.92c-2.58 0-3.91 1.62-5.19 3.2s-2.51 3-5 2.92c-2.27-.11-3.63-1.86-3.63-4.43 0-2.39 1.45-4 3.54-4a3.75 3.75 0 0 1 3.7 3.18l2.45-1.9a6.3 6.3 0 0 0-6.3-4.18 6.72 6.72 0 0 0-6.48 7c0 3.43 2.1 7.16 6.62 7.16a7.45 7.45 0 0 0 5.87-2.73 4.38 4.38 0 0 0 4.08 2.57 5.91 5.91 0 0 0 3.93-1.66 2.87 2.87 0 0 0 2.8 1.33h7.42v-2.64h-1.61v-3.21c0-3.3 1.09-4.77 3.56-4.77a3.68 3.68 0 0 1 1.45.31V8a4.81 4.81 0 0 0-1.74-.25A4.21 4.21 0 0 0 94.92 10Zm-8.47 7.48a4.93 4.93 0 0 1-3.16 1.41 1.9 1.9 0 0 1-2.05-1.91 2 2 0 0 1 2.18-2 5 5 0 0 1 3 1.18ZM11 14.52v-.89a1.78 1.78 0 0 1 .83-1.51l7.35-4.7A1.79 1.79 0 0 0 20 5.91V2.05L11 7.8 2 2.05V14.2a8.69 8.69 0 0 0 3.88 7.58L11 25.05l5.12-3.27A8.69 8.69 0 0 0 20 14.2V8.77Z" class="a"></path>
121
- </svg>
122
- </a>)
127
+ build_powered_by_link(include_text: true)
128
+ end
129
+
130
+ def big_cartel_credit_logo
131
+ build_powered_by_link(include_text: false)
132
+ end
133
+
134
+ def build_powered_by_link(include_text: true)
135
+ svg_content = read_svg_asset('big_cartel_logo.svg')
136
+
137
+ text_span = include_text ? ' <span class="bigcartel-credit__text" aria-hidden="true">Powered by</span>' : ''
138
+ <<~HTML
139
+ <a class="bigcartel-credit" href="https://www.bigcartel.com/?utm_source=bigcartel&utm_medium=storefront&utm_campaign=123}" title="Powered by Big Cartel" data-bc-hook="attribution">
140
+ #{text_span}
141
+ #{svg_content}
142
+ </a>
143
+ HTML
144
+ end
145
+
146
+ # Load theme-specific override locales
147
+ def load_theme_locales
148
+ return unless Dugway.source_dir && Dir.exist?(File.dirname(Dugway.source_dir))
149
+
150
+ parent_dir = File.dirname(Dugway.source_dir)
151
+ theme_locales_dir = File.join(parent_dir, 'locales')
152
+
153
+ if Dir.exist?(theme_locales_dir)
154
+ theme_locales_path = File.expand_path(theme_locales_dir)
155
+ # Ensure theme locales are loaded *after* defaults to allow overrides
156
+ I18n.load_path |= Dir[File.join(theme_locales_path, 'storefront.*.yml')]
157
+ I18n.backend.reload!
158
+ end
123
159
  end
124
160
  end
125
161
  end
data/lib/dugway/store.rb CHANGED
@@ -8,8 +8,9 @@ module Dugway
8
8
  default_timeout 5
9
9
  headers 'User-Agent' => "Dugway #{ Dugway::VERSION }"
10
10
 
11
- def initialize(subdomain)
11
+ def initialize(subdomain, store_options = {})
12
12
  self.class.base_uri "https://api.bigcartel.com/#{ subdomain }"
13
+ @store_options = store_options || {}
13
14
  end
14
15
 
15
16
  def account
@@ -124,7 +125,11 @@ module Dugway
124
125
  end
125
126
 
126
127
  def locale
127
- currency['locale']
128
+ @store_options[:locale] || currency['locale']
129
+ end
130
+
131
+ def website
132
+ @store_options[:website] || account['website']
128
133
  end
129
134
 
130
135
  def instant_checkout?
data/lib/dugway/theme.rb CHANGED
@@ -13,10 +13,12 @@ module Dugway
13
13
  File.join(__dir__, 'config', 'theme_color_attribute_mappings.yml')
14
14
  ).freeze
15
15
 
16
- attr_reader :errors
16
+ attr_reader :errors, :warnings
17
17
 
18
18
  def initialize(overridden_customization={})
19
19
  @overridden_customization = overridden_customization.stringify_keys
20
+ @errors = []
21
+ @warnings = []
20
22
  end
21
23
 
22
24
  def layout
@@ -39,9 +41,13 @@ module Dugway
39
41
  customization_for_type('image_sets')
40
42
  end
41
43
 
44
+ def features
45
+ customization_for_type('features')
46
+ end
47
+
42
48
  def customization
43
49
  Hash.new.tap do |customization|
44
- %w( fonts colors options images image_sets ).each do |type|
50
+ %w( fonts colors options images image_sets features).each do |type|
45
51
  customization.update(customization_for_type(type))
46
52
  end
47
53
 
@@ -95,6 +101,7 @@ module Dugway
95
101
 
96
102
  def valid?(validate_colors: true, validate_layout_attributes: true, validate_options: true)
97
103
  @errors = []
104
+ @warnings = []
98
105
 
99
106
  REQUIRED_FILES.each do |file|
100
107
  @errors << "Missing source/#{ file }" if read_source_file(file).nil?
@@ -117,7 +124,14 @@ module Dugway
117
124
 
118
125
  validate_required_color_settings if validate_colors
119
126
  validate_required_layout_attributes if validate_layout_attributes
120
- validate_options_settings if validate_options
127
+
128
+ if validate_options
129
+ validate_options_settings # Validate descriptions, requires, defaults
130
+ # Validate sections for options, images, and image_sets
131
+ validate_setting_sections(settings['options'], 'options') if settings['options']
132
+ validate_setting_sections(settings['images'], 'images') if settings['images']
133
+ validate_setting_sections(settings['image_sets'], 'image_sets') if settings['image_sets']
134
+ end
121
135
 
122
136
  @errors.empty?
123
137
  end
@@ -164,15 +178,41 @@ module Dugway
164
178
  @errors << "layout.html must have exactly one `data-bc-hook=\"footer\"`" if footer_hooks != 1
165
179
  end
166
180
 
181
+ VALID_SECTIONS = %w(global_navigation homepage product_page general messaging social translations).freeze
182
+
167
183
  def validate_options_settings
168
184
  return unless settings['options']
169
185
 
170
186
  validate_options_descriptions
171
187
  validate_options_requires
188
+ validate_option_defaults
172
189
  end
173
190
 
174
191
  private
175
192
 
193
+ # Generic method to validate the 'section' property for any array of settings
194
+ def validate_setting_sections(settings_array, setting_type_name)
195
+ return unless settings_array.is_a?(Array)
196
+
197
+ settings_array.each do |setting|
198
+ variable = setting['variable'] || 'unknown'
199
+
200
+ unless setting.key?('section')
201
+ warning_msg = "Warning: Theme setting '#{variable}' is missing the 'section' property."
202
+ @warnings << warning_msg
203
+ Kernel.warn(warning_msg)
204
+ next # Skip further checks if section is missing
205
+ end
206
+
207
+ section_value = setting['section']
208
+ unless VALID_SECTIONS.include?(section_value)
209
+ warning_msg = "Warning: Theme setting '#{variable}' has an invalid 'section' value: '#{section_value}'. Allowed values are: #{VALID_SECTIONS.join(', ')}."
210
+ @warnings << warning_msg
211
+ Kernel.warn(warning_msg)
212
+ end
213
+ end
214
+ end
215
+
176
216
  def validate_preview
177
217
  preview = settings['preset_styles']['preview']
178
218
  if preview
@@ -254,6 +294,7 @@ module Dugway
254
294
  def validate_options_requires
255
295
  return unless settings['options']
256
296
  all_variables = settings['options'].map { |o| o['variable'] }
297
+ valid_operators = %w(eq neq gt lt gte lte present)
257
298
 
258
299
  settings['options'].each do |option|
259
300
  next unless option['requires']
@@ -277,22 +318,78 @@ module Dugway
277
318
  option['requires'].each do |rule|
278
319
  next if rule == 'inventory'
279
320
 
280
- # Extract setting name from rule
281
- # Handle both simple cases ("show_search") and complex cases ("show_search eq true")
282
- setting_name = if rule.include?(' ')
283
- rule.split(/\s+/).first
284
- else
285
- rule
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
333
+
334
+ # Only validate operator/value if they exist
335
+ if operator
336
+ is_valid_operator = ['eq', 'neq'].include?(operator)
337
+
338
+ @errors << "Option '#{option['variable']}' has invalid operator '#{operator}'. Feature flags can only use `eq` or `neq`." unless is_valid_operator
339
+
340
+ is_valid_value = ['visible'].include?(value)
341
+
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
286
345
  end
287
346
 
288
- # Verify the referenced setting exists
347
+ # --- Non-feature rule validation ---
289
348
  unless all_variables.include?(setting_name)
290
349
  @errors << "Option '#{option['variable']}' requires unknown setting '#{setting_name}'"
350
+ next # Skip further checks for this rule if setting is unknown
351
+ end
352
+
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(', ')}."
291
356
  end
292
357
  end
293
358
  end
294
359
  end
295
360
 
361
+ def validate_option_value(option, value_type)
362
+ value = option[value_type]
363
+ return unless value
364
+
365
+ case option['type']
366
+ when 'select'
367
+ validate_select_option(option, value, value_type)
368
+ when 'boolean'
369
+ @errors << "#{value_type.capitalize} '#{value}' is not a boolean for #{option['variable']}" unless [true, false].include?(value)
370
+ end
371
+ end
372
+
373
+ def validate_option_defaults
374
+ return unless settings['options']
375
+ settings['options'].each do |option|
376
+ validate_option_value(option, 'default')
377
+ validate_option_value(option, 'upgrade_default')
378
+ end
379
+ end
380
+
381
+ def validate_select_option(option, value, value_type)
382
+ if option['options'] == "product_orders"
383
+ valid_values = %w[position top-selling newest views updated_new_to_old]
384
+ @errors << "#{value_type.capitalize} '#{value}' is not a valid option for #{option['variable']}" unless valid_values.include?(value)
385
+ elsif option['options'].is_a?(String) && option['options'] =~ /^\d+\.\.\d+$/
386
+ range = eval(option['options'])
387
+ @errors << "#{value_type.capitalize} '#{value}' is out of range for #{option['variable']}" unless range.include?(value.to_i)
388
+ elsif option['options'].is_a?(Array)
389
+ valid_values = option['options'].map { |o| o.is_a?(Array) ? o[1] : o }
390
+ @errors << "#{value_type.capitalize} '#{value}' is not a valid option for #{option['variable']}" unless valid_values.include?(value)
391
+ end
392
+ end
296
393
 
297
394
  def source_dir
298
395
  Dugway.source_dir
@@ -1,3 +1,3 @@
1
1
  module Dugway
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/dugway.rb CHANGED
@@ -14,6 +14,33 @@ require 'i18n'
14
14
  require 'bigcartel-currency-locales'
15
15
  require 'bigcartel/theme/fonts'
16
16
 
17
+ # Configure I18n to load locale files from the gem's locales directory.
18
+ # This allows for easy addition and management of translations.
19
+ gem_locales_pattern = File.expand_path('../locales/storefront.*.yml', File.dirname(__FILE__))
20
+ gem_locale_files = Dir.glob(gem_locales_pattern)
21
+
22
+ # Add these locale files to the I18n load path, ensuring no duplicates and preserving existing paths.
23
+ I18n.load_path = (I18n.load_path + gem_locale_files).uniq
24
+
25
+ # Determine available locales from the filenames (e.g., 'storefront.en-US.yml' -> :'en-US').
26
+ available_locales = gem_locale_files.map do |file|
27
+ # Extracts 'en-US' from 'storefront.en-US.yml' or 'de' from 'storefront.de.yml'
28
+ locale_string = File.basename(file, '.yml').split('storefront.').last
29
+ locale_string.to_sym if locale_string
30
+ end.compact.uniq
31
+
32
+ # Ensure :en is an available locale if any 'en-XX' variant exists (e.g., :en-US, :en-GB),
33
+ # as I18n can use :en as a fallback.
34
+ if available_locales.any? { |loc| loc.to_s.start_with?('en-') } && !available_locales.include?(:en)
35
+ available_locales << :en
36
+ end
37
+
38
+ # Set available locales. Provide a default if no locale files are found (e.g., during initial setup or in case of an error).
39
+ I18n.config.available_locales = available_locales.empty? ? [:'en-US', :en] : available_locales
40
+
41
+ # Reload I18n to apply changes and load the translations from the files.
42
+ I18n.backend.reload!
43
+
17
44
  require 'rack/builder'
18
45
  require 'rack/commonlogger'
19
46
  require 'better_errors'
@@ -52,7 +79,9 @@ module Dugway
52
79
  end
53
80
 
54
81
  def store
55
- @store ||= Store.new(options && options[:store] && options[:store][:subdomain] || 'dugway')
82
+ store_options = options && options[:store] ? options[:store] : {}
83
+ subdomain = store_options[:subdomain] || 'dugway'
84
+ @store ||= Store.new(subdomain, store_options)
56
85
  end
57
86
 
58
87
  def theme
@@ -74,5 +103,6 @@ module Dugway
74
103
  def options
75
104
  @options
76
105
  end
106
+
77
107
  end
78
108
  end
@@ -0,0 +1,79 @@
1
+ de:
2
+ storefront:
3
+ navigation:
4
+ all: "Alle"
5
+ all_products: "Alle Produkte"
6
+ back_to_site: "Zurück zur Seite"
7
+ cart: "Warenkorb"
8
+ categories: "Kategorien"
9
+ contact: "Kontakt"
10
+ home: "Startseite"
11
+ item: "Artikel" # capitalized as it is proper grammar in German
12
+ items: "Artikel" # capitalized as it is proper grammar in German
13
+ more: "Mehr"
14
+ next: "Weiter"
15
+ newest: "Neueste"
16
+ pages: "Seiten"
17
+ previous: "Zurück"
18
+ products: "Produkte"
19
+ quick_view: "Schnellansicht"
20
+ search: "Suche"
21
+ shop: "Shop"
22
+ social: "Folge uns"
23
+ subscribe: "Abonnieren"
24
+ top_selling: "Meistverkauft"
25
+ view: "Ansehen"
26
+ view_all: "Alle anzeigen"
27
+ # "Sort By" translations are used in very few themes, and are not exposed in theme settings for sellers to configure
28
+ sort_by: Sortieren nach
29
+ sort_by_featured: Empfohlen
30
+ sort_by_on_sale: Im Angebot
31
+ sort_by_top_selling: Bestseller
32
+ sort_by_alphabetically_a_to_z: Alphabetisch (A bis Z)
33
+ sort_by_alphabetically_z_to_a: Alphabetisch (Z bis A)
34
+ sort_by_date_new_to_old: Datum (neu zu alt)
35
+ sort_by_date_old_to_new: Datum (alt zu neu)
36
+ sort_by_price_low_to_high: Preis (niedrig zu hoch)
37
+ sort_by_price_high_to_low: Preis (hoch zu niedrig)
38
+ home:
39
+ all_products: "Alle Produkte"
40
+ featured: "Empfohlen"
41
+ featured_categories: "Empfohlene Kategorien"
42
+ featured_products: "Empfohlene Produkte"
43
+ featured_video: ""
44
+ products:
45
+ add_to_cart: "Hinzufügen"
46
+ added: "Hinzugefügt!" # Used in few themes, intentionally not exposed in theme settings
47
+ adding: "Hinzufügen..." # Used in few themes, intentionally not exposed in theme settings
48
+ almost_sold_out: "Nur noch wenige!"
49
+ coming_soon: "Demnächst verfügbar"
50
+ in_stock: "auf Lager"
51
+ description: "Beschreibung"
52
+ inventory: "Lagerbestand"
53
+ low_inventory: "Begrenzte Menge"
54
+ no_products: "Keine Produkte gefunden"
55
+ on_sale: "Im Angebot"
56
+ related_products: "Das gefällt dir vielleicht"
57
+ reset: "Zurücksetzen"
58
+ search_results: "Suchergebnisse"
59
+ select: "Wählen"
60
+ select_variant: "Option wählen"
61
+ sold_out: "Ausverkauft"
62
+ cart:
63
+ checkout: "Zur Kasse"
64
+ continue_shopping: "Weiter einkaufen"
65
+ empty_cart: "Dein Warenkorb ist leer"
66
+ quantity_abbreviated: "Anz."
67
+ quantity: "Anzahl"
68
+ remove: "Entfernen"
69
+ share_this_cart: "Diesen Warenkorb teilen"
70
+ share_this_cart_link_copy_success: "Link kopiert!"
71
+ subtotal: "Zwischensumme"
72
+ view_cart: "Warenkorb ansehen"
73
+ contact:
74
+ email: "E-Mail"
75
+ form_success: "Danke! Deine Nachricht wurde gesendet und wir werden uns bald bei dir melden."
76
+ message: "Nachricht"
77
+ name: "Name"
78
+ send_button: "Nachricht senden"
79
+ subject: "Betreff"
@@ -0,0 +1,79 @@
1
+ en-CA:
2
+ storefront:
3
+ navigation:
4
+ all: "All"
5
+ all_products: "All products"
6
+ back_to_site: "Back to site"
7
+ cart: "Cart"
8
+ categories: "Categories"
9
+ contact: "Contact"
10
+ home: "Home"
11
+ item: "item"
12
+ items: "items"
13
+ more: "More"
14
+ next: "Next"
15
+ newest: "Newest"
16
+ pages: "Pages"
17
+ previous: "Previous"
18
+ products: "Products"
19
+ quick_view: "Quick view"
20
+ search: "Search"
21
+ shop: "Shop"
22
+ social: "Follow us"
23
+ subscribe: "Subscribe"
24
+ top_selling: "Best-selling"
25
+ view: "View"
26
+ view_all: "View all"
27
+ # "Sort By" translations are used in very few themes, and are not exposed in theme settings for sellers to configure
28
+ sort_by: Sort by
29
+ sort_by_featured: Featured
30
+ sort_by_on_sale: On Sale
31
+ sort_by_top_selling: Top Selling
32
+ sort_by_alphabetically_a_to_z: Alphabetically (A to Z)
33
+ sort_by_alphabetically_z_to_a: Alphabetically (Z to A)
34
+ sort_by_date_new_to_old: Date (New to Old)
35
+ sort_by_date_old_to_new: Date (Old to New)
36
+ sort_by_price_low_to_high: Price (Low to High)
37
+ sort_by_price_high_to_low: Price (High to Low)
38
+ home:
39
+ all_products: "All products"
40
+ featured: "Featured"
41
+ featured_categories: "Featured categories"
42
+ featured_products: "Featured products"
43
+ featured_video: ""
44
+ products:
45
+ add_to_cart: "Add to cart"
46
+ adding: "Adding..." # Used in few themes, intentionally not exposed in theme settings
47
+ added: "Added!" # Used in few themes, intentionally not exposed in theme settings
48
+ almost_sold_out: "Only a few left!"
49
+ coming_soon: "Coming soon"
50
+ description: "Description"
51
+ in_stock: "in stock"
52
+ inventory: "Inventory"
53
+ low_inventory: "Limited quantities available"
54
+ no_products: "No products found"
55
+ on_sale: "On sale"
56
+ related_products: "You might also like"
57
+ reset: "Reset"
58
+ search_results: "Search results"
59
+ select: "Select"
60
+ select_variant: "Select option"
61
+ sold_out: "Sold out"
62
+ cart:
63
+ checkout: "Checkout"
64
+ continue_shopping: "Continue shopping"
65
+ empty_cart: "Your cart is empty"
66
+ quantity_abbreviated: "Qty"
67
+ quantity: "Quantity"
68
+ remove: "Remove"
69
+ share_this_cart: "Share this cart"
70
+ share_this_cart_link_copy_success: "Link copied!"
71
+ subtotal: "Subtotal"
72
+ view_cart: "View cart"
73
+ contact:
74
+ email: "Email"
75
+ form_success: "Thanks! Your message has been sent and we'll get back to you soon."
76
+ message: "Message"
77
+ name: "Name"
78
+ send_button: "Send message"
79
+ subject: "Subject"