diff --git a/app/controllers/admin/contents_controller.rb b/app/controllers/admin/contents_controller.rb
index 0a41cc389a..ac9263fdc0 100644
--- a/app/controllers/admin/contents_controller.rb
+++ b/app/controllers/admin/contents_controller.rb
@@ -18,6 +18,8 @@ module Admin
end
end
+ ContentConfig.updated_at = Time.zone.now
+
flash[:success] =
t(:successfully_updated, resource: I18n.t('admin.contents.edit.your_content'))
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index afefbf5bf5..ea8c4beeab 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -68,4 +68,14 @@ module ApplicationHelper
wicked_pdf_stylesheet_pack_tag(source)
end
end
+
+ def cache_with_locale(key = nil, options = {}, &block)
+ cache(cache_key_with_locale(key, I18n.locale), options) do
+ yield(block)
+ end
+ end
+
+ def cache_key_with_locale(key, locale)
+ Array.wrap(key) + [locale.to_s, I18nDigests.for_locale(locale)]
+ end
end
diff --git a/app/models/content_configuration.rb b/app/models/content_configuration.rb
index d0160407a4..2d473db24f 100644
--- a/app/models/content_configuration.rb
+++ b/app/models/content_configuration.rb
@@ -83,4 +83,19 @@ class ContentConfiguration < Spree::Preferences::Configuration
# User Guide
preference :user_guide_link, :string, default: 'https://guide.openfoodnetwork.org/'
+
+ # ContentConfig Caching
+ preference :updated_at_timestamp, :integer, default: Time.zone.today.to_time.to_i
+
+ def updated_at
+ Time.zone.at updated_at_timestamp
+ end
+
+ def updated_at=(time)
+ self.updated_at_timestamp = time.to_i
+ end
+
+ def cache_key
+ "ContentConfig:#{updated_at_timestamp}"
+ end
end
diff --git a/app/views/components/_spinner.html.haml b/app/views/components/_spinner.html.haml
index a3dee8a298..b4492c4b14 100644
--- a/app/views/components/_spinner.html.haml
+++ b/app/views/components/_spinner.html.haml
@@ -1 +1,2 @@
-%img.spinner{ src: image_pack_path("spinning-circles.svg"), style: "max-width: 100%" }
+= cache do
+ %img.spinner{ src: image_pack_path("spinning-circles.svg"), style: "max-width: 100%" }
diff --git a/app/views/home/_tagline.html.haml b/app/views/home/_tagline.html.haml
index 245d07263a..56dc937f25 100644
--- a/app/views/home/_tagline.html.haml
+++ b/app/views/home/_tagline.html.haml
@@ -1,8 +1,9 @@
-#tagline
- .row
- .small-12.text-center.columns
- %h1
- %img{src: image_pack_path("logo-white-notext.png"), title: Spree::Config.site_name}
- %br/
- %a.button.transparent{href: "/shops"}
- = t :home_shop
+= cache_with_locale "sitename:#{Spree::Config.site_name}" do
+ #tagline
+ .row
+ .small-12.text-center.columns
+ %h1
+ %img{src: image_pack_path("logo-white-notext.png"), title: Spree::Config.site_name}
+ %br/
+ %a.button.transparent{href: "/shops"}
+ = t :home_shop
diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml
index 4f10a189fb..202f761266 100644
--- a/app/views/producers/_fat.html.haml
+++ b/app/views/producers/_fat.html.haml
@@ -1,91 +1,92 @@
-.row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"}
- .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"}
- %p.fullwidth
- = render partial: "components/spinner"
+= cache_with_locale do
+ .row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"}
+ .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"}
+ %p.fullwidth
+ = render partial: "components/spinner"
- .columns.small-12.medium-7.large-7.fat{"ng-show" => "open() && !shopfront_loading"}
- / Will add in long description available once clean up HTML formatting producer.long_description
- %div{"ng-if" => "::producer.description"}
- %label
- = t :producers_about
- %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" }
- %p.text-small{ "ng-bind" => "::producer.description"}
- %div.show-for-medium-up{"ng-if" => "::producer.description.length==0"}
- %label
- %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" }
+ .columns.small-12.medium-7.large-7.fat{"ng-show" => "open() && !shopfront_loading"}
+ / Will add in long description available once clean up HTML formatting producer.long_description
+ %div{"ng-if" => "::producer.description"}
+ %label
+ = t :producers_about
+ %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" }
+ %p.text-small{ "ng-bind" => "::producer.description"}
+ %div.show-for-medium-up{"ng-if" => "::producer.description.length==0"}
+ %label
+ %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" }
- .columns.small-12.medium-5.large-5.fat{"ng-show" => "open() && !shopfront_loading"}
+ .columns.small-12.medium-5.large-5.fat{"ng-show" => "open() && !shopfront_loading"}
- %div{"ng-if" => "::producer.supplied_taxons"}
- %label
- = t :producers_buy
- %p.trans-sentence
- %div
- %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"}
- %span{"ng-bind" => "::taxon.name"}
- %div
- %span.fat-properties{"ng-repeat" => "property in producer.supplied_properties"}
- %span{"ng-bind" => "property.presentation"}
+ %div{"ng-if" => "::producer.supplied_taxons"}
+ %label
+ = t :producers_buy
+ %p.trans-sentence
+ %div
+ %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"}
+ %span{"ng-bind" => "::taxon.name"}
+ %div
+ %span.fat-properties{"ng-repeat" => "property in producer.supplied_properties"}
+ %span{"ng-bind" => "property.presentation"}
- %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}
-
+ %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}
+
- %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"}
- %label
- = t :producers_contact
+ %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"}
+ %label
+ = t :producers_contact
- %p.word-wrap{"ng-if" => "::producer.phone"}
- = t :producers_contact_phone
- %span{"ng-bind" => "::producer.phone"}
+ %p.word-wrap{"ng-if" => "::producer.phone"}
+ = t :producers_contact_phone
+ %span{"ng-bind" => "::producer.phone"}
- %p.word-wrap{"ng-if" => "::producer.whatsapp_phone"}
- %a{"ng-href" => "{{::producer.whatsapp_url}}", target: "_blank"}
- %img{ src: image_pack_path("social-logos/whatsapp.svg") }
- %span{"ng-bind" => "::producer.whatsapp_phone"}
+ %p.word-wrap{"ng-if" => "::producer.whatsapp_phone"}
+ %a{"ng-href" => "{{::producer.whatsapp_url}}", target: "_blank"}
+ %img{ src: image_pack_path("social-logos/whatsapp.svg") }
+ %span{"ng-bind" => "::producer.whatsapp_phone"}
- %p.word-wrap{"ng-if" => "::producer.email_address"}
- %a{"ng-href" => "{{::producer.email_address | stripUrl}}", target: "_blank", mailto: true}
- %span.obfuscatedEmail.email{"ng-bind" => "::producer.email_address | stripUrl"}
+ %p.word-wrap{"ng-if" => "::producer.email_address"}
+ %a{"ng-href" => "{{::producer.email_address | stripUrl}}", target: "_blank", mailto: true}
+ %span.obfuscatedEmail.email{"ng-bind" => "::producer.email_address | stripUrl"}
- %p.word-wrap{"ng-if" => "::producer.website"}
- %a{"ng-href" => "http://{{::producer.website | stripUrl}}", target: "_blank" }
- %span{"ng-bind" => "::producer.website | stripUrl"}
+ %p.word-wrap{"ng-if" => "::producer.website"}
+ %a{"ng-href" => "http://{{::producer.website | stripUrl}}", target: "_blank" }
+ %span{"ng-bind" => "::producer.website | stripUrl"}
- %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"}
- %label
- = t :producers_social
- .follow-icons
- %span{"ng-if" => "::producer.twitter"}
- %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"}
- %i.ofn-i_041-twitter
+ %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"}
+ %label
+ = t :producers_social
+ .follow-icons
+ %span{"ng-if" => "::producer.twitter"}
+ %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"}
+ %i.ofn-i_041-twitter
- %span{"ng-if" => "::producer.facebook"}
- %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"}
- %i.ofn-i_044-facebook
+ %span{"ng-if" => "::producer.facebook"}
+ %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"}
+ %i.ofn-i_044-facebook
- %span{"ng-if" => "::producer.linkedin"}
- %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"}
- %i.ofn-i_042-linkedin
+ %span{"ng-if" => "::producer.linkedin"}
+ %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"}
+ %i.ofn-i_042-linkedin
- %span{"ng-if" => "::producer.instagram"}
- %a{"ng-href" => "http://instagram.com/{{::producer.instagram}}", target: "_blank"}
- %i.ofn-i_043-instagram
+ %span{"ng-if" => "::producer.instagram"}
+ %a{"ng-href" => "http://instagram.com/{{::producer.instagram}}", target: "_blank"}
+ %i.ofn-i_043-instagram
-.row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs && !shopfront_loading"}
- .columns.small-12{"ng-if" => "producer.hubs.length > 0"}
- .row
- .columns.small-12.fat
- %div{"ng-if" => "::producer.name"}
- %label
- = t :producers_buy_at_html, enterprise: ''.html_safe
- %div.show-for-medium-up{"ng-if" => "::!producer.name"}
-
- .row.cta-container
- .columns.small-12
- %a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'",
- "ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined }}",
- "ng-class" => "::{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
- %i.ofn-i_068-shop-reversed{"ng-if" => "::hub.active"}
- %i.ofn-i_068-shop-reversed{"ng-if" => "::!hub.active"}
- .hub-name{"ng-bind" => "::hub.name"}
- .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"}
+ .row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs && !shopfront_loading"}
+ .columns.small-12{"ng-if" => "producer.hubs.length > 0"}
+ .row
+ .columns.small-12.fat
+ %div{"ng-if" => "::producer.name"}
+ %label
+ = t :producers_buy_at_html, enterprise: ''.html_safe
+ %div.show-for-medium-up{"ng-if" => "::!producer.name"}
+
+ .row.cta-container
+ .columns.small-12
+ %a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'",
+ "ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined }}",
+ "ng-class" => "::{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
+ %i.ofn-i_068-shop-reversed{"ng-if" => "::hub.active"}
+ %i.ofn-i_068-shop-reversed{"ng-if" => "::!hub.active"}
+ .hub-name{"ng-bind" => "::hub.name"}
+ .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"}
diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml
index 4d6723892d..a4f1f3a3e4 100644
--- a/app/views/shared/_footer.html.haml
+++ b/app/views/shared/_footer.html.haml
@@ -1,115 +1,119 @@
%footer
.footer-global
- .row
- .small-12.columns.text-center
- .logo
- %img{src: image_pack_path("logo-white-notext.png") }
- .row
- .small-12.medium-8.medium-offset-2.columns.text-center
- .alert-box
- = render 'shared/register_call'
+ = cache_with_locale "global" do
+ .row
+ .small-12.columns.text-center
+ .logo
+ %img{src: image_pack_path("logo-white-notext.png") }
+ .row
+ .small-12.medium-8.medium-offset-2.columns.text-center
+ .alert-box
+ = render 'shared/register_call'
.footer-local
- .row
- .small-12.medium-2.medium-offset-2.columns.text-center
- %p.secure-icon
- %i.ofn-i_017-locked
- .small-12.medium-6.columns.text-center
- %p.text-big.secure-text
- = t '.footer_secure'
- %p.secure-text
- = t '.footer_secure_text'
- .small-12.medium-2.columns
+ = cache_with_locale "local" do
+ .row
+ .small-12.medium-2.medium-offset-2.columns.text-center
+ %p.secure-icon
+ %i.ofn-i_017-locked
+ .small-12.medium-6.columns.text-center
+ %p.text-big.secure-text
+ = t '.footer_secure'
+ %p.secure-text
+ = t '.footer_secure_text'
+ .small-12.medium-2.columns
- .row
- .small-12.medium-8.medium-offset-2.columns.text-center
- %hr.hr-light
- %br
+ .row
+ .small-12.medium-8.medium-offset-2.columns.text-center
+ %hr.hr-light
+ %br
- .row
- .small-6.medium-3.medium-offset-2.columns.text-left
- // This is the instance-managed set of links:
- %h4
- = t '.footer_contact_headline'
- - if show_social_icons?
- %p.social-icons
- - if ContentConfig.footer_facebook_url.present?
- %a{href: ContentConfig.footer_facebook_url}
- %i.ofn-i_044-facebook
- - if ContentConfig.footer_twitter_url.present?
- %a{href: ContentConfig.footer_twitter_url}
- %i.ofn-i_041-twitter
- - if ContentConfig.footer_instagram_url.present?
- %a{href: ContentConfig.footer_instagram_url}
- %i.ofn-i_043-instagram
- - if ContentConfig.footer_linkedin_url.present?
- %a{href: ContentConfig.footer_linkedin_url}
- %i.ofn-i_042-linkedin
- - if ContentConfig.footer_googleplus_url.present?
- %a{href: ContentConfig.footer_googleplus_url}
- %i.ofn-i_046-g
- - if ContentConfig.footer_pinterest_url.present?
- %a{href: ContentConfig.footer_pinterest_url}
- %i.ofn-i_045-pintrest
- - if ContentConfig.footer_email.present?
+ = cache_with_locale ContentConfig.cache_key do
+ .row
+ .small-6.medium-3.medium-offset-2.columns.text-left
+ // This is the instance-managed set of links:
+ %h4
+ = t '.footer_contact_headline'
+ - if show_social_icons?
+ %p.social-icons
+ - if ContentConfig.footer_facebook_url.present?
+ %a{href: ContentConfig.footer_facebook_url}
+ %i.ofn-i_044-facebook
+ - if ContentConfig.footer_twitter_url.present?
+ %a{href: ContentConfig.footer_twitter_url}
+ %i.ofn-i_041-twitter
+ - if ContentConfig.footer_instagram_url.present?
+ %a{href: ContentConfig.footer_instagram_url}
+ %i.ofn-i_043-instagram
+ - if ContentConfig.footer_linkedin_url.present?
+ %a{href: ContentConfig.footer_linkedin_url}
+ %i.ofn-i_042-linkedin
+ - if ContentConfig.footer_googleplus_url.present?
+ %a{href: ContentConfig.footer_googleplus_url}
+ %i.ofn-i_046-g
+ - if ContentConfig.footer_pinterest_url.present?
+ %a{href: ContentConfig.footer_pinterest_url}
+ %i.ofn-i_045-pintrest
+ - if ContentConfig.footer_email.present?
+ %p
+ %a{href: ContentConfig.footer_email.reverse, mailto: true, target: '_blank'}
+ = t '.footer_contact_email'
+ = render_markdown(ContentConfig.footer_links_md).html_safe
+
+
+ .small-6.medium-3.columns.text-left
+ %h4
+ = t '.footer_nav_headline'
%p
- %a{href: ContentConfig.footer_email.reverse, mailto: true, target: '_blank'}
- = t '.footer_contact_email'
- = render_markdown(ContentConfig.footer_links_md).html_safe
+ %a{href: "/shops"}
+ = t :label_shops
+ %p
+ %a{href: "/map"}
+ = t :label_map
+ %p
+ %a{href: "/producers"}
+ = t :label_producers
+ %p
+ %a{href: "/groups"}
+ = t :label_groups
+ %p
+ %a{href: ContentConfig.footer_about_url}
+ = t :label_about
+ .small-12.medium-2.columns.text-left
+ %h4
+ = t '.footer_join_headline'
+ %p
+ = t '.footer_join_body'
+ %a{href: "/sell"}
+ = t '.footer_join_cta'
- .small-6.medium-3.columns.text-left
- %h4
- = t '.footer_nav_headline'
- %p
- %a{href: "/shops"}
- = t :label_shops
- %p
- %a{href: "/map"}
- = t :label_map
- %p
- %a{href: "/producers"}
- = t :label_producers
- %p
- %a{href: "/groups"}
- = t :label_groups
- %p
- %a{href: ContentConfig.footer_about_url}
- = t :label_about
+ .medium-2.columns.text-center
+ / Placeholder
- .small-12.medium-2.columns.text-left
- %h4
- = t '.footer_join_headline'
- %p
- = t '.footer_join_body'
- %a{href: "/sell"}
- = t '.footer_join_cta'
+ .row
+ .small-12.medium-8.medium-offset-2.columns.text-center
+ %hr.hr-light
+ %br
- .medium-2.columns.text-center
- / Placeholder
-
- .row
- .small-12.medium-8.medium-offset-2.columns.text-center
- %hr.hr-light
- %br
-
- .row.legal
- .small-12.medium-3.medium-offset-2.columns.text-left
- %a{href: main_app.root_path}
- %img{src: ContentConfig.url_for(:footer_logo), width: "220"}
- .small-12.medium-5.columns.text-left
- %p.text-small
- = t '.footer_legal_call'
- = link_to_platform_terms
- |
- = t '.footer_legal_visit'
- %a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} GitHub
- %p.text-small
- = t('.footer_legal_text_html', content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)' ))
- %p.text-small
- - if Spree::Config.privacy_policy_url.present?
- = t('.footer_data_text_with_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe, privacy_policy: privacy_policy_link.html_safe)
- - else
- = t('.footer_data_text_without_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe)
- .medium-2.columns.text-center
- / Placeholder
+ = cache_with_locale [ContentConfig.cache_key, TermsOfServiceFile.current_url, Spree::Config.privacy_policy_url] do
+ .row.legal
+ .small-12.medium-3.medium-offset-2.columns.text-left
+ %a{href: main_app.root_path}
+ %img{src: ContentConfig.url_for(:footer_logo), width: "220"}
+ .small-12.medium-5.columns.text-left
+ %p.text-small
+ = t '.footer_legal_call'
+ = link_to_platform_terms
+ |
+ = t '.footer_legal_visit'
+ %a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} GitHub
+ %p.text-small
+ = t('.footer_legal_text_html', content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)' ))
+ %p.text-small
+ - if Spree::Config.privacy_policy_url.present?
+ = t('.footer_data_text_with_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe, privacy_policy: privacy_policy_link.html_safe)
+ - else
+ = t('.footer_data_text_without_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe)
+ .medium-2.columns.text-center
+ / Placeholder
diff --git a/app/views/shared/_register_call.html.haml b/app/views/shared/_register_call.html.haml
index 54c55af680..1f04ef2521 100644
--- a/app/views/shared/_register_call.html.haml
+++ b/app/views/shared/_register_call.html.haml
@@ -1,12 +1,13 @@
-.alert-cta
- %h6
- -# Please forgive the hard-coded link:
- -# The more elegant 'registration_path' resolves to /signup due to spree_auth_device > config > routes.rb
- -# This is one of several possible fixes. Long-term, we'd like to bring the accounts page into OFN.
- -# View the discussion here: https://github.com/openfoodfoundation/openfoodnetwork/pull/3174
- %a{href: "/register", target: "_blank"}
- = t '.selling_on_ofn'
-
- %strong
- = t '.register'
- %i.ofn-i_054-point-right
+= cache_with_locale do
+ .alert-cta
+ %h6
+ -# Please forgive the hard-coded link:
+ -# The more elegant 'registration_path' resolves to /signup due to spree_auth_device > config > routes.rb
+ -# This is one of several possible fixes. Long-term, we'd like to bring the accounts page into OFN.
+ -# View the discussion here: https://github.com/openfoodfoundation/openfoodnetwork/pull/3174
+ %a{href: "/register", target: "_blank"}
+ = t '.selling_on_ofn'
+
+ %strong
+ = t '.register'
+ %i.ofn-i_054-point-right
diff --git a/app/views/shared/menu/_cart.html.haml b/app/views/shared/menu/_cart.html.haml
index 728b0c1daa..eb5fe0a563 100644
--- a/app/views/shared/menu/_cart.html.haml
+++ b/app/views/shared/menu/_cart.html.haml
@@ -1,8 +1,9 @@
-%span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
- %a#cart.icon{"ng-click" => "toggleCartSidebar()"}
- %span
- = t '.cart'
- %span.count
- %img{ src: image_pack_path("menu/icn-cart.svg") }
+= cache_with_locale do
+ %span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
+ %a#cart.icon{"ng-click" => "toggleCartSidebar()"}
%span
- {{ Cart.total_item_count() }}
+ = t '.cart'
+ %span.count
+ %img{ src: image_pack_path("menu/icn-cart.svg") }
+ %span
+ {{ Cart.total_item_count() }}
diff --git a/app/views/shared/menu/_cart_sidebar.html.haml b/app/views/shared/menu/_cart_sidebar.html.haml
index 93ea539fdc..423d4585cd 100644
--- a/app/views/shared/menu/_cart_sidebar.html.haml
+++ b/app/views/shared/menu/_cart_sidebar.html.haml
@@ -1,38 +1,40 @@
.expanding-sidebar.cart-sidebar{ng: {controller: 'CartCtrl', class: "{'shown': showCartSidebar}"}}
.background{ng: {click: 'toggleCartSidebar()'}}
.sidebar
- .cart-header
- %span.title{"ng-show" => "Cart.line_items.length == 1"}
- = t('.items_in_cart_singular', num: "{{ Cart.total_item_count() }}")
- %span.title{"ng-show" => "Cart.line_items.length > 1"}
- = t('.items_in_cart_plural', num: "{{ Cart.total_item_count() }}")
- %a.close{ng: {click: 'toggleCartSidebar()'}}
- = t('.close')
- %i.ofn-i_009-close
+ = cache_with_locale "cart-header" do
+ .cart-header
+ %span.title{"ng-show" => "Cart.line_items.length == 1"}
+ = t('.items_in_cart_singular', num: "{{ Cart.total_item_count() }}")
+ %span.title{"ng-show" => "Cart.line_items.length > 1"}
+ = t('.items_in_cart_plural', num: "{{ Cart.total_item_count() }}")
+ %a.close{ng: {click: 'toggleCartSidebar()'}}
+ = t('.close')
+ %i.ofn-i_009-close
.cart-content
- %table
- %tr.product-cart{"ng-repeat" => "line_item in Cart.line_items", "id" => "cart-variant-{{ line_item.variant.id }}"}
- %td.image
- %img{'ng-src' => '{{ line_item.variant.thumb_url }}'}
- %td
- %span {{ line_item.variant.extended_name | truncate: max_characters }}
- %br
- %span.options-text {{ line_item.variant.options_text | truncate: max_characters }}
- %td.text-right
- %span.quantity {{ line_item.quantity }}
- %td
- .total-price.text-right {{ line_item.total_price | localizeCurrency }}
- .unit-price
- %div{:style => "margin-right: 5px"}
- %question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
- "question-mark-with-tooltip-append-to-body" => "true",
- "question-mark-with-tooltip-placement" => "top",
- "question-mark-with-tooltip-animation" => true,
- key: "'js.shopfront.unit_price_tooltip'",
- context: "'cart-sidebar'"}
- .options-text
- {{ line_item.variant.unit_price_price | localizeCurrency }} / {{ line_item.variant.unit_price_unit }}
+ = cache_with_locale "cart-table" do
+ %table
+ %tr.product-cart{"ng-repeat" => "line_item in Cart.line_items", "id" => "cart-variant-{{ line_item.variant.id }}"}
+ %td.image
+ %img{'ng-src' => '{{ line_item.variant.thumb_url }}'}
+ %td
+ %span {{ line_item.variant.extended_name | truncate: max_characters }}
+ %br
+ %span.options-text {{ line_item.variant.options_text | truncate: max_characters }}
+ %td.text-right
+ %span.quantity {{ line_item.quantity }}
+ %td
+ .total-price.text-right {{ line_item.total_price | localizeCurrency }}
+ .unit-price
+ %div{:style => "margin-right: 5px"}
+ %question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
+ "question-mark-with-tooltip-append-to-body" => "true",
+ "question-mark-with-tooltip-placement" => "top",
+ "question-mark-with-tooltip-animation" => true,
+ key: "'js.shopfront.unit_price_tooltip'",
+ context: "'cart-sidebar'"}
+ .options-text
+ {{ line_item.variant.unit_price_price | localizeCurrency }} / {{ line_item.variant.unit_price_unit }}
.cart-empty{"ng-show" => "Cart.line_items.length == 0"}
%p
@@ -42,15 +44,16 @@
= t('.take_me_shopping')
.sidebar-footer{"ng-show" => "Cart.line_items.length > 0"}
- %p.cart-total
- %strong
- = t 'total'
- {{ Cart.total() | localizeCurrency }}
+ = cache_with_locale "cart-footer" do
+ %p.cart-total
+ %strong
+ = t 'total'
+ {{ Cart.total() | localizeCurrency }}
- %div.fullwidth
- %a.edit-cart.button.large.dark.left{href: main_app.cart_path, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }"}
- %div{ ng: { if: "Cart.dirty" } }= t(:cart_updating)
- %div{ ng: { if: "!Cart.dirty && Cart.empty()" } }= t(:cart_empty)
- %div{ ng: { if: "!Cart.dirty && !Cart.empty()" } }= t('.edit_cart')
- %a.checkout.button.large.bright.right{href: main_app.checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"}
- = t '.checkout'
+ %div.fullwidth
+ %a.edit-cart.button.large.dark.left{href: main_app.cart_path, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }"}
+ %div{ ng: { if: "Cart.dirty" } }= t(:cart_updating)
+ %div{ ng: { if: "!Cart.dirty && Cart.empty()" } }= t(:cart_empty)
+ %div{ ng: { if: "!Cart.dirty && !Cart.empty()" } }= t('.edit_cart')
+ %a.checkout.button.large.bright.right{href: main_app.checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"}
+ = t '.checkout'
diff --git a/app/views/shared/menu/_language_selector.html.haml b/app/views/shared/menu/_language_selector.html.haml
index b35a1a25b0..c379ff02ec 100644
--- a/app/views/shared/menu/_language_selector.html.haml
+++ b/app/views/shared/menu/_language_selector.html.haml
@@ -1,8 +1,9 @@
-%li.language-switcher.has-dropdown.not-click
- %a{href: '#', class: "top-bar--menu-item-with-icon"}
- %i.ofn-i_071-globe
- %span= t 'language_name'
- %ul.dropdown
- - OpenFoodNetwork::I18nConfig.locale_options.each do |l|
- %li
- = link_to t('language_name', locale: l), main_app.locale_path(l.to_s)
+= cache_with_locale OpenFoodNetwork::I18nConfig.locale_options do
+ %li.language-switcher.has-dropdown.not-click
+ %a{href: '#', class: "top-bar--menu-item-with-icon"}
+ %i.ofn-i_071-globe
+ %span= t 'language_name'
+ %ul.dropdown
+ - OpenFoodNetwork::I18nConfig.locale_options.each do |l|
+ %li
+ = link_to t('language_name', locale: l), main_app.locale_path(l.to_s)
diff --git a/app/views/shared/menu/_large_menu.html.haml b/app/views/shared/menu/_large_menu.html.haml
index b9a6cb3b7a..47e652fd89 100644
--- a/app/views/shared/menu/_large_menu.html.haml
+++ b/app/views/shared/menu/_large_menu.html.haml
@@ -1,27 +1,30 @@
%nav.top-bar.show-for-large-up
%section.top-bar-section
%ul.nav-logo
- %li.ofn-logo
- %a{href: main_logo_link(@white_label_distributor)}
- - if @white_label_logo&.variable?
- = image_tag @white_label_distributor.white_label_logo_url(:default)
- - else
- %img{src: ContentConfig.url_for(:logo)}
- %li.powered-by
- %img{src: '/favicon.ico'}
- %span
- = t 'powered_by'
- %a{href: '/'}
- = t 'title'
+ = cache_with_locale [@white_label_distributor, ContentConfig.cache_key] do
+ %li.ofn-logo
+ %a{href: main_logo_link(@white_label_distributor)}
+ - if @white_label_logo&.variable?
+ = image_tag @white_label_distributor.white_label_logo_url(:default)
+ - else
+ %img{src: ContentConfig.url_for(:logo)}
+ %li.powered-by
+ %img{src: '/favicon.ico'}
+ %span
+ = t 'powered_by'
+ %a{href: '/'}
+ = t 'title'
+
- unless @hide_ofn_navigation
- %ul.nav-main-menu
- - [*1..7].each do |menu_number|
- - menu_name = "menu_#{menu_number}"
- - if ContentConfig[menu_name].present?
- %li
- %a{href: t("#{menu_name}_url") }
- %span.nav-primary
- = t "#{menu_name}_title"
+ = cache_with_locale ContentConfig.cache_key do
+ %ul.nav-main-menu
+ - [*1..7].each do |menu_number|
+ - menu_name = "menu_#{menu_number}"
+ - if ContentConfig[menu_name].present?
+ %li
+ %a{href: t("#{menu_name}_url") }
+ %span.nav-primary
+ = t "#{menu_name}_title"
%ul.nav-icons-menu
- if OpenFoodNetwork::I18nConfig.selectable_locales.count > 1
= render 'shared/menu/language_selector'
@@ -31,11 +34,12 @@
- else
= render 'shared/menu/signed_in'
- %li.current_hub{"ng-controller" => "CurrentHubCtrl", "ng-show" => "CurrentHub.hub.id", "ng-cloak" => true}
- %a{href: main_app.shop_path}
- %span{ class: "top-bar--current-hub-prefix" }
- = t 'label_shopping'
- = '@'
- %span{ class: "top-bar--current-hub-name" } {{ CurrentHub.hub.name | truncate:25 }}
- %li.cart{"ng-cloak" => true}
- = render partial: "shared/menu/cart"
+ = cache_with_locale "cart" do
+ %li.current_hub{"ng-controller" => "CurrentHubCtrl", "ng-show" => "CurrentHub.hub.id", "ng-cloak" => true}
+ %a{href: main_app.shop_path}
+ %span{ class: "top-bar--current-hub-prefix" }
+ = t 'label_shopping'
+ = '@'
+ %span{ class: "top-bar--current-hub-name" } {{ CurrentHub.hub.name | truncate:25 }}
+ %li.cart{"ng-cloak" => true}
+ = render partial: "shared/menu/cart"
diff --git a/app/views/shared/menu/_mobile_menu.html.haml b/app/views/shared/menu/_mobile_menu.html.haml
index 75540b5ed9..7288b2a78e 100644
--- a/app/views/shared/menu/_mobile_menu.html.haml
+++ b/app/views/shared/menu/_mobile_menu.html.haml
@@ -1,25 +1,26 @@
-%nav.tab-bar.show-for-medium-down
- %section.left
- %a.left-off-canvas-toggle.menu-icon
- = image_pack_tag "menu/btn-menu-mobile.png"
+= cache_with_locale [@white_label_distributor, ContentConfig.cache_key] do
+ %nav.tab-bar.show-for-medium-down
+ %section.left
+ %a.left-off-canvas-toggle.menu-icon
+ = image_pack_tag "menu/btn-menu-mobile.png"
- %section.left
- .ofn-logo
- %a{href: main_app.root_path}
- - if @white_label_logo&.variable?
- = image_tag @white_label_distributor.white_label_logo_url(:mobile)
- - else
- %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
+ %section.left
+ .ofn-logo
+ %a{href: main_app.root_path}
+ - if @white_label_logo&.variable?
+ = image_tag @white_label_distributor.white_label_logo_url(:mobile)
+ - else
+ %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
- %section.right{"ng-cloak" => true}
- %span.cart-span{"ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
- %a.icon{ng: {click: 'toggleCartSidebar()'}}
- %span
- = t '.cart'
- %span.count
- = image_pack_tag "menu/icn-cart.svg"
- %span
- {{ Cart.total_item_count() }}
+ %section.right{"ng-cloak" => true}
+ %span.cart-span{"ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}
+ %a.icon{ng: {click: 'toggleCartSidebar()'}}
+ %span
+ = t '.cart'
+ %span.count
+ = image_pack_tag "menu/icn-cart.svg"
+ %span
+ {{ Cart.total_item_count() }}
- %a{href: main_app.shop_path}
- {{ CurrentHub.hub.name }}
+ %a{href: main_app.shop_path}
+ {{ CurrentHub.hub.name }}
diff --git a/app/views/shared/menu/_offcanvas_menu.html.haml b/app/views/shared/menu/_offcanvas_menu.html.haml
index ca8b907cf7..1bb67b8f77 100644
--- a/app/views/shared/menu/_offcanvas_menu.html.haml
+++ b/app/views/shared/menu/_offcanvas_menu.html.haml
@@ -1,16 +1,18 @@
%aside.left-off-canvas-menu.show-for-medium-down{ ng: { controller: "OffcanvasCtrl" } }
%ul.off-canvas-list
- %li.ofn-logo
- %a{href: main_app.root_path}
- %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
- - [*1..7].each do |menu_number|
- - menu_name = "menu_#{menu_number}"
- - if ContentConfig[menu_name].present?
- %li.li-menu
- %a{href: t("#{menu_name}_url") }
- %span.nav-primary
- %i{class: ContentConfig["#{menu_name}_icon_name"]}
- = t "#{menu_name}_title"
+ = cache_with_locale ContentConfig.cache_key do
+ %li.ofn-logo
+ %a{href: main_app.root_path}
+ %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
+ - [*1..7].each do |menu_number|
+ - menu_name = "menu_#{menu_number}"
+ - if ContentConfig[menu_name].present?
+ %li.li-menu
+ %a{href: t("#{menu_name}_url") }
+ %span.nav-primary
+ %i{class: ContentConfig["#{menu_name}_icon_name"]}
+ = t "#{menu_name}_title"
+
- if OpenFoodNetwork::I18nConfig.selectable_locales.count > 1
%li.language-switcher.li-menu
%a
diff --git a/app/views/shared/menu/_signed_out.html.haml b/app/views/shared/menu/_signed_out.html.haml
index 570c707b31..d6913ec554 100644
--- a/app/views/shared/menu/_signed_out.html.haml
+++ b/app/views/shared/menu/_signed_out.html.haml
@@ -1,5 +1,6 @@
-%li#login-link{ "data-controller": "login-modal" }
- %a{"auth": "login", "data-action": "click->login-modal#call" }
- %img{ src: image_pack_path("menu/icn-login.svg") }
- %span
- = t 'label_login'
+= cache_with_locale do
+ %li#login-link{ "data-controller": "login-modal" }
+ %a{"auth": "login", "data-action": "click->login-modal#call" }
+ %img{ src: image_pack_path("menu/icn-login.svg") }
+ %span
+ = t 'label_login'
diff --git a/app/views/shop/products/_applied_filters_feedback.haml b/app/views/shop/products/_applied_filters_feedback.haml
index 45730f39cf..2eb647b3b5 100644
--- a/app/views/shop/products/_applied_filters_feedback.haml
+++ b/app/views/shop/products/_applied_filters_feedback.haml
@@ -1,9 +1,10 @@
-%span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" }
- = t :products_filters_in
+= cache_with_locale do
+ %span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" }
+ = t :products_filters_in
-%span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'}
+ %span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'}
-%span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" }
- = t :products_and
+ %span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" }
+ = t :products_and
-%span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'}
+ %span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'}
diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml
index f0e69d1985..869b215c60 100644
--- a/app/views/shop/products/_filters.html.haml
+++ b/app/views/shop/products/_filters.html.haml
@@ -1,5 +1,6 @@
-.filter-shopfront.taxon-selectors{ng: {show: 'supplied_taxons != null'}}
- %filter-selector{ 'selector-set' => "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
+= cache_with_locale do
+ .filter-shopfront.taxon-selectors{ng: {show: 'supplied_taxons != null'}}
+ %filter-selector{ 'selector-set' => "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
-.filter-shopfront.property-selectors{ng: {show: 'supplied_properties != null'}}
- %filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
+ .filter-shopfront.property-selectors{ng: {show: 'supplied_properties != null'}}
+ %filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml
index 18471ead25..e5a51a50cc 100644
--- a/app/views/shop/products/_form.html.haml
+++ b/app/views/shop/products/_form.html.haml
@@ -1,51 +1,52 @@
-%form{action: main_app.cart_path}
- %products{"ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true }
+= cache_with_locale do
+ %form{action: main_app.cart_path}
+ %products{"ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true }
- = render partial: "shop/products/searchbar"
+ = render partial: "shop/products/searchbar"
- .row
- .footer-pad.small-12.columns.product-listing
- .row.full
- .medium-12.large-9.columns.full
- = render partial: "shop/products/search_feedback"
+ .row
+ .footer-pad.small-12.columns.product-listing
+ .row.full
+ .medium-12.large-9.columns.full
+ = render partial: "shop/products/search_feedback"
- %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading', "infinite-scroll-immediate-check": "false" }
- %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
- = render "shop/products/summary"
- .shop-variants
- .variants.row{"ng-controller": "ShopVariantCtrl", variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
- = render "shop/products/shop_variant"
- %product{"ng-show" => "Products.loading"}
- .summary
- .small-12.columns.text-center
- = t :products_loading
- .row.full
- .small-12.columns.text-center
- = render partial: "components/spinner"
+ %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading', "infinite-scroll-immediate-check": "false" }
+ %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
+ = render "shop/products/summary"
+ .shop-variants
+ .variants.row{"ng-controller": "ShopVariantCtrl", variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
+ = render "shop/products/shop_variant"
+ %product{"ng-show" => "Products.loading"}
+ .summary
+ .small-12.columns.text-center
+ = t :products_loading
+ .row.full
+ .small-12.columns.text-center
+ = render partial: "components/spinner"
- .hide-for-medium-down.large-1.columns
- -# Space between products and filters
-
+ .hide-for-medium-down.large-1.columns
+ -# Space between products and filters
+
- .sticky-shop-filters-container.thin-scroll-bar.hide-for-medium-down.large-2.columns
- %h5.filter-header
- = t(:products_filter_by)
- %span{ng: {show: 'filtersCount()' }}
- = "({{ filtersCount() }} #{t(:products_filter_selected)})"
- = render partial: "shop/products/filters"
-
- .expanding-sidebar.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}}
- .background{ng: {click: 'toggleFilterSidebar()'}}
- .sidebar
- %h5
+ .sticky-shop-filters-container.thin-scroll-bar.hide-for-medium-down.large-2.columns
+ %h5.filter-header
= t(:products_filter_by)
%span{ng: {show: 'filtersCount()' }}
= "({{ filtersCount() }} #{t(:products_filter_selected)})"
-
= render partial: "shop/products/filters"
- .sidebar-footer
- %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}}
- = t(:products_filter_clear)
- %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}}
- = t(:products_filter_done)
+ .expanding-sidebar.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}}
+ .background{ng: {click: 'toggleFilterSidebar()'}}
+ .sidebar
+ %h5
+ = t(:products_filter_by)
+ %span{ng: {show: 'filtersCount()' }}
+ = "({{ filtersCount() }} #{t(:products_filter_selected)})"
+
+ = render partial: "shop/products/filters"
+
+ .sidebar-footer
+ %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}}
+ = t(:products_filter_clear)
+ %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}}
+ = t(:products_filter_done)
diff --git a/app/views/shop/products/_search_feedback.haml b/app/views/shop/products/_search_feedback.haml
index ea6b0c7f22..3245c6bb50 100644
--- a/app/views/shop/products/_search_feedback.haml
+++ b/app/views/shop/products/_search_feedback.haml
@@ -1,24 +1,25 @@
-.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" }
- .small-12.columns
- .alert-box.search-alert.ng-scope
- %div{"ng-show" => "Products.products.length > 0"}
+= cache_with_locale do
+ .row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" }
+ .small-12.columns
+ .alert-box.search-alert.ng-scope
+ %div{"ng-show" => "Products.products.length > 0"}
- %a.clear-all.right{"ng-click" => "clearAll()"}
- = t :products_clear
- %i.ofn-i_009-close
+ %a.clear-all.right{"ng-click" => "clearAll()"}
+ = t :products_clear
+ %i.ofn-i_009-close
- %span.filter-label
- = t :products_results_for
- %span{ ng: { hide: "!query"} }
- %span.applied-search
- {{ query }}
- = render partial: 'shop/products/applied_filters_feedback'
+ %span.filter-label
+ = t :products_results_for
+ %span{ ng: { hide: "!query"} }
+ %span.applied-search
+ {{ query }}
+ = render partial: 'shop/products/applied_filters_feedback'
- %div.no-results-bar{"ng-show" => "Products.products.length == 0 && !Products.loading"}
- .row.summary
- .small-12.columns
- %p.no-results
- = t :products_no_results_html, query: "{{query}}".html_safe
- = render partial: 'shop/products/applied_filters_feedback'
- %button.clear-search{type: 'button', ng: {click: 'clearAll()'}}
- = t :products_clear_search
+ %div.no-results-bar{"ng-show" => "Products.products.length == 0 && !Products.loading"}
+ .row.summary
+ .small-12.columns
+ %p.no-results
+ = t :products_no_results_html, query: "{{query}}".html_safe
+ = render partial: 'shop/products/applied_filters_feedback'
+ %button.clear-search{type: 'button', ng: {click: 'clearAll()'}}
+ = t :products_clear_search
diff --git a/app/views/shop/products/_searchbar.haml b/app/views/shop/products/_searchbar.haml
index a17db1e322..b7e6f98d57 100644
--- a/app/views/shop/products/_searchbar.haml
+++ b/app/views/shop/products/_searchbar.haml
@@ -1,17 +1,18 @@
-.shop-searchbar
- .row
- .small-12.large-5.columns.flex
- %div.search-wrap
- %input#search.text{"ng-model" => "query",
- type: 'search',
- placeholder: t(:products_search),
- "ng-debounce" => "200",
- "disable-enter-with-blur" => true}
- %a.clear{type: 'button', ng: {show: 'query', click: 'clearQuery()'}, 'focus-search' => true}
- = image_pack_tag "icn-close.png"
+= cache_with_locale do
+ .shop-searchbar
+ .row
+ .small-12.large-5.columns.flex
+ %div.search-wrap
+ %input#search.text{"ng-model" => "query",
+ type: 'search',
+ placeholder: t(:products_search),
+ "ng-debounce" => "200",
+ "disable-enter-with-blur" => true}
+ %a.clear{type: 'button', ng: {show: 'query', click: 'clearQuery()'}, 'focus-search' => true}
+ = image_pack_tag "icn-close.png"
- .hide-for-large-up
- %button{type: 'button', ng: {click: 'toggleFilterSidebar()'}}
- = t(:products_filter_heading)
- %span{ng: {show: 'filtersCount()' }}
- ({{ filtersCount() }})
+ .hide-for-large-up
+ %button{type: 'button', ng: {click: 'toggleFilterSidebar()'}}
+ = t(:products_filter_heading)
+ %span{ng: {show: 'filtersCount()' }}
+ ({{ filtersCount() }})
diff --git a/app/views/shop/products/_shop_variant.html.haml b/app/views/shop/products/_shop_variant.html.haml
index 63d5f05090..b6e91c8c2f 100644
--- a/app/views/shop/products/_shop_variant.html.haml
+++ b/app/views/shop/products/_shop_variant.html.haml
@@ -1,22 +1,23 @@
-.small-4.medium-4.large-5.columns.variant-name
- .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }}
- .variant-unit {{ ::variant.unit_to_display }}
-.small-3.medium-3.large-2.columns.variant-price
- %price-breakdown{"price-breakdown" => "_", variant: "variant",
- "price-breakdown-append-to-body" => "true",
- "price-breakdown-placement" => "bottom",
- "price-breakdown-animation" => true}
- {{ variant.price_with_fees | localizeCurrency }}
- .unit-price.variant-unit-price
- %question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
- "question-mark-with-tooltip-append-to-body" => "true",
- "question-mark-with-tooltip-placement" => "top",
- "question-mark-with-tooltip-animation" => true,
- key: "'js.shopfront.unit_price_tooltip'"}
- {{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }}
+= cache_with_locale do
+ .small-4.medium-4.large-5.columns.variant-name
+ .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }}
+ .variant-unit {{ ::variant.unit_to_display }}
+ .small-3.medium-3.large-2.columns.variant-price
+ %price-breakdown{"price-breakdown" => "_", variant: "variant",
+ "price-breakdown-append-to-body" => "true",
+ "price-breakdown-placement" => "bottom",
+ "price-breakdown-animation" => true}
+ {{ variant.price_with_fees | localizeCurrency }}
+ .unit-price.variant-unit-price
+ %question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
+ "question-mark-with-tooltip-append-to-body" => "true",
+ "question-mark-with-tooltip-placement" => "top",
+ "question-mark-with-tooltip-animation" => true,
+ key: "'js.shopfront.unit_price_tooltip'"}
+ {{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }}
-.medium-2.large-2.columns.total-price
- %span{"ng-class" => "{filled: variant.line_item.total_price}"}
- {{ variant.line_item.total_price | localizeCurrency }}
-= render partial: "shop/products/shop_variant_no_group_buy"
-= render partial: "shop/products/shop_variant_with_group_buy"
+ .medium-2.large-2.columns.total-price
+ %span{"ng-class" => "{filled: variant.line_item.total_price}"}
+ {{ variant.line_item.total_price | localizeCurrency }}
+ = render partial: "shop/products/shop_variant_no_group_buy"
+ = render partial: "shop/products/shop_variant_with_group_buy"
diff --git a/app/views/shop/products/_shop_variant_no_group_buy.html.haml b/app/views/shop/products/_shop_variant_no_group_buy.html.haml
index 7885a2e6b4..c2f3e4131c 100644
--- a/app/views/shop/products/_shop_variant_no_group_buy.html.haml
+++ b/app/views/shop/products/_shop_variant_no_group_buy.html.haml
@@ -1,22 +1,23 @@
-.small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::!variant.product.group_buy"}
+= cache_with_locale do
+ .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::!variant.product.group_buy"}
- .variant-quantity-inputs{ng: {if: "variant.line_item.quantity == 0"}}
- %button.add-variant{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}}
- {{ "js.shopfront.variant.add_to_cart" | t }}
+ .variant-quantity-inputs{ng: {if: "variant.line_item.quantity == 0"}}
+ %button.add-variant{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}}
+ {{ "js.shopfront.variant.add_to_cart" | t }}
- .variant-quantity-inputs{ng: {if: "variant.line_item.quantity != 0"}}
- %button.variant-quantity{type: "button", ng: {click: "add(-1)", disabled: "!canAdd(-1)"}}>
- -# U+FF0D Fullwidth Hyphen-Minus
- -
- %input.variant-quantity{ type: "number", min: "0", max: "{{ available() }}",
- ng: {model: "variant.line_item.quantity", max: "Infinity"}}>
- %button.variant-quantity{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}}
- -# U+FF0B Fullwidth Plus Sign
- +
- .variant-remaining-stock{ng: {if: "displayRemainingInStock()"}}
- {{ "js.shopfront.variant.remaining_in_stock" | t:{quantity: available()} }}
- .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}}
- {{ "js.shopfront.variant.quantity_in_cart" | t:{quantity: variant.line_item.quantity || 0} }}
- %input{type: :hidden,
- name: "variants[{{::variant.id}}]",
- ng: {model: "variant.line_item.quantity"}}
+ .variant-quantity-inputs{ng: {if: "variant.line_item.quantity != 0"}}
+ %button.variant-quantity{type: "button", ng: {click: "add(-1)", disabled: "!canAdd(-1)"}}>
+ -# U+FF0D Fullwidth Hyphen-Minus
+ -
+ %input.variant-quantity{ type: "number", min: "0", max: "{{ available() }}",
+ ng: {model: "variant.line_item.quantity", max: "Infinity"}}>
+ %button.variant-quantity{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}}
+ -# U+FF0B Fullwidth Plus Sign
+ +
+ .variant-remaining-stock{ng: {if: "displayRemainingInStock()"}}
+ {{ "js.shopfront.variant.remaining_in_stock" | t:{quantity: available()} }}
+ .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}}
+ {{ "js.shopfront.variant.quantity_in_cart" | t:{quantity: variant.line_item.quantity || 0} }}
+ %input{type: :hidden,
+ name: "variants[{{::variant.id}}]",
+ ng: {model: "variant.line_item.quantity"}}
diff --git a/app/views/shop/products/_shop_variant_with_group_buy.html.haml b/app/views/shop/products/_shop_variant_with_group_buy.html.haml
index 262ec20e3e..fc5c6533b3 100644
--- a/app/views/shop/products/_shop_variant_with_group_buy.html.haml
+++ b/app/views/shop/products/_shop_variant_with_group_buy.html.haml
@@ -1,17 +1,18 @@
-.small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::variant.product.group_buy"}
+= cache_with_locale do
+ .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::variant.product.group_buy"}
- %button.add-variant{type: "button", ng: {if: "!variant.line_item.quantity", click: "addBulk(1)", disabled: "!canAdd(1)"}}
- {{ "js.shopfront.variant.add_to_cart" | t }}
- %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}>
- {{ variant.line_item.quantity }}
- %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}
- {{ variant.line_item.max_quantity || "-" }}
- %br
- .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}}
- {{ "js.shopfront.variant.in_cart" | t }}
- %input{type: :hidden,
- name: "variants[{{::variant.id}}]",
- ng: {model: "variant.line_item.quantity"}}
- %input{type: :hidden,
- name: "variants[{{::variant.id}}]",
- ng: {model: "variant.line_item.max_quantity"}}
+ %button.add-variant{type: "button", ng: {if: "!variant.line_item.quantity", click: "addBulk(1)", disabled: "!canAdd(1)"}}
+ {{ "js.shopfront.variant.add_to_cart" | t }}
+ %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}>
+ {{ variant.line_item.quantity }}
+ %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}
+ {{ variant.line_item.max_quantity || "-" }}
+ %br
+ .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}}
+ {{ "js.shopfront.variant.in_cart" | t }}
+ %input{type: :hidden,
+ name: "variants[{{::variant.id}}]",
+ ng: {model: "variant.line_item.quantity"}}
+ %input{type: :hidden,
+ name: "variants[{{::variant.id}}]",
+ ng: {model: "variant.line_item.max_quantity"}}
diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml
index 7beec0d15b..66479e2ff8 100644
--- a/app/views/shop/products/_summary.html.haml
+++ b/app/views/shop/products/_summary.html.haml
@@ -1,21 +1,22 @@
-.product-thumb
- %a{"ng-click" => "triggerProductModal()"}
- %span.product-thumb__bulk-label{"ng-if" => "::product.group_buy"}
- = t(".bulk")
- %img{"ng-src" => "{{::product.primaryImageOrMissing}}"}
+= cache_with_locale do
+ .product-thumb
+ %a{"ng-click" => "triggerProductModal()"}
+ %span.product-thumb__bulk-label{"ng-if" => "::product.group_buy"}
+ = t(".bulk")
+ %img{"ng-src" => "{{::product.primaryImageOrMissing}}"}
-.summary
- .summary-header
- %h3
- %a{"ng-click" => "triggerProductModal()", href: 'javascript:void(0)'}
- %span{"ng-bind" => "::product.name"}
- .product-description{ng: {"bind-html": "::product.description_html", click: "triggerProductModal()", show: "product.description_html.length"}}
- %div{ "ng-switch" => "enterprise.visible" }
- .product-producer
- = t :products_from
- %span{ "ng-switch-when": "hidden", "ng-bind" => "::enterprise.name"}
- %span{ "ng-switch-default": true }
- %enterprise-modal{"ng-bind" => "::enterprise.name"}
+ .summary
+ .summary-header
+ %h3
+ %a{"ng-click" => "triggerProductModal()", href: 'javascript:void(0)'}
+ %span{"ng-bind" => "::product.name"}
+ .product-description{ng: {"bind-html": "::product.description_html", click: "triggerProductModal()", show: "product.description_html.length"}}
+ %div{ "ng-switch" => "enterprise.visible" }
+ .product-producer
+ = t :products_from
+ %span{ "ng-switch-when": "hidden", "ng-bind" => "::enterprise.name"}
+ %span{ "ng-switch-default": true }
+ %enterprise-modal{"ng-bind" => "::enterprise.name"}
- .product-properties.filter-shopfront.property-selectors
- %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
+ .product-properties.filter-shopfront.property-selectors
+ %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
diff --git a/app/views/shops/_fat.html.haml b/app/views/shops/_fat.html.haml
index a2522271ca..17a86258de 100644
--- a/app/views/shops/_fat.html.haml
+++ b/app/views/shops/_fat.html.haml
@@ -1,53 +1,54 @@
-.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"}
- .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"}
- %p.fullwidth
- = render partial: "components/spinner"
+= cache_with_locale do
+ .row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"}
+ .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"}
+ %p.fullwidth
+ = render partial: "components/spinner"
- .columns.small-12.medium-6.large-5.fat{"ng-show" => "open() && !shopfront_loading"}
- %div{"ng-if" => "::hub.taxons"}
- %label
- = t :hubs_buy
- .trans-sentence
- %div
- %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
- %span{"ng-bind" => "::taxon.name"}
- %div
- %span.fat-properties{"ng-repeat" => "property in hub.distributed_properties"}
- %span{"ng-bind" => "property.presentation"}
- %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"}
-
- .columns.small-12.medium-3.large-2.fat{"ng-show" => "open() && !shopfront_loading"}
- %div{"ng-if" => "::(hub.pickup || hub.delivery)"}
- %label
- = t :hubs_delivery_options
- %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1
- %li.pickup{"ng-if" => "::hub.pickup"}
- %i.ofn-i_038-takeaway
- = t :hubs_pickup
- %li.delivery{"ng-if" => "::hub.delivery"}
- %i.ofn-i_039-delivery
- = t :hubs_delivery
- .columns.small-12.medium-3.large-5.fat{"ng-show" => "open() && !shopfront_loading"}
- %div{"ng-if" => "::hub.producers"}
- %label
- = t :hubs_producers
- %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"}
- %li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"}
- %enterprise-modal
- %i.ofn-i_036-producers
- %span{"ng-bind" => "::enterprise.name"}
- %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"}
- %enterprise-modal
- %i.ofn-i_036-producers
- %span{"ng-bind" => "::enterprise.name"}
- %li{"data-is-link" => "true", "class" => "more-producers-link", "ng-show" => "::hub.producers.length>7"}
- %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers; $event.stopPropagation()"}
- .more
- +
- %span{"ng-bind" => "::hub.producers.length-7"}
- = t :label_more
- .less
- = t :label_less
+ .columns.small-12.medium-6.large-5.fat{"ng-show" => "open() && !shopfront_loading"}
+ %div{"ng-if" => "::hub.taxons"}
+ %label
+ = t :hubs_buy
+ .trans-sentence
+ %div
+ %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
+ %span{"ng-bind" => "::taxon.name"}
+ %div
+ %span.fat-properties{"ng-repeat" => "property in hub.distributed_properties"}
+ %span{"ng-bind" => "property.presentation"}
+ %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"}
+
+ .columns.small-12.medium-3.large-2.fat{"ng-show" => "open() && !shopfront_loading"}
+ %div{"ng-if" => "::(hub.pickup || hub.delivery)"}
+ %label
+ = t :hubs_delivery_options
+ %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1
+ %li.pickup{"ng-if" => "::hub.pickup"}
+ %i.ofn-i_038-takeaway
+ = t :hubs_pickup
+ %li.delivery{"ng-if" => "::hub.delivery"}
+ %i.ofn-i_039-delivery
+ = t :hubs_delivery
+ .columns.small-12.medium-3.large-5.fat{"ng-show" => "open() && !shopfront_loading"}
+ %div{"ng-if" => "::hub.producers"}
+ %label
+ = t :hubs_producers
+ %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"}
+ %li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"}
+ %enterprise-modal
+ %i.ofn-i_036-producers
+ %span{"ng-bind" => "::enterprise.name"}
+ %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"}
+ %enterprise-modal
+ %i.ofn-i_036-producers
+ %span{"ng-bind" => "::enterprise.name"}
+ %li{"data-is-link" => "true", "class" => "more-producers-link", "ng-show" => "::hub.producers.length>7"}
+ %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers; $event.stopPropagation()"}
+ .more
+ +
+ %span{"ng-bind" => "::hub.producers.length-7"}
+ = t :label_more
+ .less
+ = t :label_less
- %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"}
-
+ %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"}
+
diff --git a/app/views/shops/_hubs.html.haml b/app/views/shops/_hubs.html.haml
index 243f2e7c63..23410b2505 100644
--- a/app/views/shops/_hubs.html.haml
+++ b/app/views/shops/_hubs.html.haml
@@ -7,31 +7,32 @@
= render "shared/components/enterprise_search"
= render "filters"
- .row
- .small-12.columns
- .name-matches{"ng-show" => "nameMatchesFiltered.length > 0"}
- %h2
- = t :hubs_matches
- = render "hubs_table", enterprises: "nameMatches"
+ = cache_with_locale do
+ .row
+ .small-12.columns
+ .name-matches{"ng-show" => "nameMatchesFiltered.length > 0"}
+ %h2
+ = t :hubs_matches
+ = render "hubs_table", enterprises: "nameMatches"
- .distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"}
- %h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"}
- = t :hubs_matches
- %span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}...
- %span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}...
+ .distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"}
+ %h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"}
+ = t :hubs_matches
+ %span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}...
+ %span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}...
- = render "hubs_table", enterprises: "distanceMatches"
+ = render "hubs_table", enterprises: "distanceMatches"
- .show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"}
- %a{href: "", "ng-click" => "showDistanceMatches()"}
- = t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}"
- .more-controls
- %span{ng: {show: "closed_shops_loading", cloak: true}}
- = render partial: "components/spinner"
- %span{ng: {if: "!show_closed", cloak: true}}
- %a.button{href: "", ng: {click: "showClosedShops()"}}
- = t '.show_closed_shops'
- %span{ng: {if: "show_closed", cloak: true}}
- %a.button{href: "", ng: {click: "hideClosedShops()"}}
- = t '.hide_closed_shops'
- %a.button{href: main_app.map_path}= t '.show_on_map'
+ .show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"}
+ %a{href: "", "ng-click" => "showDistanceMatches()"}
+ = t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}"
+ .more-controls
+ %span{ng: {show: "closed_shops_loading", cloak: true}}
+ = render partial: "components/spinner"
+ %span{ng: {if: "!show_closed", cloak: true}}
+ %a.button{href: "", ng: {click: "showClosedShops()"}}
+ = t '.show_closed_shops'
+ %span{ng: {if: "show_closed", cloak: true}}
+ %a.button{href: "", ng: {click: "hideClosedShops()"}}
+ = t '.hide_closed_shops'
+ %a.button{href: main_app.map_path}= t '.show_on_map'
diff --git a/app/views/shops/_hubs_table.html.haml b/app/views/shops/_hubs_table.html.haml
index 76bd2a6695..15a6b57334 100644
--- a/app/views/shops/_hubs_table.html.haml
+++ b/app/views/shops/_hubs_table.html.haml
@@ -1,10 +1,11 @@
-.active_table
- %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | closedShops:show_closed | taxons:activeTaxons | properties:activeProperties:'distributed_properties' | shipping:shippingTypes | orderBy:['-active', '+distance', '+orders_close_at'])",
- "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
- "ng-controller" => "HubNodeCtrl",
- id: "{{hub.hash}}"}
- .small-12.columns
- = render 'skinny'
- = render 'fat'
+= cache_with_locale enterprises do
+ .active_table
+ %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | closedShops:show_closed | taxons:activeTaxons | properties:activeProperties:'distributed_properties' | shipping:shippingTypes | orderBy:['-active', '+distance', '+orders_close_at'])",
+ "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
+ "ng-controller" => "HubNodeCtrl",
+ id: "{{hub.hash}}"}
+ .small-12.columns
+ = render 'skinny'
+ = render 'fat'
- = render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered"
+ = render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered"
diff --git a/app/views/shops/_skinny.html.haml b/app/views/shops/_skinny.html.haml
index a5c6c48531..33a2106fd5 100644
--- a/app/views/shops/_skinny.html.haml
+++ b/app/views/shops/_skinny.html.haml
@@ -1,46 +1,47 @@
-.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"}
- .columns.small-12.medium-5.large-5.skinny-head
- %a.hub{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"}
- %i{ng: {class: "::hub.icon_font"}}
- %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"}
+= cache_with_locale do
+ .row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"}
+ .columns.small-12.medium-5.large-5.skinny-head
+ %a.hub{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"}
+ %i{ng: {class: "::hub.icon_font"}}
+ %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"}
- .columns.small-4.medium-2.large-2
- %span.margin-top.ellipsed{"ng-bind" => "::hub.address.city"}
- .columns.small-3.medium-2.large-2
- %span.margin-top.ellipsed{"ng-bind" => "::hub.address.state_name"}
- %span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km)
+ .columns.small-4.medium-2.large-2
+ %span.margin-top.ellipsed{"ng-bind" => "::hub.address.city"}
+ .columns.small-3.medium-2.large-2
+ %span.margin-top.ellipsed{"ng-bind" => "::hub.address.state_name"}
+ %span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km)
- .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::hub.active"}
- %a.hub.open_closed.flex.flex-align-center{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
- %span{ ng: { if: "::current()" } }
- %em= t :hubs_shopping_here
+ .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::hub.active"}
+ %a.hub.open_closed.flex.flex-align-center{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
+ %span{ ng: { if: "::current()" } }
+ %em= t :hubs_shopping_here
+ %span{ ng: { if: "::!current()" } }
+ %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"}
+ %i.ofn-i_068-shop-reversed.show-for-medium-up
+ %span{style: "margin-left: 0.5rem;"}
+ %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
+
+ .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::!hub.active"}
+ %a.hub.open_closed.flex{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
+ %span{ ng: { if: "::current()" } }
+ %em= t :hubs_shopping_here
+ %span{ ng: { if: "::!current()" } }
+ = t :hubs_orders_closed
+ %i.ofn-i_068-shop-reversed.show-for-medium-up
+ %span{style: "margin-left: 0.5rem;"}
+ %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
+
+ .row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"}
+ .columns.small-12.medium-6.large-5.skinny-head
+ %a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"}
+ %i{ng: {class: "hub.icon_font"}}
+ %span.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"}
+
+ .columns.small-4.medium-2.large-2
+ %span.ellipsed{"ng-bind" => "::hub.address.city"}
+ .columns.small-2.medium-1.large-1
+ %span.ellipsed{"ng-bind" => "::hub.address.state_name"}
+
+ .columns.small-6.medium-3.large-4.text-right.no-wrap.flex.flex-align-center.flex-justify-end
%span{ ng: { if: "::!current()" } }
- %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"}
- %i.ofn-i_068-shop-reversed.show-for-medium-up
- %span{style: "margin-left: 0.5rem;"}
- %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
-
- .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::!hub.active"}
- %a.hub.open_closed.flex{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
- %span{ ng: { if: "::current()" } }
- %em= t :hubs_shopping_here
- %span{ ng: { if: "::!current()" } }
- = t :hubs_orders_closed
- %i.ofn-i_068-shop-reversed.show-for-medium-up
- %span{style: "margin-left: 0.5rem;"}
- %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
-
-.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"}
- .columns.small-12.medium-6.large-5.skinny-head
- %a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"}
- %i{ng: {class: "hub.icon_font"}}
- %span.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"}
-
- .columns.small-4.medium-2.large-2
- %span.ellipsed{"ng-bind" => "::hub.address.city"}
- .columns.small-2.medium-1.large-1
- %span.ellipsed{"ng-bind" => "::hub.address.state_name"}
-
- .columns.small-6.medium-3.large-4.text-right.no-wrap.flex.flex-align-center.flex-justify-end
- %span{ ng: { if: "::!current()" } }
- %em= t :hubs_profile_only
+ %em= t :hubs_profile_only
diff --git a/config/application.rb b/config/application.rb
index 101a35e2e2..13e94443fe 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -23,6 +23,7 @@ end
require_relative "../lib/open_food_network/i18n_config"
require_relative '../lib/spree/core/environment'
require_relative '../lib/spree/core/mail_interceptor'
+require_relative "../lib/i18n_digests"
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
@@ -184,6 +185,9 @@ module Openfoodnetwork
config.i18n.available_locales = OpenFoodNetwork::I18nConfig.available_locales
I18n.locale = config.i18n.locale = config.i18n.default_locale
+ # Calculate digests for locale files so we can know when they change
+ I18nDigests.build_digests config.i18n.available_locales
+
# Setting this to true causes a performance regression in Rails 3.2.17
# When we're on a version with the fix below, we can set it to true
# https://github.com/svenfuchs/i18n/issues/230
diff --git a/lib/i18n_digests.rb b/lib/i18n_digests.rb
new file mode 100644
index 0000000000..23fe078a6a
--- /dev/null
+++ b/lib/i18n_digests.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class I18nDigests
+ class << self
+ def build_digests(available_locales)
+ available_locales.each do |locale|
+ i18n_digests[locale.to_sym] = locale_file_digest(locale)
+ end
+ end
+
+ def for_locale(locale)
+ i18n_digests[locale.to_sym]
+ end
+
+ private
+
+ def i18n_digests
+ Rails.application.config.x.i18n_digests
+ end
+
+ def locale_file_digest(locale)
+ Digest::MD5.hexdigest(Rails.root.join("config/locales/#{locale}.yml").read)
+ end
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index a690869f93..80847b4f7f 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -42,4 +42,51 @@ describe ApplicationHelper, type: :helper do
end
end
end
+
+ describe "#cache_with_locale" do
+ let(:available_locales) { ["en", "es"] }
+ let(:current_locale) { "es" }
+ let(:locale_digest) { "8a7s5dfy28u0as9du" }
+ let(:options) { { expires_in: 10.seconds } }
+
+ before do
+ allow(I18n).to receive(:available_locales) { available_locales }
+ allow(I18n).to receive(:locale) { current_locale }
+ allow(I18nDigests).to receive(:for_locale) { locale_digest }
+ end
+
+ it "passes key, options, and block to #cache method with locale and locale digest appended" do
+ expect(helper).to receive(:cache_key_with_locale).
+ with("test-key", current_locale).and_return(["test-key", current_locale, locale_digest])
+
+ expect(helper).to receive(:cache).
+ with(["test-key", current_locale, locale_digest], options) do |&block|
+ expect(block.call).to eq("cached content")
+ end
+
+ helper.cache_with_locale "test-key", options do
+ "cached content"
+ end
+ end
+ end
+
+ describe "#cache_key_with_locale" do
+ let(:en_digest) { "asd689asy0239" }
+ let(:es_digest) { "9d8tu23oirhad" }
+
+ before { allow(I18nDigests).to receive(:for_locale).with("en") { en_digest } }
+ before { allow(I18nDigests).to receive(:for_locale).with("es") { es_digest } }
+
+ it "appends locale and digest to a single key" do
+ expect(
+ helper.cache_key_with_locale("single-key", "en")
+ ).to eq(["single-key", "en", en_digest])
+ end
+
+ it "appends locale and digest to multiple keys" do
+ expect(
+ helper.cache_key_with_locale(["array", "of", "keys"], "es")
+ ).to eq(["array", "of", "keys", "es", es_digest])
+ end
+ end
end
diff --git a/spec/lib/i18n_digests_spec.rb b/spec/lib/i18n_digests_spec.rb
new file mode 100644
index 0000000000..391cbb0050
--- /dev/null
+++ b/spec/lib/i18n_digests_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe I18nDigests do
+ describe "#build_digests" do
+ let(:available_locales) { ["en", "es"] }
+ let(:md5_hex_regex) { /([a-f0-9]){10}/ }
+
+ around do |example|
+ original = Rails.application.config.x.i18n_digests
+ example.run
+ Rails.application.config.x.i18n_digests = original
+ end
+
+ it "computes and stores digests for each locale file" do
+ Rails.application.config.x.i18n_digests = {}
+ I18nDigests.build_digests(available_locales)
+
+ expect(Rails.application.config.x.i18n_digests.keys).to eq [:en, :es]
+ expect(Rails.application.config.x.i18n_digests.values).to all match(md5_hex_regex)
+
+ expect(
+ Rails.application.config.x.i18n_digests[:en]
+ ).to eq(Digest::MD5.hexdigest(Rails.root.join("config/locales/en.yml").read))
+
+ expect(
+ Rails.application.config.x.i18n_digests[:es]
+ ).to eq(Digest::MD5.hexdigest(Rails.root.join("config/locales/es.yml").read))
+ end
+ end
+
+ describe "#for_locale" do
+ let(:digests) { { en: "as8d7a9sdh", es: "iausyd9asdh" } }
+
+ before do
+ allow(Rails).to receive_message_chain(:application, :config, :x, :i18n_digests) { digests }
+ end
+
+ it "returns the digest for a given locale" do
+ expect(I18nDigests.for_locale("en")).to eq "as8d7a9sdh"
+ end
+ end
+end