From cfb640f0d12defc3672a72e12150e23793b24d68 Mon Sep 17 00:00:00 2001 From: Danny Gleckler Date: Fri, 23 Aug 2024 09:27:07 -0400 Subject: [PATCH] Add `Localize` and `formatRelativeDateTime` docs (#189) * Add localize and formatRelativeDateTime docs * Feedback --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++- lib/localize.js | 18 ++++---- 2 files changed, 116 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e3e3915..df00520 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ const value = parseNumber('-8 942,39'); // -> -8942.39 in fr-CA ## Date/Time Formatting -Dates and times can be formatted in the user's locale using `formatDate`, `formatTime` and `formatDateTime`. +Dates and times can be formatted in the user's locale using `formatDate`, `formatTime`, `formatDateTime`, and `formatRelativeDateTime`. Timestamps (milliseconds since the epoch) can be formatted in the user's locale and timezone using `formatDateTimeFromTimestamp`. @@ -173,6 +173,16 @@ Options: - **full**: includes timezone. e.g. `'1:25 PM EST'` - **medium** or **short**: excludes timezone e.g. `'1:25 PM'` +To format a date/time in relative time, use `formatRelativeDateTime`: + +```javascript +import { formatRelativeDateTime } from '@brightspace-ui/intl/lib/dateTime.js'; + +const relativeDateTime = formatRelativeDateTime( + new Date(2024, 8, 18) +); // If today is 2024-08-22, -> 'last week' in en-US +``` + ## Date Parsing To parse a date written in the user's locale, use `parseDate`: @@ -258,6 +268,102 @@ const separator = getSeparator({ nonBreaking: true }); // -> ',\xa0' in en-US Options: - **nonBreaking**: a Boolean flag, whether to use non-breaking spaces instead of standard spaces; default is `false` +## Language Localization + +The `Localize` class allows text to be displayed in the user's preferred language. + +### Resources + +Each resource is comprised of a name and a message, which must be provided as a key-value pair in a JavaScript object: + +```javascript +{ myMessage: "This is my message" } +``` + +#### Name + +Names should succinctly and uniquely describe the text being localized. `camelCase` is recommended, although `snake_case` and `kebab-case` are also supported. + +For large projects, resources may be grouped using the `:` character. For example: `parentGroup:subGroup:messageName`. + +#### Message + +Messages must conform to the [ICU Message Syntax](https://formatjs.io/docs/core-concepts/icu-syntax/) format. It supports features such as: [simple arguments](https://formatjs.io/docs/core-concepts/icu-syntax/#simple-argument), the [`select` format](https://formatjs.io/docs/core-concepts/icu-syntax/#select-format) and [pluralization](https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format). + +> **Note:** Avoid using the ICU Message Syntax number, date and time formatting functionality. Brightspace allows customization of how these are localized, so use `formatNumber`, `formatDate` and `formatTime` instead. + +### Using `Localize` + +Import `Localize` and create a new instance. The `importFunc` option is required. It will be passed a language tag which can be used to fetch resources: + +```javascript +import { Localize } from '@brightspace-ui/intl/lib/localize.js'; + +const localizer = new Localize({ + importFunc: async lang => (await import(`../lang/${lang}.js`)).default +}); +``` + +Wait for resources to be available before attempting to use them: +```javascript +await localizer.ready; +``` + +### `localize()` + +The `localize()` method is used to localize a message. + +If the message contains arguments, provide replacement values in the second parameter: + +```javascript +const helloText = localizer.localize('hello', { firstName: 'Mary' }); +``` + +### `localizeHTML()` + +Rich formatting can be included in messages and safely converted to HTML with the `localizeHTML()` method. + +#### Basic Formatting + +The following formatting elements are supported out-of-the-box: + +* `

paragraphs

` +* `line

breaks` (note the end tag is required) +* `bold` +* `strong` +* `italic` +* `emphasis` + +Remember that `` is for content of greater importance (browsers show this visually using bold), while `` only bolds the text visually without increasing its importance. + +Similarly `` *emphasizes* a particular piece of text (browsers show this visually using italics), whereas `` only italicizes the text visually without emphasis. + +Example: + +```json +{ + "myMessage": "This is bold but not all that important." +} +``` + +```javascript +localizer.localizeHTML('myMessage'); +``` + +### `onResourcesChange` +Provide an `onResourcesChange` callback function to perform tasks when the document language is changed and updated resources are available: + +```javascript +const localizer = new Localize({ + onResourcesChange: () => document.title = localizer.localize('pageTitle') +}); +``` + +To stop listening for changes, disconnect the instance: +```javascript +localizer.disconnect() +``` + ## Developing and Contributing After cloning the repo, run `npm install` to install dependencies. diff --git a/lib/localize.js b/lib/localize.js index 709509d..66259f0 100644 --- a/lib/localize.js +++ b/lib/localize.js @@ -41,8 +41,8 @@ export const getLocalizeClass = (superclass = class {}) => class LocalizeClass e const allResources = {}; const resolvedLocales = new Set(); for (const { language, resources } of localizeResources) { - for (const [key, value] of Object.entries(resources)) { - allResources[key] = { language, value }; + for (const [name, value] of Object.entries(resources)) { + allResources[name] = { language, value }; resolvedLocales.add(language); } } @@ -80,15 +80,15 @@ export const getLocalizeClass = (superclass = class {}) => class LocalizeClass e this.#connected = false; } - localize(key) { + localize(name, replacements) { - const { language, value } = this.localize.resources?.[key] ?? {}; + const { language, value } = this.localize.resources?.[name] ?? {}; if (!value) return ''; let params = {}; - if (arguments.length > 1 && arguments[1]?.constructor === Object) { + if (replacements?.constructor === Object) { // support for key-value replacements as a single arg - params = arguments[1]; + params = replacements; } else { // legacy support for localize-behavior replacements as many args for (let i = 1; i < arguments.length; i += 2) { @@ -112,9 +112,9 @@ export const getLocalizeClass = (superclass = class {}) => class LocalizeClass e return formattedMessage; } - localizeHTML(key, params = {}) { + localizeHTML(name, replacements = {}) { - const { language, value } = this.localize.resources?.[key] ?? {}; + const { language, value } = this.localize.resources?.[name] ?? {}; if (!value) return ''; const translatedMessage = new IntlMessageFormat(value, language); @@ -127,7 +127,7 @@ export const getLocalizeClass = (superclass = class {}) => class LocalizeClass e i: chunks => LocalizeClass.#localizeMarkup`${chunks}`, p: chunks => LocalizeClass.#localizeMarkup`

${chunks}

`, strong: chunks => LocalizeClass.#localizeMarkup`${chunks}`, - ...params + ...replacements }); validateMarkup(unvalidated); formattedMessage = unvalidated;