overleaf/services/web/frontend/js/features/plans/utils/group-plan-pricing.js
Antoine Clausse b2ef7a935f [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
2024-04-19 08:03:54 +00:00

84 lines
2.1 KiB
JavaScript

import getMeta from '../../../utils/meta'
/**
* @typedef {import('@/shared/utils/currency').CurrencyCode} CurrencyCode
*/
// plan: 'collaborator' or 'professional'
// the rest of available arguments can be seen in the groupPlans value
/**
* @param {'collaborator' | 'professional'} plan
* @param {string} licenseSize
* @param {CurrencyCode} currency
* @param {'enterprise' | 'educational'} usage
* @param {string?} locale
* @param {(amount: number, currency: CurrencyCode, locale: string, includeSymbol: boolean) => string} formatCurrency
* @returns {{localizedPrice: string, localizedPerUserPrice: string}}
*/
export function createLocalizedGroupPlanPrice({
plan,
licenseSize,
currency,
usage,
locale = window.i18n.currentLangCode || 'en',
formatCurrency,
}) {
const groupPlans = getMeta('ol-groupPlans')
const priceInCents =
groupPlans[usage][plan][currency][licenseSize].price_in_cents
const price = priceInCents / 100
const perUserPrice = price / parseInt(licenseSize)
/**
* @param {number} price
* @returns {string}
*/
const formatPrice = price => formatCurrency(price, currency, locale, true)
return {
localizedPrice: formatPrice(price),
localizedPerUserPrice: formatPrice(perUserPrice),
}
}
const LOCALES = {
BRL: 'pt-BR',
MXN: 'es-MX',
COP: 'es-CO',
CLP: 'es-CL',
PEN: 'es-PE',
}
/**
* @param {number} amount
* @param {string} currency
*/
export function formatCurrencyDefault(amount, currency) {
const currencySymbols = getMeta('ol-currencySymbols')
const currencySymbol = currencySymbols[currency]
switch (currency) {
case 'BRL':
case 'MXN':
case 'COP':
case 'CLP':
case 'PEN':
// Test using toLocaleString to format currencies for new LATAM regions
return amount.toLocaleString(LOCALES[currency], {
style: 'currency',
currency,
minimumFractionDigits: Number.isInteger(amount) ? 0 : null,
})
case 'CHF':
return `${currencySymbol} ${amount}`
case 'DKK':
case 'SEK':
case 'NOK':
return `${amount} ${currencySymbol}`
default: {
return `${currencySymbol}${amount}`
}
}
}