Neste tutorial veremos como adicionar a possibilidade de calcular frete para outros países sem ter que mudar a moeda ou idioma.
Vale a pena explicar que nas lojas existem dois tipos de entidades para cada país configuradas na tela Configurações > Idiomas e Moedas.
- storefront_country: É o país que define o idioma e a moeda.
- shipping_country: É o país para o qual o pedido é enviado (pode ser escolhido em um dropdown no paso 1 do checkout)
Em todas as lojas storefront_country é igual a shipping_country, portanto se o usuário alterar o idioma em uma loja, por exemplo para Argentina, o país de envio também será Argentina.
Nos próximos passos veremos como tornar o shipping_country independente do storefront_country, portanto o usuário poderá comprar olhando a loja em sua versão Brasileira mas enviando para outro país sem ter que mudar o Storefront_country.

HTML
1. A primeira coisa que vamos fazer é adicionar o seguinte código para o texto que diz “Meios de envio para [Shipping country]” dentro do snipplet shipping-calculator.tpl dentro da pasta de snipplets / shipping. Devemos substituir o texto que diz "Meios de envio" pelo seguinte:
{% if languages | length > 1 %}
{{ ' para ' | translate }}
{% for language in languages %}
{% if (shipping_country_id and language.id == shipping_country_id) or (not shipping_country_id and language.active) %}
<a href="#" data-toggle="#{% if product_detail %}product{% else %}cart{% endif %}-shipping-country" class="js-modal-open js-shipping-country-label btn-link btn-link-primary text-capitalize">
{{ language.country_name }}
</a>
{% endif %}
{% endfor %}
{% endif %}E então no final deste arquivo vamos adicionar o modal que permite a mudança de país:
{# Shipping country modal #}
{% if languages | length > 1 %}
{% if product_detail %}
{% set country_modal_id = 'product-shipping-country' %}
{% else %}
{% set country_modal_id = 'cart-shipping-country' %}
{% endif %}
{% embed "snipplets/modal.tpl" with{modal_id: country_modal_id, modal_class: 'bottom modal-centered-small js-modal-shipping-country', modal_position: 'center', modal_transition: 'slide', modal_header: true, modal_footer: true, modal_width: 'centered', modal_zindex_top: true, modal_mobile_full_screen: false} %}
{% block modal_head %}
{{ 'País de entrega' | translate }}
{% endblock %}
{% block modal_body %}
{% embed "snipplets/forms/form-select.tpl" with{select_label: true, select_label_name: 'País donde entregaremos tu compra' | translate, select_aria_label: 'País donde entregaremos tu compra' | translate, select_custom_class: 'js-shipping-country-select', select_group_custom_class: 'mt-4' } %}
{% block select_options %}
{% for language in languages %}
<option value="{{ language.id }}" data-country-url="{{ language.url }}" {% if (shipping_country_id and language.id == shipping_country_id) or (not shipping_country_id and language.active) %}selected{% endif %}>{{ language.country_name }}</option>
{% endfor %}
{% endblock select_options%}
{% endembed %}
{% endblock %}
{% block modal_foot %}
<a href="#" class="js-save-shipping-country js-modal-close btn btn-primary float-right">{{ 'Aplicar' | translate }}</a>
{% endblock %}
{% endembed %}
{% endif %}2. Agora precisamos criar o snipplet para o componente modal ou pop-up dentro da pasta de snipplets. Este tpl é denominado modal.tpl e o código é:
{#/*============================================================================
#Shipping calculator
==============================================================================*/ #}
{# /* // Change store country: From invalid zipcode message */ #}
$(document).on("click", ".js-save-shipping-country", function(e) {
e.preventDefault();
{# Change shipping country #}
var $selected_country_select = $(this).closest(".js-modal").find(".js-shipping-country-select option:selected");
var selected_country_val_placeholder = $selected_country_select.text();
var selected_country_val = $selected_country_select.val();
// Sync multiple selects on DOM
$(".js-shipping-country-select ").val(selected_country_val);
// Update selected country with front end until backend request is ready
$(".js-shipping-country-label, .js-zipcode-error-country").text(selected_country_val_placeholder);
// If has selected shipping options, reset results and totals
$(".js-shipping-calculator-response").hide();
LS.resetCalculatedShipping();
{# Save shipping country id #}
LS.saveShippingCountry("/shipping_country/", selected_country_val, function(data){
{# Update selected wording #}
$(".js-shipping-country-label, .js-zipcode-error-country").text(data.shipping_country.country_name);
});
});
{# /*============================================================================
#Modal
==============================================================================*/
#Properties
// ID
// Position - Top, Right, Bottom, Left
// Transition - Slide and Fade
// Width - Full and Box
// modal_form_action - For modals that has a form
#Head
// Block - modal_head
#Body
// Block - modal_body
#Footer
// Block - modal_footer
#}
{% set modal_overlay = modal_overlay | default(true) %}
<div id="{{ modal_id }}" class="js-modal {% if modal_mobile_full_screen %}js-fullscreen-modal{% endif %} modal modal-{{ modal_class }} modal-{{modal_position}} transition-{{modal_transition}} modal-{{modal_width}} transition-soft" style="display: none;">
{% if modal_form_action %}
<form action="{{ modal_form_action }}" method="post" class="{{ modal_form_class }}" {% if modal_form_hook %}data-store="{{ modal_form_hook }}"{% endif %}>
{% endif %}
<div class="js-modal-close {% if modal_mobile_full_screen %}js-fullscreen-modal-close{% endif %} modal-header">
<span class="modal-close">
{% include "snipplets/svg/times.tpl" with {svg_custom_class: "icon-inline svg-icon-text"} %}
</span>
{% block modal_head %}{% endblock %}
</div>
<div class="modal-body">
{% block modal_body %}{% endblock %}
</div>
{% if modal_footer %}
<div class="modal-footer d-md-block">
{% block modal_foot %}{% endblock %}
</div>
{% endif %}
{% if modal_form_action %}
</form>
{% endif %}
</div>3. Para usar o snipplet que criamos no passo anterior, teremos que criar um novo componente para o select ou dropdown. Para isso, criamos o arquivo form-select.tpl dentro da pasta snipplets/forms com o seguinte código:
{# /*============================================================================
#Form select
==============================================================================*/
#Properties
#Group
//select_group_custom_class for custom CSS classes
#Label
// select_label_name for name
// select_label_id for ID
// select_for for label for
// select_label_custom_class for custom CSS classes
#Select
// select_id for id
// select_name for name
// select_custom_class for custom CSS classes
// input_rows for textarea rows
// select_options to insert select options
// select_aria_label for aria-label attribute
#}
<div class="form-group {{ select_group_custom_class }}">
{% if select_label %}
<label {% if select_label_id%}id="{{ select_label_id }}"{% endif %} class="form-label {{ select_label_custom_class }}" {% if select_for %}for="{{ select_for }}"{% endif %}>{{ select_label_name }}</label>
{% endif %}
<select
{% if select_id %}id="{{ select_id }}"{% endif %}
class="form-select {{ select_custom_class }} {% if select_inline %}form-control-inline{% endif %}"
{% if select_data %}data-{{select_data}}="{{select_data_value}}"{% endif %}
{% if select_name %}name="{{ select_name }}"{% endif %}
{% if select_aria_label %}aria-label="{{ select_aria_label }}"{% endif %}>
{% block select_options %}
{% endblock select_options %}
</select>
<div class="form-select-icon">
{% include "snipplets/svg/chevron-down.tpl" with {svg_custom_class: "icon-inline icon-w-14 icon-lg svg-icon-text"} %}
</div>
</div>
4.Por fim, para a parte HTML, dentro da pasta snipplets/SVG adicionaremos os SVGs que usamos para o select, o ícone de filtros e o modal.
times.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>chevron-left.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path d="M231.293 473.899l19.799-19.799c4.686-4.686 4.686-12.284 0-16.971L70.393 256 251.092 74.87c4.686-4.686 4.686-12.284 0-16.971L231.293 38.1c-4.686-4.686-12.284-4.686-16.971 0L4.908 247.515c-4.686 4.686-4.686 12.284 0 16.971L214.322 473.9c4.687 4.686 12.285 4.686 16.971-.001z"/></svg>chevron-down.tpl
<svg class="{{ svg_custom_class }}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M441.9 167.3l-19.8-19.8c-4.7-4.7-12.3-4.7-17 0L224 328.2 42.9 147.5c-4.7-4.7-12.3-4.7-17 0L6.1 167.3c-4.7 4.7-4.7 12.3 0 17l209.4 209.4c4.7 4.7 12.3 4.7 17 0l209.4-209.4c4.7-4.7 4.7-12.3 0-17z"/></svg>CSS
Requisito:
Ter adicionado helper classes em seu layout. Você pode seguir este pequeno tutorial para fazer isso (é só copiar e colar algumas classes, não leva mais que 1 minuto).
1. Adicionamos o seguinte SASS de cores em style-colors.scss.tpl (ou na stylesheet do seu layout que possui as cores e fontes da loja). Lembre-se de que as variáveis de cores e fontes podem variar em relação ao seu layout:
{# /* // Mixins */ #}
{# This mixin adds browser prefixes to a CSS property #}
@mixin prefix($property, $value, $prefixes: ()) {
@each $prefix in $prefixes {
#{'-' + $prefix + '-' + $property}: $value;
}
#{$property}: $value;
}
{# /* // Links */ #}
a {
color: $main-foreground;
fill: $main-foreground;
@include prefix(transition, all 0.4s ease, webkit ms moz o);
&:hover,
&:focus{
color: rgba($main-foreground, .5);
fill: rgba($main-foreground, .5);
}
}
.btn-link{
color: $primary-color;
fill: $primary-color;
text-transform: uppercase;
border-bottom: 1px solid;
font-weight: bold;
cursor: pointer;
&:hover,
&:focus{
color: rgba($primary-color, .5);
fill: rgba($primary-color, .5);
}
}
{# /* // Modals */ #}
.modal{
color: $main-foreground;
background-color:$main-background;
}
{# /* // Forms */ #}
input,
textarea {
font-family: $body-font;
}
.form-control {
display: block;
padding: 8px;
width: 100%;
font-size: 16px; /* Hack to avoid autozoom on IOS */
border: 0;
border-bottom: 1px solid rgba($main-foreground, .5);
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
color: $main-foreground;
background-color: $main-background;
&:focus{
outline: 0;
}
&-inline{
display: inline;
}
}
.form-control::-webkit-input-placeholder {
color: $main-foreground;
}
.form-control:-moz-placeholder {
color: $main-foreground;
}
.form-control::-moz-placeholder {
color: $main-foreground;
}
.form-control:-ms-input-placeholder {
color: $main-foreground;
}
.form-select{
display: block;
padding: 10px 0;
width: 100%;
font-size: 16px; /* Hack to avoid autozoom on IOS */
border: 0;
border-bottom: 1px solid rgba($main-foreground, .5);
border-radius: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
color: $main-foreground;
background-color: $main-background;
@extend %body-font;
&-icon{
background: $main-background;
}
}2. Adicione os estilos no arquivo static/style-critical.tpl
Se em seu layout você usar um stylesheet para o CSS crítico, precisaremos do seguinte código dentro dele, do contrário, você pode unificar o CSS dos passos 2 e 3 em um único arquivo.
{# /* // Forms */ #}
.form-group {
position: relative;
width: 100%;
}
.form-group .form-select-icon{
position: absolute;
bottom: 12px;
right: 0;
pointer-events: none;
}3. Agregar los estilos dentro del archivo static/style-async.tpl
Si en tu diseño usas una hoja de estilos para CSS asíncrono, vamos a necesitar agregar el siguiente código dentro de la misma, pero si no es el caso podés unificar el CSS de los pasos 2 y 3 en un solo archivo.
{# /* // Mixins */ #}
{# This mixin adds browser prefixes to a CSS property #}
@mixin prefix($property, $value, $prefixes: ()) {
@each $prefix in $prefixes {
#{'-' + $prefix + '-' + $property}: $value;
}
#{$property}: $value;
}
{# /* // Modals */ #}
.modal {
position: fixed;
top: 0;
display: block;
width: 80%;
height: 100%;
padding: 10px;
-webkit-overflow-scrolling: touch;
overflow-y: auto;
transition: all .2s cubic-bezier(.16,.68,.43,.99);
z-index: 20000;
&-header{
width: calc(100% + 20px);
margin: -10px 0 10px -10px;
padding: 10px 15px;
font-size: 20px;
}
&-footer{
padding: 10px 0;
clear: both;
}
&-full {
width: 100%;
}
&-docked-md{
width: 100%;
}
&-docked-small{
width: 80%;
}
&-top{
top: -100%;
left: 0;
}
&-bottom{
top: 100%;
left: 0;
}
&-left{
left: -100%;
}
&-right{
right: -100%;
}
&-centered{
height: 100%;
width: 100%;
&-small{
left: 50%;
width: 80%;
height: auto;
@include prefix(transform, translate(-50%, -50%), webkit ms moz o);
.modal-body{
min-height: 150px;
max-height: 400px;
overflow: auto;
}
}
}
&-top.modal-show,
&-bottom.modal-show {
top: 0;
&.modal-centered-small{
top: 50%;
}
}
&-bottom-sheet {
top: initial;
bottom: -100%;
height: auto;
&.modal-show {
top: initial;
bottom: 0;
height: auto;
}
}
&-left.modal-show {
left: 0;
}
&-right.modal-show {
right: 0;
}
&-close {
display: inline-block;
padding: 1px 5px 5px 0;
margin-right: 5px;
vertical-align: middle;
cursor: pointer;
}
.tab-group{
margin: 0 -10px 20px -10px;
}
}
.modal-overlay{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000047;
z-index: 10000;
&.modal-zindex-top{
z-index: 20000;
}
}
{# /* // Forms */ #}
.form-group{
@extend %element-margin;
.form-label{
float: left;
width: 100%;
margin-bottom: 10px;
}
.alert{
margin: 10px 0 0 0;
}
}
.form-select {
display: block;
width: 100%;
&:focus{
outline:0;
}
&::-ms-expand {
display: none;
}
}
{#/*============================================================================
#Media queries
==============================================================================*/ #}
{# /* // Min width 768px */ #}
@media (min-width: 768px) {
{# /* //// Components */ #}
{# /* Modals */ #}
.modal{
&-centered{
height: 80%;
width: 80%;
left: 10%;
margin: 5% auto;
&-small{
left: 50%;
width: 30%;
height: auto;
max-height: 80%;
margin: 0;
}
}
&-docked-md{
width: 500px;
&-centered{
left: calc(50% - 250px);
bottom: auto;
height: auto;
}
}
&-bottom-sheet {
top: 100%;
&.modal-show {
top: 0;
bottom: auto;
}
}
&-docked-small{
width: 350px;
}
}JS
⚠️ A partir do dia 30 de janeiro de 2023, a biblioteca jQuery será removida do código de nossas lojas, portanto, a função "$" não poderá ser utilizada.
JavaScript precisam ser adicionados no arquivo store.js.tpl (ou onde você tem suas funções JS). O código que precisamos é o seguinte:
{#/*============================================================================
#Shipping calculator
==============================================================================*/ #}
{# /* // Lang select */ #}
changeLang = function(element) {
var selected_country_url = element.find("option").filter((el) => el.selected).attr("data-country-url");
location.href = selected_country_url;
};
jQueryNuvem(document).on("click", ".js-save-shipping-country", function(e) {
e.preventDefault();
{# Change shipping country #}
lang_select_option = jQueryNuvem(this).closest(".js-modal-shipping-country");
changeLang(lang_select_option);
jQueryNuvem(this).text('{{ "Aplicando..." | translate }}').addClass("disabled");
});
{#/*============================================================================
#Modals
==============================================================================*/ #}
{# Full screen mobile modals back events #}
if (window.innerWidth < 768) {
{# Clean url hash function #}
cleanURLHash = function(){
const uri = window.location.toString();
const clean_uri = uri.substring(0, uri.indexOf("#"));
window.history.replaceState({}, document.title, clean_uri);
};
{# Go back 1 step on browser history #}
goBackBrowser = function(){
cleanURLHash();
history.back();
};
{# Clean url hash on page load: All modals should be closed on load #}
if(window.location.href.indexOf("modal-fullscreen") > -1) {
cleanURLHash();
}
{# Open full screen modal and url hash #}
jQueryNuvem(document).on("click", ".js-fullscreen-modal-open", function(e) {
e.preventDefault();
var modal_url_hash = jQueryNuvem(this).data("modalUrl");
window.location.hash = modal_url_hash;
});
{# Close full screen modal: Remove url hash #}
jQueryNuvem(document).on("click", ".js-fullscreen-modal-close", function(e) {
e.preventDefault();
goBackBrowser();
});
{# Hide panels or modals on browser backbutton #}
window.onhashchange = function() {
if(window.location.href.indexOf("modal-fullscreen") <= -1) {
{# Close opened modal #}
if(jQueryNuvem(".js-fullscreen-modal").hasClass("modal-show")){
{# Remove body lock only if a single modal is visible on screen #}
if(jQueryNuvem(".js-modal.modal-show").length == 1){
jQueryNuvem("body").removeClass("overflow-none");
}
var $opened_modal = jQueryNuvem(".js-fullscreen-modal.modal-show");
var $opened_modal_overlay = $opened_modal.prev();
$opened_modal.removeClass("modal-show");
setTimeout(() => $opened_modal.hide(), 500);
$opened_modal_overlay.fadeOut(500);
}
}
}
}
jQueryNuvem(document).on("click", ".js-modal-open", function(e) {
e.preventDefault();
var modal_id = jQueryNuvem(this).data('toggle');
var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="' + modal_id + '"]');
if (jQueryNuvem(modal_id).hasClass("modal-show")) {
let modal = jQueryNuvem(modal_id).removeClass("modal-show");
setTimeout(() => modal.hide(), 500);
} else {
{# Lock body scroll if there is no modal visible on screen #}
if(!jQueryNuvem(".js-modal.modal-show").length){
jQueryNuvem("body").addClass("overflow-none");
}
$overlay_id.fadeIn(400);
jQueryNuvem(modal_id).detach().appendTo("body");
$overlay_id.detach().insertBefore(modal_id);
jQueryNuvem(modal_id).show().addClass("modal-show");
}
});
jQueryNuvem(document).on("click", ".js-modal-close", function(e) {
e.preventDefault();
{# Remove body lock only if a single modal is visible on screen #}
if(jQueryNuvem(".js-modal.modal-show").length == 1){
jQueryNuvem("body").removeClass("overflow-none");
}
var $modal = jQueryNuvem(this).closest(".js-modal");
var modal_id = $modal.attr('id');
var $overlay_id = jQueryNuvem('.js-modal-overlay[data-modal-id="#' + modal_id + '"]');
$modal.removeClass("modal-show");
setTimeout(() => $modal.hide(), 500);
$overlay_id.fadeOut(500);
{# Close full screen modal: Remove url hash #}
if ((window.innerWidth < 768) && (jQueryNuvem(this).hasClass(".js-fullscreen-modal-close"))) {
goBackBrowser();
}
});
jQueryNuvem(document).on("click", ".js-modal-overlay", function(e) {
e.preventDefault();
{# Remove body lock only if a single modal is visible on screen #}
if(jQueryNuvem(".js-modal.modal-show").length == 1){
jQueryNuvem("body").removeClass("overflow-none");
}
var modal_id = jQueryNuvem(this).data('modalId');
let modal = jQueryNuvem(modal_id).removeClass("modal-show");
setTimeout(() => modal.hide(), 500);
jQueryNuvem(this).fadeOut(500);
if (jQueryNuvem(this).hasClass("js-fullscreen-overlay") && (window.innerWidth < 768)) {
cleanURLHash();
}
});Traduções
Nesta etapa, adicionamos os textos para as traduções no arquivo config/translations.txt
es "Aplicar"
pt "Aplicar"
en "Apply"
es_mx "Aplicar"
es "Aplicando..."
pt "Aplicando..."
en "Aplying..."
es_mx "Aplicando..."
es "No encontramos este código postal{1}"
pt "Não conseguimos encontrar esse CEP{1}"
en "We couldn't find this zipcode{1}"
es_mx "No encontramos este código postal{1}"
es " para "
pt " para "
en " for "
es_mx " para "
es ". ¿Está bien escrito?"
pt ". Está bem escrito?"
en ". Is it written right?"
es_mx ". ¿Está bien escrito?"
es ". Podés intentar con otro o"
pt ". Pode tentar outro ou"
en ". You can try another or"
es_mx ". Puedes intentar con otro o"
es "cambiar tu país de entrega"
pt "alterar o país de entrega"
en "change your shipping country"
es_mx "cambiar tu país de entrega"
es "País de entrega"
pt "Pais de entrega"
en "Shipping country"
es_mx "País de entrega"
es "País donde entregaremos tu compra"
pt "País onde entregaremos seu pedido"
en "Country where we will deliver your order"
es_mx "País donde entregaremos tu compra"Activación
Por último recordá que para tener más de un idioma en tu tienda debes hacerlo desde la parte de “Configuraciones/Idiomas y monedas”.