Locales
๐ Private Beta
This feature is currently in private beta. If you would like access, please reach out to Support.
The locales feature enables you to quickly deploy your full storefront to various locales tailored to each country you operate in. Learn how to create and manage locales below.
Adding Locales
Navigate to
Site
โSettings
โLocales
Add desired locales and define your Page Path Prefix
On your next publish, your full storefront will be enabled on the added locales
Publishing
Once you have added locales and published your site, all pages on your storefront will be available on each locale page path defined via Settings. Shogun Frontend will automatically include the following in each subsequent publication:
Locale subpaths (for
/yourpage
,/en-ca/yourpage
and/en-gb/yourpage
now exist)Metatags setup
Href setup
Locale Hooks
You can now develop locale specific functionality on your storefront using the hooks below.
useStoreLocales
The @getshogun/frontend-hooks
package exposes a hook called useStoreLocales
which provides the following:
locales
- The list of available store localesselectedLocale
- The currently active/selected store localebuildLinkWithLocale
- A helper function to construct a new locale-dependent URL given a pathname and locale prefix
useRouter
Please note that usage of useRouter
requires a change to correctly get the current locale-aware pathname. Usually, it is done via:
JavaScript
const { pathname } = useRouter()
And that needs to be changed to:
JavaScript
const { location: { pathname } } = useRouter()
Locale-Aware Components
LocaleLink
Using the useStoreLocales
hook, we can create a custom locale-aware link component which can be used to navigate the store:
import React from 'react'
import { useRouter } from 'frontend-router'
import { useStoreLocales } from '@getshogun/frontend-hooks'
import Link from 'frontend-link'
const LocaleLink = ({ href, ...props }) => {
const { location: { pathname } } = useRouter()
const { selectedLocale, buildLinkWithLocale } = useStoreLocales()
let path = href
if(selectedLocale && !selectedLocale.default) {
path = buildLinkWithLocale(href, selectedLocale.pathPrefix)
}
return <Link href={path} {...props} />
}
export default LocaleLink
Existing usages of Link
or a
should be replaced with LocaleLink
to provide locale-aware navigation inside the store. For example, instead of:
<Link href={`/products/${productslug}`>{product.name}<Link/>
It should now use the LocaleLink component:
<LocaleLink href={`/products/${productslug}`>{product.name}<LocaleLink/>
LocaleSwitcher
We can also create a component to allow switching locales:
import React from 'react'
import { useRouter } from 'frontend-router'
import { useStoreLocales } from '@getshogun/frontend-hooks'
import Link from 'frontend-link'
import styles from './styles.module.css'
const LocaleSwitcher = () => {
const ICONS = {
'en-US': '๐บ๐ธ',
'en-GB': '๐ฌ๐ง',
'default': '๐ณ'
}
const { locales, buildLinkWithLocale } = useStoreLocales()
const { location: { pathname } } = useRouter()
return (
<>
{locales.map(locale => {
const href = buildLinkWithLocale(pathname, locale.pathPrefix)
const icon = ICONS[locale.code] || ICONS.default
const label = locale.code.toUpperCase()
return <Link href={href}>{icon} {label}</Link>
})}
<>
)
}
export default LocaleSwitcher
Shopify Markets
The frontend-checkout
package now supports Shopify Markets. Learn more here. Below are some example for implementing international pricing on Shogun Frontend using Shopify Markets.
Building the country selector
import { useCartActions, useCartState } from "frontend-checkout";
const CountrySelector = () => {
const { countryCode } = useCartState();
const { selectCountry } = useCartActions();
return (
<select
defaultValue={countryCode}
onChange={(e) => {
selectCountry(e.target.value);
}}
>
<option value="US">US</option>
<option value="FR">FR</option>
<option value="CA">CA</option>
</select>
);
};
๐ง Selected country is persisted between sessions in localStorage. Thereโs no need to implement that.
Updating the cart currency
Here is how to update the cart currency:
const CartDrawer = () => {
const { hideCart } = useCartActions();
const { isCartShown, currencyCode } = useCartState(); // here you get the currency
return (
<Drawer isOpen={isCartShown} onClose={hideCart} size="md">
<CartItems inDrawer />
</Drawer>
);
};
You may want to update the cart item component to format the price according to countryโs currency:
function formatMoney({ money, locales, options }) {
if (typeof money === "string") {
money = Number(money);
}
const formatter = new Intl.NumberFormat(locales, {
style: "currency",
currency: "USD",
...options,
});
return formatter.format(money);
}
...
<Text>
{formatMoney({
money: Number(checkoutProduct.variant.priceV2.amount) * quantity,
options: { currency: currencyCode },
})}
</Text>
Retrieving product information
๐ง
product.price
should not be used anymore. Instead priceV2 from variants should be used in order to get the right amount in the selected currency (via country).
const ProductGridItem = ({ imageLoading, product: cmsProduct }) => {
const product = useNormalizedProduct(cmsProduct);
const { countryCode } = useCartState();
const { fetchProduct } = useCartActions();
React.useEffect(() => {
if (!product) return;
async function handleFetchProduct() {
const productResponse = await fetchProduct(product.id, {
country: countryCode,
});
console.log({ productResponse });
}
handleFetchProduct();
}, [fetchProduct, product, store.country]);
// ...
};
Here is an example productResponse
response:
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzY1NzQ2MTg2MDc3NDU=",
"availableForSale": true,
"createdAt": "2022-08-29T06:34:32Z",
"updatedAt": "2022-09-02T10:56:22Z",
"descriptionHtml": "<p>Of Course I Still Love You. Paleo pickled pork belly cardigan chillwave crucifix. Irregular Apocalypse. Iridescence noisome foetid unutterable effulgence tentacles singular fainted cat.</p>",
"description": "Of Course I Still Love You. Paleo pickled pork belly cardigan chillwave crucifix. Irregular Apocalypse. Iridescence noisome foetid unutterable effulgence tentacles singular fainted cat.",
"handle": "aerodynamic-granite-watch",
"productType": "Automotive",
"title": "Aerodynamic Granite Watch",
"vendor": "Hessel-Bernier",
"publishedAt": "2022-09-02T10:56:21Z",
"onlineStoreUrl": "https://storefront-lde.shogun.dev/products/aerodynamic-granite-watch",
"options": [
{
"name": "Color",
"values": [
"green",
"turquoise",
"plum"
]
}
],
"images": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0SW1hZ2UvMjg2NTUxNTUyNDkyODE=",
"src": "https://cdn.shopify.com/s/files/1/0553/3938/4961/products/source-404_41b0b7f1-e8b2-4e18-9068-083d3364fa58.jpg?v=1661754872",
"altText": "Bora Horza Gobuchul"
}
],
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC8zOTQwMzYxODMwNDEyOQ==",
"title": "green",
"price": "24.00",
"priceV2": {
"amount": "24.95",
"currencyCode": "EUR"
},
"weight": 0.8995,
"quantityAvailable": 22,
"available": true,
"sku": "",
"compareAtPrice": null,
"compareAtPriceV2": null,
"image": {
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0SW1hZ2UvMjg2NTUxNTUyNDkyODE=",
"src": "https://cdn.shopify.com/s/files/1/0553/3938/4961/products/source-404_41b0b7f1-e8b2-4e18-9068-083d3364fa58.jpg?v=1661754872",
"altText": "Bora Horza Gobuchul"
},
"selectedOptions": [
{
"name": "Color",
"value": "green"
}
],
"unitPrice": null,
"unitPriceMeasurement": {
"measuredType": null,
"quantityUnit": null,
"quantityValue": 0,
"referenceUnit": null,
"referenceValue": 0
}
},
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC8zOTQwMzYxODMzNjg5Nw==",
"title": "turquoise",
"price": "24.00",
"priceV2": {
"amount": "24.95",
"currencyCode": "EUR"
},
"weight": 0.194,
"quantityAvailable": 18,
"available": true,
"sku": "aerodynamic-granite-watch-turquoise",
"compareAtPrice": null,
"compareAtPriceV2": null,
"image": {
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0SW1hZ2UvMjg2NTUxNTUyNDkyODE=",
"src": "https://cdn.shopify.com/s/files/1/0553/3938/4961/products/source-404_41b0b7f1-e8b2-4e18-9068-083d3364fa58.jpg?v=1661754872",
"altText": "Bora Horza Gobuchul"
},
"selectedOptions": [
{
"name": "Color",
"value": "turquoise"
}
],
"unitPrice": null,
"unitPriceMeasurement": {
"measuredType": null,
"quantityUnit": null,
"quantityValue": 0,
"referenceUnit": null,
"referenceValue": 0
}
},
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC8zOTQwMzYxODM2OTY2NQ==",
"title": "plum",
"price": "24.00",
"priceV2": {
"amount": "24.95",
"currencyCode": "EUR"
},
"weight": 0.8201,
"quantityAvailable": 8,
"available": true,
"sku": "aerodynamic-granite-watch-plum",
"compareAtPrice": null,
"compareAtPriceV2": null,
"image": {
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0SW1hZ2UvMjg2NTUxNTUyNDkyODE=",
"src": "https://cdn.shopify.com/s/files/1/0553/3938/4961/products/source-404_41b0b7f1-e8b2-4e18-9068-083d3364fa58.jpg?v=1661754872",
"altText": "Bora Horza Gobuchul"
},
"selectedOptions": [
{
"name": "Color",
"value": "plum"
}
],
"unitPrice": null,
"unitPriceMeasurement": {
"measuredType": null,
"quantityUnit": null,
"quantityValue": 0,
"referenceUnit": null,
"referenceValue": 0
}
}
]
}