Skip to content

Commit

Permalink
fix(vue-demo-store): use locale from accept-language header on SSR (#284
Browse files Browse the repository at this point in the history
)
  • Loading branch information
mkucmus authored Jul 3, 2023
1 parent 14d97c5 commit bb48e13
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 46 deletions.
6 changes: 6 additions & 0 deletions .changeset/four-months-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"vue-demo-store": patch
"docs": patch
---

Set default locale from accept-language header in SSR
5 changes: 5 additions & 0 deletions .changeset/tame-toys-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/composables-next": minor
---

Use function constructor instead of init method to set config for price displaying
5 changes: 2 additions & 3 deletions apps/docs/src/packages/composables/usePrice.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ category: CMS
Internally, `usePrice` composable uses [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) in order to format a price according to the right currency standard, for corresponding locale and symbol.

```js
const { init, getFormattedPrice } = usePrice();
init({
const { getFormattedPrice } = usePrice({
currencyCode: 'EUR'
localeCode: 'de-DE' // value taken from browser's navigator.language variable if localeCode is not provided
})
});

const regularPrice = getFormattedPrice(49.95);
// regularPrice: '49,95 €'
Expand Down
29 changes: 20 additions & 9 deletions packages/composables/src/usePrice.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { describe, expect, it } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createApp } from "vue";
import { usePrice } from "./usePrice";

export function withSetup(composable: any) {
let result;

vi.mock("./useSessionContext.ts", () => ({
useSessionContext() {
return {
sessionContext: {
currency: {
isoCode: "EUR",
},
},
};
},
}));

const app = createApp({
setup() {
result = composable();
Expand All @@ -12,15 +25,13 @@ export function withSetup(composable: any) {
return () => {};
},
});

app.mount(document.createElement("div"));
// return the result and the app instance
// for testing provide / unmount
return [result, app];
}

describe("usePrice", () => {
const { init, getFormattedPrice } = usePrice();
init({
const { getFormattedPrice, update } = usePrice({
localeCode: "en-US",
currencyCode: "USD",
});
Expand All @@ -34,19 +45,19 @@ describe("usePrice", () => {
});

it("should update config", () => {
init({
update({
localeCode: "de-DE",
currencyCode: "EUR",
});
// applied workaround for non-breaking space that is inserted by Intl.NumberFormat
expect(getFormattedPrice(4.1).replace(/\s/g, " ")).toBe("4,10 €");
});

it("should return price with language locale code taken from navigator", () => {
init({
it("should return price with current locale", () => {
update({
currencyCode: "USD",
currencyLocale: undefined,
} as any);
expect(getFormattedPrice(2.55)).toStrictEqual(`$2.55`);
expect(getFormattedPrice(2.55).replace(/\s/g, " ")).toStrictEqual(`2,55 $`);
});
});
63 changes: 44 additions & 19 deletions packages/composables/src/usePrice.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,57 @@
import { ref } from "vue";
import { ref, watch } from "vue";
import { createSharedComposable } from "@vueuse/core";
import { useSessionContext } from "./useSessionContext";

export type UsePriceReturn = {
/**
* Set init data: localeCode & currencyCode
*/
init(options: { localeCode: string | undefined; currencyCode: string }): void;
/**
* Format price i.e. (2) -> 2.00 $
*/
getFormattedPrice(value: number | string | undefined): string;
/**
* Update configuration
*/
update(params: {
localeCode: string | undefined;
currencyCode: string;
}): void;
};

/**
* Composable for getting formatted price
* Set the default currency code and locale in order to format a price correctly
*
* @public
* @category Product
*/
function _usePrice(): UsePriceReturn {
const currencyLocale = ref<string>("");
function _usePrice(params?: {
localeCode: string | undefined;
currencyCode: string;
}): UsePriceReturn {
const { sessionContext } = useSessionContext();
const currencyLocale = ref<string | undefined>();
const currencyCode = ref<string>("");

// TODO: make sure why there is no decimal precision in api response
const decimalPrecision = 2;
/**
* Set init data from backend response
*
* as a fallback for params.localeCode is navigator?.language
* @param params
*/
function init(params: {
localeCode: string | undefined;
if (params) {
currencyCode.value = params.currencyCode;
currencyLocale.value = params.localeCode;
}

function update(params: {
localeCode?: string | undefined;
currencyCode: string;
}): void {
}) {
_setCurrencyCode(params.currencyCode);
_setLocaleCode(
params.localeCode ||
currencyLocale.value ||
(typeof navigator !== "undefined" && navigator?.language) ||
"en-US"
);
}

// TODO: make sure why there is no decimal precision in api response
const decimalPrecision = 2;

function _setCurrencyCode(code: string) {
currencyCode.value = code;
}
Expand All @@ -67,9 +78,23 @@ function _usePrice(): UsePriceReturn {
}).format(+value);
}

watch(
() => sessionContext.value?.currency,
(newCurrency) => {
if (!!newCurrency)
update({
// locale code is read only once on SSR because it's unavailable in the context
currencyCode: newCurrency?.isoCode as string,
});
},
{
immediate: true,
}
);

return {
init,
getFormattedPrice,
update,
};
}

Expand Down
16 changes: 1 addition & 15 deletions packages/composables/src/useSessionContext.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { computed, ComputedRef, inject, provide, Ref, ref } from "vue";
import { computed, ComputedRef } from "vue";
import {
ShippingMethod,
PaymentMethod,
Expand All @@ -20,7 +20,6 @@ import {
setCurrentLanguage,
} from "@shopware-pwa/api-client";
import { useShopwareContext } from "./useShopwareContext";
import { usePrice } from "./usePrice";
import { _useContext } from "./internal/_useContext";

export type UseSessionContextReturn = {
Expand Down Expand Up @@ -108,14 +107,6 @@ export function useSessionContext(
newContext?: SessionContext
): UseSessionContextReturn {
const { apiInstance } = useShopwareContext();
const { init } = usePrice();

if (newContext) {
init({
currencyCode: newContext.currency?.isoCode,
localeCode: newContext.salesChannel?.language?.locale?.code,
});
}

const _sessionContext = _useContext("swSessionContext", {
replace: newContext,
Expand All @@ -126,11 +117,6 @@ export function useSessionContext(
try {
const context = await getSessionContext(apiInstance);
_sessionContext.value = context;

init({
currencyCode: context.currency?.isoCode,
localeCode: context.salesChannel?.language?.locale?.code,
});
} catch (e) {
console.error("[UseSessionContext][refreshSessionContext]", e);
}
Expand Down
11 changes: 11 additions & 0 deletions templates/vue-demo-store/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ const { data: sessionContextData } = await useAsyncData(
}
);
// read the locale from accept-language header (i.e. en-GB or de-DE)
// and set configuration for price formatting globally
const headers = useRequestHeaders();
const localeFromHeader = headers?.["accept-language"]
?.split(",")
?.find((languageConfig) => languageConfig.match(/([a-z]){2}\-([A-Z]{2})/));
usePrice({
currencyCode: sessionContextData.value?.currency?.isoCode || "",
localeCode: localeFromHeader,
});
useSessionContext(sessionContextData.value as SessionContext);
const { getWishlistProducts } = useWishlist();
Expand Down

2 comments on commit bb48e13

@vercel
Copy link

@vercel vercel bot commented on bb48e13 Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

frontends-demo – ./templates/vue-demo-store

frontends-demo-git-main-shopware-frontends.vercel.app
frontends-demo-shopware-frontends.vercel.app
frontends-demo.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bb48e13 Jul 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.