i18n formatting library
npm install @procore/globalization-toolkiti18n formatting library



Before implementing the Globalization Toolkit (GTK), please review the Globalization Toolkit Adoption Diagram to determine the best implementation strategy for you. Continue reading for instructions on direct usage of the GTK.
- Add new Locale to GTK
- GTK Adoption Training Presentation
- GTK SDR
- GTK FAQ
sh
$ npm i --save @procore/globalization-toolkit
`
YARN
`sh
$ yarn add @procore/globalization-toolkit
`
Usage
The GTK has 3 available classes: NumberFormatter, DateTimeFormatter, CurrencyFormatter. The structure of the GTK Library allows for instantiation of a formatting class with defined options. The instantiated class gives access to related functions. In addition, the GTK makes the class functions available as stand-alone functions. Below are examples of GTK usage with class formatter and with stand-alone functions.$3
The GTK supports industry standard locale codes, which means some locales used in the Procore app will not work with the GTK or may not work as expected. For example, the GTK does NOT support Procore Custom Locales. When implementing the GTK, please sanitize the locale to follow industry standard of like en-US.Suggested sanitizations:
| User Locale | Locale Passed to GTK | Reason |
|-|-|-|
|
en-international | en-GB | Custom locales are not supported |
| en-owner | en-US | Custom locales are not supported |
| en-owner-beta | en-US | Custom locales are not supported |
| en-specialty-contractor | en-US | Custom locales are not supported |
| en | en-US | en will work as expected, but prefer format |
| es | es-419 | es is used for Latin America in the monolith but will format numbers like es-ES in the GTK if the region is not specified |
$3
The GTK NumberFormatter class has the ability to format numbers for a given locale. The NumberFormatter class is instantiated with NumberOptions and has 4 available functions:
- formatNumber(value: number),
- formatNumberToParts(value: number),
- parseNumber(value: string), and
- getNumberSeparators(locale: string).The NumberOptions are summarized below:
-
locale: string
- maximumFractionDigits?: number
- minimumFractionDigits?: number
- percent?: 'standard', 'decimal'Below code blocks display usage of the NumberFormatter class and available functions.
#### formatNumber() & formatNumberToParts()
`ts
import { NumberFormatter } from '@procore/globalization-toolkit';const value = 1234;
const numberOptions = { locale: "en-US", minimumFractionDigits: 3 };
const formatter = new NumberFormatter(numberOptions);
console.log(formatter.formatNumber(value)) // expected output: '1,234.000'
console.log(formatter.formatNumberToParts(value))
/**
* expected output is an array of objects representing the parts of the formatted number:
* [
* { type: "integer", value: "1", },
* { type: "group", value: ",", },
* { type: "integer", value: "234", },
* { type: "decimal", value: ".", },
* { type: "fraction", value: "000", },
* ];
*/
`#### parseNumber()
`ts
import { NumberFormatter } from '@procore/globalization-toolkit';const value = "123 456 789,12";
const numberOptions = { locale: "fr-FR" };
const formatter = new NumberFormatter(numberOptions);
console.log(formatter.parseNumber(value)); // expected output: '123456789.12'
console.log(formatter.parseNumber("Abcd")); // expected output: NaN
`
#### getSeparators()`ts
import { NumberFormatter } from '@procore/globalization-toolkit';const germanNumberOptions = { locale: "de-DE" };
const germanFormatter = new NumberFormatter(germanNumberOptions);
console.log(germanFormatter.getSeparators()); // expected output: { decimal: ",", group: "." }
const usNumberOptions = { locale: 'en-US' };
const formatter = new NumberFormatter(usNumberOptions);
console.log(formatter.getSeparators()); // expected output: { decimal: ".", group: "," }
`
$3
The functions used on the NumberFormatter class are also available as stand alone functions. Read more information about each function in the GTK Documentation.
- formatNumber
- formatNumberToParts
- parseNumber
- getNumberSeparatorsThe example below shows usage of the stand alone function:
formatNumber().
`ts
import { formatNumber } from '@procore/globalization-toolkit';const value = 1234;
const numberOptions = { locale: "en-US", minimumFractionDigits: 3 };
console.log(formatNumber(value, numberOptions)) // expected output: '1,234.000'
`
$3
The GTK CurrencyFormatter class has the ability to format a number with given options. The CurrencyFormatter class is instantiated with CurrencyOptions.
The CurrencyFormatter has 5 available functions:
- formatCurrency(value: number),
- formatCurrencyToParts(value: number)
- parseCurrency(value: string)
- getCurrencySeparators()
- getSupportedCurrencies()The CurrencyOptions are summarized below:
-
currencyIsoCode: string (acceptable ISO codes)
- locale: string
- currencySign?: 'accounting', 'standard'
- currencyDisplay?: 'code', 'name', 'narrowSymbol', 'symbol'
- maximumFractionDigits?: number
- minimumFractionDigits?: numberBelow code blocks display usage of the CurrencyFormatter class and available functions.
`ts
const value = 1234;
const currencyOptions = { locale: "en-US", currencyIsoCode: "USD" };
const formatter = new CurrencyFormatter(currencyOptions);console.log(formatter.formatCurrency(value)) // expected outcome: '$1,234.00'
`
Usage with currencyDisplay option
`ts
import { CurrencyFormatter } from '@procore/globalization-toolkit';const value = 1234.567;
const currencyOptions: CurrencyOptions = {
locale: "de-DE",
currencyIsoCode: "USD",
currencyDisplay: "name",
};
const formatter = new CurrencyFormatter(currencyOptions);
console.log(formatter.formatCurrency(value)); // expected outcome: '1.234,57 US-Dollar'
`
Usage with minimumFractionDigits and currencyDisplay
`ts
const value = 1234.567;
const currencyOptions = {
locale: "ja-JP",
minimumFractionDigits: 2,
currencyIsoCode: "JPY",
};
const formatter = new CurrencyFormatter(currencyOptions);console.log(formatter.formatCurrency(value)); // expected outcome: '¥1,234.57'
`
formatCurrencyToParts()
`ts
import { CurrencyFormatter } from '@procore/globalization-toolkit';const value = -1234.56;
const currencyOptions: CurrencyOptions = {
locale: "en-US",
currencyIsoCode: "CAD",
currencySign: "accounting",
};
const formatter = new CurrencyFormatter(currencyOptions);
console.log(formatter.formatCurrencyToParts(value));
/** expected output:
* [
* { type: "literal", value: "(", },
* { type: "currency", value: "CA$", },
* { type: "integer", value: "1", },
* { type: "group", value: ",", },
* { type: "integer", value: "234", },
* { type: "decimal", value: ".", },
* { type: "fraction", value: "56", },
* { type: "literal", value: ")", },
* ]
**/
`
parseCurrency(value: string)
`ts
const value = "(1 234 567,56 €)";
const currencyOptions: CurrencyOptions = {
locale: "fr-FR",
currencyIsoCode: "EUR",
currencySign: "accounting",
};
const formatter = new CurrencyFormatter(currencyOptions);console.log(formatter.parseCurrency(value)); // expected outcome: -1234567.56
``ts
const currencyOptions = { locale: "de-DE", currencyIsoCode: "EUR" };
const formatter = new CurrencyFormatter(currencyOptions);console.log(formatter.getCurrencySeparators()) // expected outcome: { decimal: ",", group: "." }
``ts
const currencyOptions = { locale: "es-MX", currencyIsoCode: MXN" }
const formatter = new CurrencyFormatter(currencyOption)console.log(formatter.getSupportedCurrencies())
/** expected outcome
* [
* "AED",
* "AFN",
* "ALL",
* ...
* ...
* "ZWL"
* ]
*/
`
$3
The functions used on the CurrencyFormatter class are also available as stand alone functions. Read more information about each function in the GTK Documentation.
- formatCurrency
- formatCurrencyToParts
- parseCurrency
- getCurrencySeparatorsThe example below shows usage of the stand alone function:
formatCurrency(value, options).
`ts
import { formatCurrency, getCurrencySeparators } from '@procore/globalization-toolkit';const value = 1234;
const currencyOptions = { locale: 'en-US', currencyIsoCode: 'USD' };
console.log(formatCurrency(value, currencyOptions)) // expected output: '$1,234.00'
console.log(getCurrencySeparators(currencyOptions)) // expected output: { decimal: ".", group: "," }
`$3
The GTK DateTimeFormatter class has the ability to format date and time for the correct timezone. The DateTimeFormatter class is instantiated with DateTimeOptions.
The DateTimeFormatter has 3 available functions:
- formatDateTime(value: Date),
- formatDateTimeToParts(value: Date), and
- getStartDayOfTheWeek()$3
The DateTimeOptions can be used in two ways, but the two options cannot be combined. This follows the pattern of Intl.DateTimeFormat.
1. Style Options:
- timeStyle: 'short', 'medium', 'long', 'full'
- dateStyle: 'short', 'medium', 'long', 'full'
2. Component Options
- weekday: 'long', 'short', 'narrow'
- year: 'numeric', '2-digit'
- month: numeric', '2-digit', 'long', 'short', 'narrow'
- day: 'numeric', '2-digit'
- hour: 'numeric', '2-digit'
- minute: 'numeric', '2-digit'
- second: 'numeric', '2-digit'
$3
The following combinations of parameters are allowed for DateTime formatting:
1. Date Only:
-
year, month, day
- year, month, day, weekday2. Time Only:
-
hour, minute
- hour, minute, second3. Date and Time:
-
year, month, day, hour, minute, second, weekday
- year, month, day, hour, minute, weekday
- year, month, day, hour, minute, secondNote: The subsets of parameters must follow the Intl standards. The Intl.DateTimeFormat API, which GTK relies on, has specific requirements for including certain date and time components together to ensure proper formatting. For example, if you want to include
year, month, day, hour, and minute, you must also include weekday and second. This is because the Intl standards are designed to provide consistent and culturally appropriate formatting, and omitting certain components can lead to ambiguous or incorrect representations of date and time.Below code blocks display usage of the DateTimeFormatter class and available functions.
formatDateTime(value: Date)
SIMPLE OPTIONS
`ts
import { DateTimeFormatter } from '@procore/globalization-toolkit';const value = new Date(1445419756738);
const dateTimeOptions = {
locale: 'en-US',
timeZone: 'UTC',
dateStyle: 'full',
timeStyle: 'long',
};
const formatter = new DateTimeFormatter(dateTimeOptions);
console.log(formatter.formatDateTime(value))
// expected output: Wednesday, October 21, 2015 at 9:29:16 AM UTC
`
COMPLEX OPTIONS
`js
const value = new Date(Date.UTC(1885, 8, 1, 12, 0, 16, 738));
const dateTimeOptions = {
locale: 'en-US',
timeZone: 'UTC',
weekday: 'long',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: 'numeric',
minute: '2-digit',
second: '2-digit',
};
const formatter = new DateTimeFormatter(dateTimeOptions);console.log(formatter.formatDateTime(value))
// expected output: "Tuesday, 09/01/1885, 12:00:16 PM";
`
formatDateTimeToParts(value: Date)`ts
const value = new Date(Date.UTC(1955, 10, 5, 6, 0, 16, 738));
const dateTimeOptions = {
locale: "en-GB",
timeZone: "UTC",
dateStyle: DateStyle.FULL,
timeStyle: TimeStyle.SHORT,
};
const formatter = new DateTimeFormatter(dateTimeOptions);console.log(formatter.formatDateTime(value))
/** expected output:
* [ { type: "weekday", value: "Saturday", },
* { type: "literal", value: ", ", },
* { type: "day", value: "5", },
* { type: "literal", value: " ", },
* { type: "month", value: "November", },
* { type: "literal", value: " ", },
* { type: "year", value: "1955", },
* { type: "literal", value: " at ",},
* { type: "hour", value: "06",},
* { type: "literal", value: ":",},
* { type: "minute", value: "00",}, ];
**/
`
[formatDateTimeToLocalISOString(value: Date)]
This function returns a local ISO 8601 string.
This method was designed only for internal purposes.
Must not be used to display dates in the Procore UI.`ts
const value = new Date(Date.UTC(1955, 10, 5, 6, 0, 16, 738));
const dateTimeOptions = {
locale: "en-GB",
timeZone: "UTC",
dateStyle: DateStyle.FULL,
timeStyle: TimeStyle.SHORT,
};
const formatter = new DateTimeFormatter(dateTimeOptions);console.log(formatter.formatDateTimeToLocalISOString(value))
// expected output: "1955-11-05";
`
getStartDayOfTheWeek()
This function returns an index value representing the start day of the week on the locale's calendar { Sunday: 0, Monday: 1 ...etc }`ts
const dateTimeOptions = { locale: 'en-US' };
const formatter = new DateTimeFormatter(dateTimeOptions);console.log(formatter.startDayOfTheWeek()); // expected output: 0
`
$3
The functions used on the DateTimeFormatter class are also available as stand alone functions. Read more information about each function in the GTK Documentation.
- formatDateTime()
- formaDateTimeToParts()
- getStartDayOfTheWeek(locale: string)The example below shows usage of the stand alone function:
formatDateTime().
`ts
import { formatDateTime } from '@procore/globalization-toolkit';const value = new Date(Date.UTC(1955, 10, 5, 6, 0, 16, 738));
const dateTimeOptions = { locale: "en-US", timeZone: "UTC" };
console.log(formatDateTime(value, dateTimeOptions)) // expected output: 11/5/1955
`
$3
The formatRelativeTime function allows you to format a time difference as a localized, human-readable string, based on the provided locale and relative time options. This is useful for displaying time differences like "2 hours ago" or "in 3 days" in a format that is appropriate for the user's language and region.The example below shows usage of the stand alone function :
formatRelativeTime().
`ts
import { formatRelativeTime} from '@procore/globalization-toolkit';import formatRelativeTime from
const result1 = formatRelativeTime('en-US', { value: 1, unit: 'hour' });
// Output: "in 1 hour"
const result2 = formatRelativeTime('es-ES', { value: -1, unit: 'hour' });
// Output: "hace 1 hora"
const result3 = formatRelativeTime('en-GB', { value: new Date(Date.now() - 3600 * 1000) });
// Output: "1 hour ago"
`
$3
The getOverrideLocale function returns a mapped locale based on predefined settings.
If the environment locale isn't directly supported, it provides a fallback from the predefined mappings.Note: This override locale is meant for translation and should not be used directly with GTK formatting functions. For formatting, refer to the Sanitizing Locales section.
#### Usage
Here is an example of how to use the getOverrideLocale function:
`ts
import { getOverrideLocale } from '@procore/globalization-toolkit';// Example 1: Known supported locale
const locale1 = getOverrideLocale('ja-JP');
console.log(locale1); // Output: ja-JP
// Example 2: Fallback locale
const locale2 = getOverrideLocale('en-AE');
console.log(locale2); // Output: en-GB
// Example 3: Unknown locale
const locale3 = getOverrideLocale('unknown');
console.log(locale3); // Output: en
`$3
The getFallbackLocaleList function returns a list of locales which should have translation files loaded in the client, including fallbacks when available in addition to the English source.
If the environment locale is not supported directly, it overrides the locale according to predefined mappings.Important
This method should be only used by clients that don't support custom locales.
The example below shows usage of the standalone function:
getFallbackLocaleList().
`ts
import { getFallbackLocaleList } from '@procore/globalization-toolkit';// Example 1: Known supported locale
const locale1 = getFallbackLocaleList('ja-JP');
console.log(locale1); // Output: ['ja-JP', 'en']
// Example 2: Known supported locale with fallback available
const locale2 = getOverrideLocale('es-ES');
console.log(locale2); // Output: ['es-ES', 'es', 'en']
// Example 3: Unsupported locale
const locale3 = getOverrideLocale('en-AE');
console.log(locale3); // Output: ['en-GB', 'en']
// Example 4: Unknown/Custom locale (eg. en-owner)
const locale4 = getOverrideLocale('unknown');
console.log(locale4); // Output: ['en']
`$3
The
getSupportDocsBaseURL function returns the appropriate Procore support base URL for a given locale. This is useful for directing users to the correct localized support site. If the locale is not directly supported, it defaults to the main support base URL.The
generateSupportDocsURL function generates a full support URL for a given path or URL and locale, ensuring the link points to the correct localized support site. It accepts both relative and absolute URLs.#### Usage
Here are examples of how to use the
getSupportDocsBaseURL and generateSupportDocsURL functions:`ts
import { getSupportDocsBaseURL, generateSupportDocsURL } from '@procore/globalization-toolkit';// Example 1: Get the support domain for a known supported locale
const domain1 = getSupportDocsBaseURL('ja-JP');
console.log(domain1); // Output: 'https://ja-jp.support.procore.com'
// Example 2: Get the support domain for an unsupported locale
const domain2 = getSupportDocsBaseURL('unknown');
console.log(domain2); // Output: 'https://support.procore.com'
// Example 3: Generate a support URL for a relative path and supported locale
const url1 = generateSupportDocsURL('/foo/bar', 'fr-FR');
console.log(url1); // Output: 'https://fr.support.procore.com/foo/bar'
// Example 4: Generate a support URL for an absolute URL and supported locale
const url2 = generateSupportDocsURL('https://support.procore.com/help/article', 'de-DE');
console.log(url2); // Output: 'https://de.support.procore.com/help/article'
// Example 5: Generate a support URL for a relative path and unsupported locale
const url3 = generateSupportDocsURL('/help', 'unknown');
console.log(url3); // Output: 'https://support.procore.com/help'
`$3
The getTranslationsFromLocale function is a utility designed to load and merge translations for a given locale and its fallback locales. It is part of the @procore/globalization-toolkit and provides a robust way to handle multiple languages in your application.
`ts
import { getTranslationsFromLocale } from "@procore/globalization-toolkit"// Example locale loader function
async function loadLocale(lang: string): Promise<{ default: Translations }> {
return import(
../locales/${lang}.json)
}// Example usage of getTranslationsFromLocale
const envLocale = "en"
const translations = await getTranslationsFromLocale(envLocale, loadLocale)
`Polyfills
The GTK implementation relies heavily on Intl functionality. This is generally well-supported by browsers, but depending on the browser versions you intend to support and GTK features you plan to use, it may require polyfills. In particular, consumers have seen issues with Safari versions <14.1. If you intend to support older browser versions, we recommend using the Format.JS polyfills, of which NumberFormat, PluralRules, Locale, GetCanonicalLocales, and supportedValuesOf are the ones relevant to GTK. Please reach out if you have questions.
____
Developing
Install dependencies:
`sh
$ npm install
`Prepare project:
`sh
$ npm run prepare
`Compile Typescript:
`sh
$ npm run build
`(Delete build
npm run clean)Add new locale
When adding a new locale to the Globalization Toolkit, follow these steps:
1. Add the new locale to
TMS_SUPPORTED_LOCALES in src/Formatter.types.ts:
`ts
export const TMS_SUPPORTED_LOCALES = [
// ... existing locales ...
"new-locale", // e.g., "fr-BE"
] as const;
`2. If the locale needs special handling for formatting or if it is not compatible with INTL locales, add it to the
getFormatterLocale function in src/FormatterUtils.ts:
`ts
export const getFormatterLocale = (locale: string): string => {
const newLocale = getOverrideLocale(locale);
switch (locale) {
// ... existing cases ...
case "new-locale":
return "fallback-locale"; // e.g., "fr-FR"
default:
return newLocale === "en" ? "en-US" : newLocale;
}
};
`3. If the locale needs a fallback locale for translations, add it to
FALLBACK_LOCALE_MAP in src/getFallbackLocaleList/GetFallbackLocaleList.ts:
`ts
export const FALLBACK_LOCALE_MAP: Record = {
// ... existing mappings ...
"new-locale": "fallback-locale", // e.g., "fr-BE": "fr-FR"
};
`4. If the locale should override another locale, add it to
OVERWRITE_LOCALE_MAP in src/getOverrideLocale/GetOverrideLocale.ts:
`ts
export const OVERWRITE_LOCALE_MAP: Record = {
// ... existing mappings ...
"custom-locale": "new-locale", // e.g., "fr-CA": "fr-FR"
} as const;
`5. Add tests for the new locale in
src/FormatterUtils.test.ts:
`ts
describe("validateLocale", () => {
// ... existing tests ...
it("new-locale should be compatible with Intl.NumberFormat", async () => {
await expect(async () => {
validateLocale(getFormatterLocale("new-locale"));
}).not.toThrowError();
});
});
`6. Update the documentation to reflect the new locale support.
Note: Make sure the new locale follows the industry standard format of
(e.g., en-US, fr-FR). The locale must be supported by Intl.NumberFormat or mapped to a locale supported by Intl.NumberFormat for proper formatting.LOCALE_URL_PREFIX_MAP with locale mapping when its support subdomain (mindtouch) is ready.Testing
`sh
$ npm run test
`If adding a new supported export (or removing one in a breaking change), update the snapshot test output and commit the change.
`sh
$ npm run test -- -u
`To check formatting run
npm run lint:check and npm run format:check.To check dependencies run
`sh
$ npm run depcheck
`Documentation
We auto-generate documentation on what the Globalization Toolkit offers by utilizing
Typedoc.
Once you generate the updated documentation, you can navigate it locally by opening 'docs/index.html'.To generate updated documentation run:
`sh
$ npm run typedoc
`Versioning and Publishing
We use [
changesets][changesets] to manage releases, and encourage you to include a changeset with each commit or pull request.To create a changeset run
npm run changeset and follow the prompt instructions.To publish a release with changes:
- Create new branch (e.g.,
git checkout -b chore/release).
- Run npm run changeset version to create a release containing all changes. This command will update the version of GTK, as well update the CHANGELOG.md file.
- Commit the changes (e.g., git commit -m "chore: release").
- Create a pull request. git push -u origin HEAD, and then create pull request in the web UI.
- Once the pull request is merged to main` the updated packages will be published to npm once the CI pipeline is completed.The package is available under the Procore Developers License
src="https://www.procore.com/images/procore_logo.png"
alt="Procore Logo"
width="250px"
/>
Procore - building the software that builds the world.
Learn more about the #1 most widely used construction management software at procore.com
[changesets]: https://github.com/changesets/changesets/