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 i18n doc #1904

Merged
merged 29 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
234c349
Add i18n doc
gund Jun 5, 2023
93c659f
Move to i18n folder
gund Jun 5, 2023
290ccd6
Update docs
tobi-or-not-tobi Jul 24, 2023
69a6f81
Merge branch 'master' into feat-hrz-2942-i18n
tobi-or-not-tobi Jul 24, 2023
e6f4556
fixes
tobi-or-not-tobi Jul 25, 2023
5c1fa1f
Update localization.md
andriitserkovnyi Jul 26, 2023
f8216bc
review
andriitserkovnyi Jul 26, 2023
00e8b28
Update localization.md
andriitserkovnyi Jul 26, 2023
e80647b
Update localization.md
andriitserkovnyi Jul 27, 2023
ee2f6c9
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 14, 2023
ca6c9f6
Update localization.md
andriitserkovnyi Aug 14, 2023
d86e17c
Update localization.md
andriitserkovnyi Aug 14, 2023
53816a8
Update localization.md
andriitserkovnyi Aug 15, 2023
5615c37
Update localization.md
andriitserkovnyi Aug 15, 2023
1118ca7
Update localization.md
andriitserkovnyi Aug 16, 2023
34c5011
move
andriitserkovnyi Aug 16, 2023
8a8a57d
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 16, 2023
704e441
fixing syntax
andriitserkovnyi Aug 16, 2023
7861a9c
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 16, 2023
64c8291
Fix comments
andriitserkovnyi Aug 21, 2023
27d42e5
Update oryx-localization.md
andriitserkovnyi Aug 21, 2023
6b2ab65
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 21, 2023
c3c7f8d
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 23, 2023
1fa0788
links
andriitserkovnyi Aug 23, 2023
efea401
Apply suggestions from code review
andriitserkovnyi Aug 28, 2023
6986978
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 28, 2023
01bb892
add link to signals
andriitserkovnyi Aug 28, 2023
f5eb9d9
links
andriitserkovnyi Aug 28, 2023
6557ce3
Merge branch 'master' into feat-hrz-2942-i18n
andriitserkovnyi Aug 28, 2023
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
5 changes: 3 additions & 2 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
* @spryker/docs

/docs/scos/dev/front-end-development/*/oryx/* @andriitserkovnyi
docs/scos/dev/front-end-development/*/oryx/oryx-localization.md @andriitserkovnyi @tobi-or-not-tobi @tolerants
/docs/scos/dev/front-end-development/*/oryx/feature-sets.md @tobi-or-not-tobi @andriitserkovnyi
/docs/scos/dev/front-end-development/*/oryx/dependency-injection/* @dunqan @andriitserkovnyi
/docs/scos/dev/front-end-development/*/oryx/styling/* @tobi-or-not-tobi @andriitserkovnyi
/docs/scos/dev/front-end-development/*/oryx/styling/* @tobi-or-not-tobi @andriitserkovnyi @tolerants
/docs/scos/dev/front-end-development/*/oryx/styling/oryx-icon-system.md @supproduction @tobi-or-not-tobi @andriitserkovnyi
/docs/scos/dev/front-end-development/*/oryx/styling/oryx-design-tokens.md @tolerants @tobi-or-not-tobi @andriitserkovnyi
/docs/scos/dev/front-end-development/*/oryx/styling/oryx-design-tokens.md @tolerants @tobi-or-not-tobi @andriitserkovnyi
/docs/scos/dev/set-up-spryker-locally/* @andriitserkovnyi
2 changes: 2 additions & 0 deletions _data/sidebars/scos_dev_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3401,6 +3401,8 @@ entries:
url: /docs/scos/dev/front-end-development/oryx/oryx-versioning.html
- title: Presets
url: /docs/scos/dev/front-end-development/oryx/oryx-presets.html
- title: Localization
url: /docs/scos/dev/front-end-development/oryx/oryx-localization.html
- title: Supported browsers
url: /docs/scos/dev/front-end-development/oryx/oryx-supported-browsers.html
- title: Dependency injection
Expand Down
150 changes: 150 additions & 0 deletions docs/scos/dev/front-end-development/202212.0/oryx/oryx-localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
title: "Oryx: Localization"
description: Localizations are decoupled from component implementations
template: concept-topic-template
last_updated: July 23, 2023
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved
---

Localization is the part of internationalization (`i18n`) that's concerned with creating variations of the same application in different locales.

While most localized content is managed in other systems, like product or content management systems, components often use small text fragments in the UI. The examples are static site labels or aria labels.

To make sure all components can be customized and localized, Oryx doesn't provide _any_ hardcoded text. Instead, components use _tokens_ that function as a reference to a text. When there's no localized text available for such a reference, Oryx renders the token in a human-readable format.

## Translation keys

Translation keys, also knows as _i18n tokens_, are used to resolve localized text. The tokens are created with the following conventions in mind:
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

- Tokens are written in English.
- Tokens are written in kebab-case format—for example, `my-token`.
- Tokens are organized by domains—for example, `cart.add-to-cart`.
- Tokens support context variables, which are wrapped in angle brackets in camelCase format. For example, `cart.totals.<count>-items`.

## Resolving translations from translation resources

The localization of labels is driven by the current language and the translation key. Translations are typically provided as lazy-loaded resources next to the component implementation. Although, they can aslo be added as part of the static resources that are loaded in Oryx. If the resources are loaded externally, they can be provided by static JSON files or a third-party service through an API.
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

Oryx uses the active application language to look up the available labels. When the language is `en`, the locales for English are resolved.

If a language resource can be resolved, the translation key is evaluated against the available translations in the resource using all "parts" of the key. For example, if a translation key contains multiple parts, like `cart.increase`, you can provide a global translation for just the `increase` part or for `cart.increase`.

## Auto-conversion of translation keys
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

If a translation key doesn't match any of the translations, or if the i18n feature is not installed (which is the default behavior), the translation key is converted into a human-readable message.

For example, if a token contains multiple parts, like `cart.increase`, you can provide a global translation for just the `increase` part or for `cart.increase`. This mechanism allows for a single translation of `increase` that might affect multiple components. This provides a consistent and convenient translation mechanism while remaining flexible to add specific localizations for some components.

The following examples show how the tokens are translated.

| TOKEN | CONVERTED LABEL |
| --------------------------- | --------------- |
| `cart.increase` | Increase |
| `cart.add-to-cart` | Add to cart |
| `cart.totals.<count>-items` | 5 items |

This mechanism lets Oryx avoid distributing localizations as a standard package or as part of the boilerplate. Labels are quite opinionated and may change frequently over time, which may cause breaking changes. For 90% of the cases, the keys are fairly short and provide an OK experience.

{% info_block infoBox "" %}

The [Oryx labs package](https://www.npmjs.com/package/@spryker-oryx/labs) provides some localizations mainly for demonstration reasons. For more information on the labs package, see [Feature sets](/docs/scos/dev/front-end-development/{{page.version}}/oryx/feature-sets.html).

{% endinfo_block %}



## Installing the i18n package

To use the i18n package, add `I18nFeature` to the app and configure translation resources using the loader function:

```ts
import { appBuilder } from "@spryker-oryx/application";
import { I18nFeature } from "@spryker-oryx/i18n";

export const app = appBuilder()
.withFeature(
new I18nFeature({
// Here as example we are loading translations from TS modules
load: (localeId) => import(`../i18n/${localeId}.ts`), // <-- Required part
})
)
.create();
```

This gives you full flexibility of how and where to load your translation texts from. The `load` function returns a promise of a `{ default: I18nData }` type.
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

## Translation resources

To translate text in Oryx, you can provide translation data in a TS module.


```ts {% raw %}
export default {
increase: "Increase",

// using token parameters
"cart.entry.<quantity>-items": "x {quantity}",

// pluralization example
"order.<count>-items":
"Products ({count, plural, one {{count} item} other {{count} items}})",
};
{% endraw %}```


You can also load the text from a third-party translation engine.

The [globalize](https://www.npmjs.com/package/globalize) translation engine is based on the standardized [ICU message expressions](https://unicode-org.github.io/icu/userguide/format_parse/messages/). The ICU message format defines a standard syntax to handle common translation cases:

- Pluralization: for example, `You have {count} {count, plural, one {item} other {items}} in cart`.
- Selects: for example, `{gender, select, male {He} female {She} other {They}} invited you to party!`.

Other ICU capabilities, such as [number formatting](https://unicode-org.github.io/icu/userguide/format_parse/numbers/) or [date and time formatting](https://unicode-org.github.io/icu/userguide/format_parse/datetime/) can be loaded in addition. Most likely, it's not needed in your project, because Oryx uses the standard [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API, which is widely supported.
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

## Using the i18n directive in components

Localized content uses [the fine-grained reactivity system](/docs/scos/dev/front-end-development/{{page.version}}/oryx/reactivity/reactivity.html). The localizations are updated as soon the active locale is changed; for example, when the user interacts with the localization selector, or when the `LocaleService.set()` API is used. All localized content in Oryx components is updated instantly without a page reload.
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

To support such fine-grained reactivity, a framework specific implementation is required. Oryx provides an `i18n` Lit directive that's used inside components. Using this directive ensures that the DOM is aligned with the localizations in an efficient way. This requires the `@signalAware()` decorator on the component class. To simplify the integration, you can use `I18nMixin` in your component implementation. `I18nMixin` adds `@signalAware()` and exposes the `i18n` function as a method on the component.

<!-- TODO: add link to signal documentation once it's available so users can read about signals and @signalAware decorator -->
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we going to resolve this TODO after publishing this PR?


The following is an example of using i18n in a Lit component. The tokens are used both as attributes and plain text, and the example shows the usage with and without a token context.

```ts
import { I18nMixin } from "@spryker-oryx/utilities";

export class MyComponent extends I18nMixin(LitElement) {
protected override render(): TemplateResult {
return html`
<button aria-label=${this.i18n("site.my-token")}>
${this.i18n("site.my-token-<count>", { count: 12 })}
</button>
`;
}
}
```

## Localizing texts in vanilla JS components

The i18n directive uses `I18nService`. You can inject `I18nService` in JS or TS using [dependency injection](/docs/scos/dev/front-end-development/{{page.version}}/oryx/dependency-injection/dependency-injection.html). The service provides an observable that you can subscribe to.

```ts
import { inject } from "@spryker-oryx/injector";
import { I18nService } from "@spryker-oryx/i18n";

class Example {
constructor(i18nService = inject(I18nService)) {
i18nService
.translate("domain.hello")
.subscribe((text) => console.log(text));
}
}
```

If a token requires context, you can pass the context as a second argument to the `translate` method.

```ts
i18nService
.translate('domain.hello-<name>', { name: 'world' }))
.subscribe((text) => console.log(text));
```
146 changes: 146 additions & 0 deletions docs/scos/dev/front-end-development/202307.0/oryx/oryx-localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
title: "Oryx: Localization"
description: Localizations are decoupled from component implementations
template: concept-topic-template
last_updated: July 23, 2023
---

Localization is the part of internationalization (`i18n`) that's concerned with creating variations of the same application in different locales.

While most localized content is managed in other systems, like product or content management systems, components often use small text fragments in the UI. The examples are static site labels or aria labels.

To make sure all components can be customized and localized, Oryx doesn't provide _any_ hardcoded text. Instead, components use _tokens_ that function as a reference to a text. When there's no localized text available for such a reference, Oryx renders the token in a human-readable format.

## Translation keys

Translation keys, also knows as _i18n tokens_, are used to resolve localized text. The tokens are created with the following conventions in mind:

- Tokens are written in English.
- Tokens are written in kebab-case format—for example, `my-token`.
- Tokens are organized by domains—for example, `cart.add-to-cart`.
- Tokens support context variables, which are wrapped in angle brackets in camelCase format. For example, `cart.totals.<count>-items`.

## Resolving translations from translation resources

The localization of labels is driven by the current language and the translation key. Translations are typically provided as lazy-loaded resources next to the component implementation. Although, they can aslo be added as part of the static resources that are loaded in Oryx. If the resources are loaded externally, they can be provided by static JSON files or a third-party service through an API.

Oryx uses the active application language to look up the available labels. When the language is `en`, the locales for English are resolved.

If a language resource can be resolved, the translation key is evaluated against the available translations in the resource using all "parts" of the key. For example, if a translation key contains multiple parts, like `cart.increase`, you can provide a global translation for just the `increase` part or for `cart.increase`. This mechanism allows for a single translation of `increase` that might affect multiple components. This provides a consistent and convenient translation mechanism while remaining flexible to add specific localizations for some components.

## Auto-conversion of translation keys

If a translation key doesn't match any of the translations, or if the i18n feature is not installed (which is the default behavior), the translation key is converted into a human-readable message.

The following examples show how the tokens are translated.

| TOKEN | CONVERTED LABEL |
| --------------------------- | --------------- |
| `cart.increase` | Increase |
| `cart.add-to-cart` | Add to cart |
| `cart.totals.<count>-items` | 5 items |

This mechanism lets Oryx avoid distributing localizations as a standard package or as part of the boilerplate. Labels are quite opinionated and may change frequently over time, which may cause breaking changes. For 90% of the cases, the keys are fairly short and provide an OK experience.

{% info_block infoBox "" %}

The [Oryx labs package](https://www.npmjs.com/package/@spryker-oryx/labs) provides some localizations mainly for demonstration reasons. For more information on the labs package, see [Feature sets](/docs/scos/dev/front-end-development/{{page.version}}/oryx/feature-sets.html).

{% endinfo_block %}



## Installing the i18n package

To use the i18n package, add `I18nFeature` to the app and configure translation resources using the loader function:

```ts
import { appBuilder } from "@spryker-oryx/application";
import { I18nFeature } from "@spryker-oryx/i18n";

export const app = appBuilder()
.withFeature(
new I18nFeature({
// Here as example we are loading translations from TS modules
load: (localeId) => import(`../i18n/${localeId}.ts`), // <-- Required part
})
)
.create();
```

This gives you full flexibility of how and where to load your translation texts from. The `load` function returns a promise of a `{ default: I18nData }` type.

## Translation resources

To translate text in Oryx, you can provide translation data in a TS module.

```ts{% raw %}
export default {
increase: "Increase",

// using token parameters
"cart.entry.<quantity>-items": "x {quantity}",

// pluralization example
"order.<count>-items":
"Products ({count, plural, one {{count} item} other {{count} items}})",
};{% endraw %}
```

You can also load the text from a third-party translation engine.

The [globalize](https://www.npmjs.com/package/globalize) translation engine is based on the standardized [ICU message expressions](https://unicode-org.github.io/icu/userguide/format_parse/messages/). The ICU message format defines a standard syntax to handle common translation cases:

- Pluralization: for example, `You have {count} {count, plural, one {item} other {items}} in cart`.
- Selects: for example, `{gender, select, male {He} female {She} other {They}} invited you to party!`.

Other ICU capabilities, such as [number formatting](https://unicode-org.github.io/icu/userguide/format_parse/numbers/) or [date and time formatting](https://unicode-org.github.io/icu/userguide/format_parse/datetime/) can be loaded in addition. Most likely, it's not needed in your project, because Oryx uses the standard [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API, which is widely supported.

## Using the i18n directive in components

Localized content uses [the fine-grained reactivity system](/docs/scos/dev/front-end-development/{{page.version}}/oryx/reactivity/reactivity.html). The localizations are updated as soon the active locale is changed; for example, when the user interacts with the localization selector, or when the `LocaleService.set()` API is used. All localized content in Oryx components is updated instantly without a page reload.

To support such fine-grained reactivity, a framework specific implementation is required. Oryx provides an `i18n` Lit directive that's used inside components. Using this directive ensures that the DOM is aligned with the localizations in an efficient way. This requires the `@signalAware()` decorator on the component class. To simplify the integration, you can use `I18nMixin` in your component implementation. `I18nMixin` adds `@signalAware()` and exposes the `i18n` function as a method on the component.
andriitserkovnyi marked this conversation as resolved.
Show resolved Hide resolved

<!-- TODO: add link to signal documentation once it's available so users can read about signals and @signalAware decorator -->

The following is an example of using i18n in a Lit component. The tokens are used both as attributes and plain text, and the example shows the usage with and without a token context.

```ts
import { I18nMixin } from "@spryker-oryx/utilities";

export class MyComponent extends I18nMixin(LitElement) {
protected override render(): TemplateResult {
return html`
<button aria-label=${this.i18n("site.my-token")}>
${this.i18n("site.my-token-<count>", { count: 12 })}
</button>
`;
}
}
```

## Localizing texts in vanilla JS components

The i18n directive uses `I18nService`. You can inject `I18nService` in JS or TS using [dependency injection](/docs/scos/dev/front-end-development/{{page.version}}/oryx/dependency-injection/dependency-injection.html). The service provides an observable that you can subscribe to.

```ts
import { inject } from "@spryker-oryx/injector";
import { I18nService } from "@spryker-oryx/i18n";

class Example {
constructor(i18nService = inject(I18nService)) {
i18nService
.translate("domain.hello")
.subscribe((text) => console.log(text));
}
}
```

If a token requires context, you can pass the context as a second argument to the `translate` method.

```ts
i18nService
.translate('domain.hello-<name>', { name: 'world' }))
.subscribe((text) => console.log(text));
dunqan marked this conversation as resolved.
Show resolved Hide resolved
```
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Additional guidance for introducing short and long examples:

<div class="width-100">

| GUIDENCE | RECOMMENDED | NOT RECOMMENDED |
| GUIDANCE | RECOMMENDED | NOT RECOMMENDED |
|-|-|-|
| To introduce a short example, use a comma or an em dash. | Create a new module—for example, `src/Yves/ContentFooWidget`. | Create a new module. For example, `src/Yves/ContentFooWidget`. |
| To introduce a long example in a short sentence, use a comma or an em dash. | A customer is the final consumer of the store—for example, the person who places an order. | A customer is the final consumer of the store. For example, the person who places an order. |
Expand Down