Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ _Returns_

- `boolean`: Whether locale is RTL.

### numberFormatI18n

Format a number according to the current locale.

_Parameters_

- _number_ `number`: The number to format.
- _decimals_ The number of decimal places to include.

_Returns_

- The formatted number as a string.

### resetLocaleData

Resets all current Tannin instance locale data and sets the specified locale data for the domain. Accepts data in a Jed-formatted JSON object shape.
Expand Down
56 changes: 56 additions & 0 deletions packages/i18n/src/create-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Tannin from 'tannin';
import type {
getFilterDomain,
I18n,
I18nDomainMetadata,
LocaleData,
SubscribeCallback,
TranslatableText,
Expand Down Expand Up @@ -36,6 +37,22 @@ const DEFAULT_LOCALE_DATA: LocaleData = {
*/
const I18N_HOOK_REGEXP = /^i18n\.(n?gettext|has_translation)(_|$)/;

/**
* Validates a locale string using Intl.getCanonicalLocales.
* Returns true if the locale is valid, false otherwise.
*
* @param locale The locale string to validate.
* @return Whether the locale is valid.
*/
const isValidLocale = ( locale: string ): boolean => {
try {
Intl.getCanonicalLocales( locale );
return true;
} catch {
return false;
}
};

/**
* Create an i18n instance
*
Expand Down Expand Up @@ -389,6 +406,44 @@ export const createI18n = < TextDomain extends string >(
hooks.addAction( 'hookRemoved', 'core/i18n', onHookAddedOrRemoved );
}

const numberFormatI18n: I18n[ 'numberFormatI18n' ] = (
number,
decimals = 0,
domain = 'default' as TextDomain
) => {
const localeData = ( getLocaleData( domain as TextDomain )?.[
''
] as I18nDomainMetadata< TextDomain > ) ?? {
lang: 'en_US',
domain: 'default',
plural_forms: 'n === 1 ? 0 : 1',
};

const wpLocale = localeData.lang || 'en_US';
// Convert WordPress locale format to JavaScript locale format by replacing underscores with hyphens
let jsLocale = wpLocale.replace( /_/g, '-' );

// Validate the locale using Intl.getCanonicalLocales and fallback to en-US if invalid
if ( ! isValidLocale( jsLocale ) ) {
jsLocale = 'en-US';
}

/**
* Range limit of `maximumFractionDigits` in Node.js is 0-20
* Max limit of `maximumFractionDigits` in Browsers is 0-100 ( {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#maximumfractiondigits} )
*
* Hence we limit the decimals to 20 to ensure compatibility across environments.
*/
const validDecimals = Math.max( 0, Math.min( decimals, 20 ) );
Comment on lines +431 to +437
Copy link
Member

Choose a reason for hiding this comment

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

What's the motivation for this? Is it really our responsibility to clamp this value? Seems more like the consumer's job.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason behind this was that during testing I found out that while Browser supported upto 100 decimals , Nodejs was limited to around 20 decimal points and anything above that it would throw an error saying invalid range.
Hence I thought of adding clamp to this to prevent this error.


const options: Intl.NumberFormatOptions = {
minimumFractionDigits: validDecimals,
maximumFractionDigits: validDecimals,
};

return new Intl.NumberFormat( jsLocale, options ).format( number );
};

return {
getLocaleData,
setLocaleData,
Expand All @@ -401,5 +456,6 @@ export const createI18n = < TextDomain extends string >(
_nx,
isRTL,
hasTranslation,
numberFormatI18n,
};
};
11 changes: 11 additions & 0 deletions packages/i18n/src/default-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,14 @@ export const isRTL = i18n.isRTL.bind( i18n );
* @return {boolean} Whether the translation exists or not.
*/
export const hasTranslation = i18n.hasTranslation.bind( i18n );

/**
* Format a number according to the current locale.
*
* @param number The number to format.
* @param [decimals] The number of decimal places to include.
*
* @return The formatted number as a string.
*/
export const numberFormatI18n = ( number: number, decimals = 0 ) =>
i18n.numberFormatI18n( number, decimals, 'default' );
15 changes: 0 additions & 15 deletions packages/i18n/src/index.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/i18n/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export {
_nx,
isRTL,
hasTranslation,
numberFormatI18n,
} from './default-i18n';
Loading
Loading