Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Localize and formatRelativeDateTime docs #189

Merged
merged 2 commits into from
Aug 23, 2024
Merged
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
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