diff --git a/src/api/language.js b/src/api/language.js index e5c457b55..f8bc6e08e 100644 --- a/src/api/language.js +++ b/src/api/language.js @@ -47,3 +47,15 @@ export function cardinal(number) { const rem = number % 100; return number + (suffixes[(rem - 20) % 10] || suffixes[rem] || suffixes[0]); } + +export function currencyString(number, metadata, maximumFractionDigits) { + return number.toLocaleString("en-US", { + style: "currency", + currency: metadata.countryId === "uk" ? "GBP" : "USD", + maximumFractionDigits: maximumFractionDigits, + }); +} + +export function localeString(metadata) { + return metadata.countryId === "uk" ? "en" : "en-US"; +} diff --git a/src/pages/policy/output/AverageImpactByDecile.jsx b/src/pages/policy/output/AverageImpactByDecile.jsx index e4015d9d5..73fa6539c 100644 --- a/src/pages/policy/output/AverageImpactByDecile.jsx +++ b/src/pages/policy/output/AverageImpactByDecile.jsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import Plot from "react-plotly.js"; import { ChartLogo } from "../../../api/charts"; -import { cardinal } from "../../../api/language"; +import { cardinal, currencyString, localeString } from "../../../api/language"; import { formatVariableValue } from "../../../api/variables"; import HoverCard, { HoverCardContext } from "../../../layout/HoverCard"; import useMobile from "../../../layout/Responsive"; @@ -33,11 +33,7 @@ export default function AverageImpactByDecile(props) { value < 0 ? style.colors.DARK_GRAY : style.colors.BLUE, ), }, - text: yArray.map( - (value) => - metadata.currency + - value.toLocaleString("en-GB", { maximumFractionDigits: 0 }), - ), + text: yArray.map((value) => currencyString(value, metadata, 0)), textangle: 0, ...(useHoverCard ? { @@ -77,8 +73,7 @@ export default function AverageImpactByDecile(props) { }, yaxis: { title: "Average change", - tickprefix: metadata.countryId === "uk" ? "£" : "$", - tickformat: ",.0f", + tickformat: "$,.0f", }, ...(useHoverCard ? {} @@ -107,6 +102,7 @@ export default function AverageImpactByDecile(props) { config={{ displayModeBar: false, responsive: true, + locale: localeString(metadata), }} style={{ width: "100%", diff --git a/src/pages/policy/output/AverageImpactByWealthDecile.jsx b/src/pages/policy/output/AverageImpactByWealthDecile.jsx index d80e8ebdb..28a13def7 100644 --- a/src/pages/policy/output/AverageImpactByWealthDecile.jsx +++ b/src/pages/policy/output/AverageImpactByWealthDecile.jsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import Plot from "react-plotly.js"; import { ChartLogo } from "../../../api/charts"; -import { cardinal } from "../../../api/language"; +import { cardinal, currencyString, localeString } from "../../../api/language"; import { formatVariableValue } from "../../../api/variables"; import HoverCard, { HoverCardContext } from "../../../layout/HoverCard"; import useMobile from "../../../layout/Responsive"; @@ -33,11 +33,7 @@ export default function AverageImpactByWealthDecile(props) { value < 0 ? style.colors.DARK_GRAY : style.colors.BLUE, ), }, - text: yArray.map( - (value) => - metadata.currency + - value.toLocaleString("en-GB", { maximumFractionDigits: 0 }), - ), + text: yArray.map((value) => currencyString(value, metadata, 0)), textangle: 0, ...(useHoverCard ? { @@ -77,8 +73,7 @@ export default function AverageImpactByWealthDecile(props) { }, yaxis: { title: "Average change", - tickprefix: metadata.countryId === "uk" ? "£" : "$", - tickformat: ",.0f", + tickformat: "$,.0f", }, ...(useHoverCard ? {} @@ -107,6 +102,7 @@ export default function AverageImpactByWealthDecile(props) { config={{ displayModeBar: false, responsive: true, + locale: localeString(metadata), }} style={{ width: "100%", diff --git a/src/pages/policy/output/BudgetaryImpact.jsx b/src/pages/policy/output/BudgetaryImpact.jsx index b80723a53..a29d992be 100644 --- a/src/pages/policy/output/BudgetaryImpact.jsx +++ b/src/pages/policy/output/BudgetaryImpact.jsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import Plot from "react-plotly.js"; import { ChartLogo } from "../../../api/charts"; -import { aggregateCurrency } from "../../../api/language"; +import { aggregateCurrency, localeString } from "../../../api/language"; import HoverCard, { HoverCardContext } from "../../../layout/HoverCard"; import useMobile from "../../../layout/Responsive"; import DownloadableScreenshottable from "./DownloadableScreenshottable"; @@ -129,8 +129,7 @@ export default function BudgetaryImpact(props) { }, yaxis: { title: "Budgetary impact (bn)", - tickprefix: metadata.currency, - tickformat: ",.1f", + tickformat: "$,.1f", }, ...(useHoverCard ? {} @@ -157,6 +156,7 @@ export default function BudgetaryImpact(props) { config={{ displayModeBar: false, responsive: true, + locale: localeString(metadata), }} style={{ width: "100%", diff --git a/src/pages/policy/output/DetailedBudgetaryImpact.jsx b/src/pages/policy/output/DetailedBudgetaryImpact.jsx index 16ec218d2..8e3de98d2 100644 --- a/src/pages/policy/output/DetailedBudgetaryImpact.jsx +++ b/src/pages/policy/output/DetailedBudgetaryImpact.jsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import Plot from "react-plotly.js"; import { ChartLogo } from "../../../api/charts"; -import { aggregateCurrency } from "../../../api/language"; +import { aggregateCurrency, localeString } from "../../../api/language"; import HoverCard, { HoverCardContext } from "../../../layout/HoverCard"; import useMobile from "../../../layout/Responsive"; import Screenshottable from "../../../layout/Screenshottable"; @@ -100,8 +100,7 @@ export default function DetailedBudgetaryImpact(props) { }, yaxis: { title: "Budgetary impact (bn)", - tickprefix: metadata.currency, - tickformat: ",.1f", + tickformat: "$,.1f", }, ...(useHoverCard ? {} @@ -130,6 +129,7 @@ export default function DetailedBudgetaryImpact(props) { config={{ displayModeBar: false, responsive: true, + locale: localeString(metadata), }} style={{ width: "100%", diff --git a/src/plotly_locales/locale-en-us.js b/src/plotly_locales/locale-en-us.js new file mode 100644 index 000000000..b978fa14c --- /dev/null +++ b/src/plotly_locales/locale-en-us.js @@ -0,0 +1,12 @@ +// same as plotly.js/src/locale-en-us.js except currency symbol + +"use strict"; + +module.exports = { + moduleType: "locale", + name: "en-US", + format: { + date: "%m/%d/%Y", + currency: ["$", ""], + }, +}; diff --git a/src/plotly_locales/locale-en.js b/src/plotly_locales/locale-en.js new file mode 100644 index 000000000..84e6bfaed --- /dev/null +++ b/src/plotly_locales/locale-en.js @@ -0,0 +1,63 @@ +// same as plotly.js/src/locale-en.js except currency symbol + +"use strict"; + +module.exports = { + moduleType: "locale", + name: "en", + dictionary: { + "Click to enter Colorscale title": "Click to enter Colourscale title", + }, + format: { + days: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + shortMonths: [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + periods: ["AM", "PM"], + dateTime: "%a %b %e %X %Y", + date: "%d/%m/%Y", + time: "%H:%M:%S", + decimal: ".", + thousands: ",", + grouping: [3], + currency: ["£", ""], + year: "%Y", + month: "%b %Y", + dayMonth: "%b %-d", + dayMonthYear: "%b %-d, %Y", + }, +}; diff --git a/src/redesign/components/PolicyEngine.jsx b/src/redesign/components/PolicyEngine.jsx index 2533d5528..9eae5bf59 100644 --- a/src/redesign/components/PolicyEngine.jsx +++ b/src/redesign/components/PolicyEngine.jsx @@ -19,6 +19,8 @@ import Header from "./Header"; import Testimonials from "./Testimonials"; import CalculatorInterstitial from "./CalculatorInterstitial"; import CitizensEconomicCouncil from "./CitizensEconomicCouncil"; +import loc_en from "../../plotly_locales/locale-en.js"; +import loc_en_us from "../../plotly_locales/locale-en-us.js"; const PolicyPage = lazy(() => import("../../pages/PolicyPage")); const HouseholdPage = lazy(() => import("../../pages/HouseholdPage")); @@ -34,6 +36,9 @@ function ScrollToTop() { } export default function PolicyEngine({ pathname }) { + var Plotly = require("plotly.js/dist/plotly.js"); + Plotly.register(loc_en); + Plotly.register(loc_en_us); const COUNTRIES = ["us", "uk", "ca", "ng", "il"]; // First, check if the country is specified (.org/[country]/...) diff --git a/src/redesign/components/TextBox.jsx b/src/redesign/components/TextBox.jsx index d7b5efc4a..9fa2b047b 100644 --- a/src/redesign/components/TextBox.jsx +++ b/src/redesign/components/TextBox.jsx @@ -38,7 +38,7 @@ export default function TextBox({ placeholder={placeholder} id={id} enterKeyHint={enterKeyHint} - onChange={(e) => onChange(e.target.value)} + onChange={(e) => onChange?.(e.target.value)} style={{ border: "none", borderBottom: `1px solid ${style.colors.WHITE}`,