Skip to content

Commit

Permalink
Add Localize and formatRelativeDateTime docs (#189)
Browse files Browse the repository at this point in the history
* Add localize and formatRelativeDateTime docs

* Feedback
  • Loading branch information
bearfriend authored Aug 23, 2024
1 parent 3b7f3d1 commit cfb640f
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 10 deletions.
108 changes: 107 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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`:
Expand Down Expand Up @@ -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:

* `<p>paragraphs</p>`
* `line<br></br>breaks` (note the end tag is required)
* `<b>bold</b>`
* `<strong>strong</strong>`
* `<i>italic</i>`
* `<em>emphasis</em>`

Remember that `<strong>` is for content of greater importance (browsers show this visually using bold), while `<b>` only bolds the text visually without increasing its importance.

Similarly `<em>` *emphasizes* a particular piece of text (browsers show this visually using italics), whereas `<i>` only italicizes the text visually without emphasis.

Example:

```json
{
"myMessage": "This is <b>bold</b> but <em>not</em> all that <strong>important</strong>."
}
```

```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.
Expand Down
18 changes: 9 additions & 9 deletions lib/localize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -127,7 +127,7 @@ export const getLocalizeClass = (superclass = class {}) => class LocalizeClass e
i: chunks => LocalizeClass.#localizeMarkup`<i>${chunks}</i>`,
p: chunks => LocalizeClass.#localizeMarkup`<p>${chunks}</p>`,
strong: chunks => LocalizeClass.#localizeMarkup`<strong>${chunks}</strong>`,
...params
...replacements
});
validateMarkup(unvalidated);
formattedMessage = unvalidated;
Expand Down

0 comments on commit cfb640f

Please sign in to comment.