[web] Use localized number formatting for currencies (#17622)

* Add a unit test on `SubscriptionFormatters.formatPrice`

* Add JSDoc to `formatPrice`

Also: Name the functions before exporting:
This fixes my IDE (WebStorm) navigation

* Make `'USD'` the default param instead of reassigning

* Create `formatCurrency` function

* Use `formatCurrency` in SubscriptionFormatters

* Use an `isNoCentsCurrency` logic for `CLP` `JPY` `KRW` `VND`

And remove custom `CLP` logic and locale

* Add `locale` param to `formatPrice`

* Generate `groups.json` and `localizedPlanPricing.json`

```
bin/exec web node ./scripts/recurly/recurly_prices.js --download -o prices.json
bin/exec web node ./scripts/plan-prices/plans.js -f ../../prices.json -o dir
```

* Update scripts/plan-prices/plans.js to generate numbers instead of localized amounts

* Generate `groups.json` and `localizedPlanPricing.json`

```
bin/exec web node ./scripts/recurly/recurly_prices.js --download -o prices.json
bin/exec web node ./scripts/plan-prices/plans.js -f ../../prices.json -o dir
```

* Remove generation of `plans.json`

As /services/web/frontend/js/main/plans.js was removed in https://github.com/overleaf/internal/pull/12593

* Sort currencies in alphabetical order in scripts/plan-prices/plans.js

* Generate `groups.json` and `localizedPlanPricing.json`

```
bin/exec web node ./scripts/recurly/recurly_prices.js --download -o prices.json
bin/exec web node ./scripts/plan-prices/plans.js -f ../../prices.json -o dir
```

* Use `formatCurrency` in price-summary.tsx

* Use `formatCurrency` in Subscription Pug files

* Fix unit tests SubscriptionHelperTests.js

* Remove unused `currencySymbol`

* Change to `formatCurrency` in other React components

* Add `CurrencyCode` JSDoc types

* Duplicate `formatCurrency` into services/web/app/src/util

* Wrap tests in a top-level describe block

* Use `narrowSymbol`

* Fix tests with `narrowSymbol` expects

* Revert deletion of old `formatPrice` in SubscriptionFormatters.js

* Rename `formatCurrency` -> `formatCurrencyLocalized`

* Revert deletion of `CurrencySymbol`

* Add split-test in SubscriptionController.js

* Add split-test in SubscriptionViewModelBuilder.js

* Add split-test in plans

* Add split-test in subscription-dashboard-context.tsx

* Add split-test in 4 more components

* Update tests

* Show currency and payment methods in interstitial page

* Fix `–` being printed. Use `–` instead

* Fix test with NOK

* Storybook: Fix missing `SplitTestProvider`

* Storybook: Revert "Remove unused `currencySymbol`"

This reverts commit e55387d4753f97bbf8e39e0fdc3ad17312122aaa.

* Replace `getSplitTestVariant` by `useSplitTestContext`

* Use parameterize currencyFormat in `generateInitialLocalizedGroupPrice`

* Fixup import paths of `formatCurrencyLocalized`

* Replace `% 1 === 0` by `Number.isInteger`

* Add comment explaining that any combinations of languages/currencies could happen

* Fixup after rebase: import `useSplitTestContext`

* Revert "Remove SplitTestProvider from subscription root"

This reverts commit be9f378fda715b86589ab0759737581c72321d87.

* Revert "Remove split test provider from some tests"

This reverts commit 985522932b550cfd38fa6a4f4c3d2ebaee6ff7df.

GitOrigin-RevId: 59a83cbbe0f7cc7e45f189c654e23fcf9bfa37af
This commit is contained in:
Antoine Clausse
2024-04-19 08:03:54 +00:00
committed by Copybot
parent 76955c814a
commit b2ef7a935f
22 changed files with 985 additions and 437 deletions
@@ -9,7 +9,6 @@ The scripts will put the output results into the `output` folder.
_Command_ `node plans.js -f fileName -o outputdir` - generates three json files:
- `localizedPlanPricing.json` for `/services/web/config/settings.overrides.saas.js`
- `plans.json` for `/services/web/frontend/js/main/plans.js`
- `groups.json` for `/services/web/app/templates/plans/groups.json`
The input file can be in `.csv` or `.json` format
+27 -135
View File
@@ -39,118 +39,38 @@ const plansMap = {
professional: 'professional',
}
const currencies = {
USD: {
symbol: '$',
placement: 'before',
},
EUR: {
symbol: '€',
placement: 'before',
},
GBP: {
symbol: '£',
placement: 'before',
},
SEK: {
symbol: ' kr',
placement: 'after',
},
CAD: {
symbol: '$',
placement: 'before',
},
NOK: {
symbol: ' kr',
placement: 'after',
},
DKK: {
symbol: ' kr',
placement: 'after',
},
AUD: {
symbol: '$',
placement: 'before',
},
NZD: {
symbol: '$',
placement: 'before',
},
CHF: {
symbol: 'Fr ',
placement: 'before',
},
SGD: {
symbol: '$',
placement: 'before',
},
INR: {
symbol: '₹',
placement: 'before',
},
BRL: {
code: 'BRL',
locale: 'pt-BR',
symbol: 'R$ ',
placement: 'before',
},
MXN: {
code: 'MXN',
locale: 'es-MX',
symbol: '$ ',
placement: 'before',
},
COP: {
code: 'COP',
locale: 'es-CO',
symbol: '$ ',
placement: 'before',
},
CLP: {
code: 'CLP',
locale: 'es-CL',
symbol: '$ ',
placement: 'before',
},
PEN: {
code: 'PEN',
locale: 'es-PE',
symbol: 'S/ ',
placement: 'before',
},
}
const buildCurrencyValue = (amount, currency) => {
// Test using toLocaleString to format currencies for new LATAM regions
if (currency.locale && currency.code) {
return amount.toLocaleString(currency.locale, {
style: 'currency',
currency: currency.code,
minimumFractionDigits: 0,
})
}
return currency.placement === 'before'
? `${currency.symbol}${amount}`
: `${amount}${currency.symbol}`
}
const currencies = [
'AUD',
'BRL',
'CAD',
'CHF',
'CLP',
'COP',
'DKK',
'EUR',
'GBP',
'INR',
'MXN',
'NOK',
'NZD',
'PEN',
'SEK',
'SGD',
'USD',
]
function generatePlans(workSheetJSON) {
// localizedPlanPricing object for settings.overrides.saas.js
const localizedPlanPricing = {}
// plans object for main/plans.js
const plans = {}
for (const [currency, currencyDetails] of Object.entries(currencies)) {
for (const currency of currencies) {
localizedPlanPricing[currency] = {
symbol: currencyDetails.symbol.trim(),
free: {
monthly: buildCurrencyValue(0, currencyDetails),
annual: buildCurrencyValue(0, currencyDetails),
monthly: 0,
annual: 0,
},
}
plans[currency] = {
symbol: currencyDetails.symbol.trim(),
}
for (const [outputKey, actualKey] of Object.entries(plansMap)) {
const monthlyPlan = workSheetJSON.find(
@@ -174,24 +94,17 @@ function generatePlans(workSheetJSON) {
`Missing currency "${currency}" for plan "${actualKeyAnnual}"`
)
const monthly = buildCurrencyValue(monthlyPlan[currency], currencyDetails)
const monthlyTimesTwelve = buildCurrencyValue(
monthlyPlan[currency] * 12,
currencyDetails
)
const annual = buildCurrencyValue(annualPlan[currency], currencyDetails)
const monthly = Number(monthlyPlan[currency])
const monthlyTimesTwelve = Number(monthlyPlan[currency] * 12)
const annual = Number(annualPlan[currency])
localizedPlanPricing[currency] = {
...localizedPlanPricing[currency],
[outputKey]: { monthly, monthlyTimesTwelve, annual },
}
plans[currency] = {
...plans[currency],
[outputKey]: { monthly, annual },
}
}
}
return { localizedPlanPricing, plans }
return { localizedPlanPricing }
}
function generateGroupPlans(workSheetJSON) {
@@ -199,25 +112,6 @@ function generateGroupPlans(workSheetJSON) {
data.plan_code.startsWith('group')
)
const currencies = [
'AUD',
'BRL',
'CAD',
'CHF',
'CLP',
'COP',
'DKK',
'EUR',
'GBP',
'INR',
'MXN',
'NOK',
'NZD',
'SEK',
'SGD',
'USD',
'PEN',
]
const sizes = ['2', '3', '4', '5', '10', '20', '50']
const result = {}
@@ -275,7 +169,7 @@ function writeFile(outputFile, data) {
fs.writeFileSync(outputFile, data)
}
const { localizedPlanPricing, plans } = generatePlans(input)
const { localizedPlanPricing } = generatePlans(input)
const groupPlans = generateGroupPlans(input)
if (argv.output) {
@@ -291,10 +185,8 @@ if (argv.output) {
process.exit(1)
}
writeFile(`${dir}/localizedPlanPricing.json`, formatJS(localizedPlanPricing))
writeFile(`${dir}/plans.json`, formatJS(plans))
writeFile(`${dir}/groups.json`, formatJSON(groupPlans))
} else {
console.log('PLANS', plans)
console.log('LOCALIZED', localizedPlanPricing)
console.log('GROUP PLANS', JSON.stringify(groupPlans, null, 2))
}