From 2b1b2054e3da18d47132eccc60cc17f718592077 Mon Sep 17 00:00:00 2001 From: Mathias Oterhals Myklebust Date: Thu, 12 Sep 2024 14:45:35 +0200 Subject: [PATCH] feat(i18n): format currency using Sanity site locale and currency fields --- src/app/(main)/[slug]/page.tsx | 20 ++++++++-- src/compensations/Compensations.tsx | 34 +++++++++++++--- src/compensations/CompensationsPreview.tsx | 25 ++++++++++-- .../yearlyBonuses/YearlyBonuses.tsx | 13 ++++++- src/utils/i18n.ts | 9 +++++ studio/lib/payloads/companyDetails.ts | 2 + studio/schemas/documents/companyInfo.ts | 25 ++++++++++-- studio/utils/currencies.ts | 10 +++++ studio/utils/locales.ts | 39 +++++++++++++++++++ 9 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 src/utils/i18n.ts create mode 100644 studio/utils/currencies.ts create mode 100644 studio/utils/locales.ts diff --git a/src/app/(main)/[slug]/page.tsx b/src/app/(main)/[slug]/page.tsx index dfe51b3cf..daee6fef3 100644 --- a/src/app/(main)/[slug]/page.tsx +++ b/src/app/(main)/[slug]/page.tsx @@ -18,8 +18,14 @@ import { loadQuery } from "studio/lib/store"; import CompensationsPreview from "src/compensations/CompensationsPreview"; import { homeLink } from "../../../blog/components/utils/linkTypes"; import CustomErrorMessage from "../../../blog/components/customErrorMessage/CustomErrorMessage"; -import { CompanyLocation } from "studio/lib/payloads/companyDetails"; -import { COMPANY_LOCATIONS_QUERY } from "studio/lib/queries/companyDetails"; +import { + CompanyInfo, + CompanyLocation, +} from "studio/lib/payloads/companyDetails"; +import { + COMPANY_INFO_QUERY, + COMPANY_LOCATIONS_QUERY, +} from "studio/lib/queries/companyDetails"; export const dynamic = "force-dynamic"; @@ -52,6 +58,7 @@ async function Page({ params }: Props) { initialBlogPage, initialCompensationsPage, initialLocationsData, + initialCompanyInfo, ] = await Promise.all([ loadQuery(SLUG_QUERY, { slug }, { perspective }), loadQuery(BLOG_PAGE_QUERY, { slug }, { perspective }), @@ -61,6 +68,7 @@ async function Page({ params }: Props) { { perspective }, ), loadQuery(COMPANY_LOCATIONS_QUERY, {}, { perspective }), + loadQuery(COMPANY_INFO_QUERY, {}, { perspective }), ]); if (initialPage.data) { @@ -106,16 +114,22 @@ async function Page({ params }: Props) { ); } - if (initialCompensationsPage.data && initialLocationsData.data) { + if ( + initialCompensationsPage.data && + initialLocationsData.data && + initialCompanyInfo.data + ) { return isDraftMode ? ( ) : ( ); } diff --git a/src/compensations/Compensations.tsx b/src/compensations/Compensations.tsx index a7327d641..a83f7293b 100644 --- a/src/compensations/Compensations.tsx +++ b/src/compensations/Compensations.tsx @@ -13,17 +13,22 @@ import { minSalariesExaminationYear, salariesFromLocation, } from "./utils/salary"; -import { CompanyLocation } from "studio/lib/payloads/companyDetails"; +import { + CompanyInfo, + CompanyLocation, +} from "studio/lib/payloads/companyDetails"; import { IOption, RadioButtonGroup, } from "src/components/forms/radioButtonGroup/RadioButtonGroup"; import YearlyBonuses from "./components/yearlyBonuses/YearlyBonuses"; import BenefitsByLocation from "./components/benefitsByLocation/BenefitsByLocation"; +import { formatAsCurrency } from "../utils/i18n"; interface CompensationsProps { compensations: CompensationsPage; locations: CompanyLocation[]; + companyInfo: CompanyInfo; } interface SalaryCalculatorFormState { @@ -31,7 +36,11 @@ interface SalaryCalculatorFormState { selectedDegree: Degree; } -const Compensations = ({ compensations, locations }: CompensationsProps) => { +const Compensations = ({ + compensations, + locations, + companyInfo, +}: CompensationsProps) => { const [selectedLocation, setSelectedLocation] = useState( locations[0]._id, ); @@ -119,11 +128,23 @@ const Compensations = ({ compensations, locations }: CompensationsProps) => { /> {salary !== null ? (
- Du vil få en årlig lønn på {salary} + + {" "} + Du vil få en årlig lønn på{" "} + {formatAsCurrency( + salary, + companyInfo.siteMetadata.locale, + companyInfo.siteMetadata.currency, + )} + {compensations.pensionPercent && ( Du vil få en årlig pensjon på omtrent{" "} - {calculatePension(salary, compensations.pensionPercent)} + {formatAsCurrency( + calculatePension(salary, compensations.pensionPercent), + companyInfo.siteMetadata.locale, + companyInfo.siteMetadata.currency, + )} )}
@@ -131,7 +152,10 @@ const Compensations = ({ compensations, locations }: CompensationsProps) => { )} {yearlyBonusesForLocation && ( - + )} diff --git a/src/compensations/CompensationsPreview.tsx b/src/compensations/CompensationsPreview.tsx index 5a592e6d3..146f81ef8 100644 --- a/src/compensations/CompensationsPreview.tsx +++ b/src/compensations/CompensationsPreview.tsx @@ -3,18 +3,26 @@ import { Suspense } from "react"; import Compensations from "./Compensations"; import { QueryResponseInitial, useQuery } from "@sanity/react-loader"; import { CompensationsPage } from "studio/lib/payloads/compensations"; -import { CompanyLocation } from "studio/lib/payloads/companyDetails"; -import { COMPANY_LOCATIONS_QUERY } from "studio/lib/queries/companyDetails"; +import { + CompanyInfo, + CompanyLocation, +} from "studio/lib/payloads/companyDetails"; +import { + COMPANY_INFO_QUERY, + COMPANY_LOCATIONS_QUERY, +} from "studio/lib/queries/companyDetails"; import { COMPENSATIONS_PAGE_QUERY } from "studio/lib/queries/pages"; interface CompensationsPreviewProps { initialCompensations: QueryResponseInitial; initialLocations: QueryResponseInitial; + initialCompanyInfo: QueryResponseInitial; } const CompensationsPreview = ({ initialCompensations, initialLocations, + initialCompanyInfo, }: CompensationsPreviewProps) => { const { data } = useQuery( COMPENSATIONS_PAGE_QUERY, @@ -27,10 +35,19 @@ const CompensationsPreview = ({ { initial: initialLocations }, ); + const { data: companyInfo } = useQuery(COMPANY_INFO_QUERY, { + initial: initialCompanyInfo, + }); + return ( - locationData && ( + locationData && + companyInfo && ( - + ) ); diff --git a/src/compensations/components/yearlyBonuses/YearlyBonuses.tsx b/src/compensations/components/yearlyBonuses/YearlyBonuses.tsx index 78c65167f..0d41acb9b 100644 --- a/src/compensations/components/yearlyBonuses/YearlyBonuses.tsx +++ b/src/compensations/components/yearlyBonuses/YearlyBonuses.tsx @@ -1,12 +1,15 @@ import { BonusPage } from "studio/lib/payloads/compensations"; import Text from "../../../components/text/Text"; import styles from "./yearlyBonuses.module.css"; +import { formatAsCurrency } from "../../../utils/i18n"; +import { CompanyInfo } from "../../../../studio/lib/payloads/companyDetails"; interface YearlyBonusesProps { bonuses: BonusPage[]; + companyInfo: CompanyInfo; } -const YearlyBonuses = ({ bonuses }: YearlyBonusesProps) => { +const YearlyBonuses = ({ bonuses, companyInfo }: YearlyBonusesProps) => { return (
Historisk bonus @@ -30,7 +33,13 @@ const YearlyBonuses = ({ bonuses }: YearlyBonusesProps) => { {bonus.year} - {bonus.bonus} + + {formatAsCurrency( + bonus.bonus, + companyInfo.siteMetadata.locale, + companyInfo.siteMetadata.currency, + )} + ))} diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts new file mode 100644 index 000000000..68a2d62e4 --- /dev/null +++ b/src/utils/i18n.ts @@ -0,0 +1,9 @@ +export function formatAsCurrency( + number: number, + locale: string, + currency: string, +) { + return new Intl.NumberFormat(locale, { style: "currency", currency }).format( + number, + ); +} diff --git a/studio/lib/payloads/companyDetails.ts b/studio/lib/payloads/companyDetails.ts index 57cefdb2a..0e9a33a95 100644 --- a/studio/lib/payloads/companyDetails.ts +++ b/studio/lib/payloads/companyDetails.ts @@ -14,6 +14,8 @@ export interface BrandAssets { interface SiteMetadata { siteName: string; defaultLanguage: string; + locale: string; + currency: string; } export interface CompanyLocation { diff --git a/studio/schemas/documents/companyInfo.ts b/studio/schemas/documents/companyInfo.ts index 2e9b3c87f..863a05bc6 100644 --- a/studio/schemas/documents/companyInfo.ts +++ b/studio/schemas/documents/companyInfo.ts @@ -1,5 +1,7 @@ import { defineType, defineField } from "sanity"; import seo from "../objects/seo"; +import { relevantLocales } from "../../utils/locales"; +import { relevantCurrencies } from "../../utils/currencies"; export const companyInfoID = "companyInfo"; @@ -37,14 +39,31 @@ const companyInfo = defineType({ ], }, }), + defineField({ + name: "locale", + type: "string", + title: "Locale", + description: "Select the most relevant locale for the website.", + initialValue: relevantLocales[0].code, + options: { + list: relevantLocales.map(({ title, code }) => ({ + title: `${title} (${code})`, + value: code, + })), + }, + }), defineField({ name: "currency", type: "string", title: "Currency", - description: "Currency to be used for monetary values", + description: "Select the most relevant currency for the website.", + initialValue: relevantCurrencies[0], options: { - - } + list: relevantCurrencies.map((currency) => ({ + title: currency, + value: currency, + })), + }, }), defineField({ name: "contactEmail", diff --git a/studio/utils/currencies.ts b/studio/utils/currencies.ts new file mode 100644 index 000000000..9d8bb4aaf --- /dev/null +++ b/studio/utils/currencies.ts @@ -0,0 +1,10 @@ +// ISO 4217 currency codes (https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) +export const relevantCurrencies = [ + "NOK", + "SEK", + "EUR", + "GBP", + "USD", + "DKK", + "ISK", +]; diff --git a/studio/utils/locales.ts b/studio/utils/locales.ts new file mode 100644 index 000000000..c95e7c34b --- /dev/null +++ b/studio/utils/locales.ts @@ -0,0 +1,39 @@ +interface Locale { + title: string; + code: string; +} + +export const relevantLocales: Locale[] = [ + { + title: "Norway", + code: "nb-NO", + }, + { + title: "Norway", + code: "nn-NO", + }, + { + title: "Sweden", + code: "sv-SE", + }, + { + title: "United Kingdom", + code: "en-GB", + }, + { + title: "United States", + code: "en-US", + }, + { + title: "Denmark", + code: "da-DK", + }, + { + title: "Finland", + code: "fi-FI", + }, + { + title: "Iceland", + code: "is-IS", + }, +];