Skip to content

Commit

Permalink
feat(framework): introduce the i18n decorator and the cldr option (
Browse files Browse the repository at this point in the history
  • Loading branch information
vladitasev authored Oct 1, 2024
1 parent 65f75eb commit 1f29d23
Show file tree
Hide file tree
Showing 94 changed files with 299 additions and 459 deletions.
7 changes: 2 additions & 5 deletions packages/ai/src/PromptInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import type ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
import "@ui5/webcomponents-icons/dist/paper-plane.js";
Expand Down Expand Up @@ -224,12 +224,9 @@ class PromptInput extends UI5Element {
})
valueStateMessage!: Array<HTMLElement>;

@i18n("@ui5/webcomponents-ai")
static i18nBundle: I18nBundle;

static async onDefine() {
PromptInput.i18nBundle = await getI18nBundle("@ui5/webcomponents-ai");
}

_onkeydown(e: KeyboardEvent) {
if (isEnter(e)) {
this.fireEvent("submit");
Expand Down
2 changes: 2 additions & 0 deletions packages/base/src/Boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type OpenUI5Support from "./features/OpenUI5Support.js";
import type F6Navigation from "./features/F6Navigation.js";
import type { PromiseResolve } from "./types.js";
import { attachThemeRegistered } from "./theming/ThemeRegistered.js";
import fixSafariActiveState from "./util/fixSafariActiveState.js";

let booted = false;
let bootPromise: Promise<void>;
Expand Down Expand Up @@ -66,6 +67,7 @@ const boot = async (): Promise<void> => {
openUI5Support && openUI5Support.attachListeners();
insertFontFace();
insertSystemCSSVars();
fixSafariActiveState();

resolve();

Expand Down
4 changes: 4 additions & 0 deletions packages/base/src/FeaturesRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import type UI5Element from "./UI5Element.js";
abstract class ComponentFeature {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-empty-function
constructor(...args: any[]) {}

/**
* @deprecated assign the feature's "i18nBundle" static member directly from the component that uses the feature
*/
static define?: () => Promise<void>;
static dependencies?: Array<typeof UI5Element>
}
Expand Down
37 changes: 31 additions & 6 deletions packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import type {
import { updateFormValue, setFormValue } from "./features/InputElementsFormSupport.js";
import type { IFormInputElement } from "./features/InputElementsFormSupport.js";
import { getComponentFeature, subscribeForFeatureLoad } from "./FeaturesRegistry.js";
import { getI18nBundle } from "./i18nBundle.js";
import { fetchCldr } from "./asset-registries/LocaleData.js";
import getLocale from "./locale/getLocale.js";

const DEV_MODE = true;
let autoId = 0;
Expand Down Expand Up @@ -1207,23 +1210,48 @@ abstract class UI5Element extends HTMLElement {
* Hook that will be called upon custom element definition
*
* @protected
* @deprecated use the "i18n" decorator for fetching message bundles and the "cldr" option in the "customElements" decorator for fetching CLDR
*/
static async onDefine(): Promise<void> {
return Promise.resolve();
}

static fetchI18nBundles() {
return Promise.all(Object.entries(this.getMetadata().getI18n()).map(pair => {
const { bundleName } = pair[1];
return getI18nBundle(bundleName);
}));
}

static fetchCLDR() {
if (this.getMetadata().needsCLDR()) {
return fetchCldr(getLocale().getLanguage(), getLocale().getRegion(), getLocale().getScript());
}
return Promise.resolve();
}

static asyncFinished: boolean;
static definePromise: Promise<[void, void]> | undefined;
static definePromise: Promise<void> | undefined;

/**
* Registers a UI5 Web Component in the browser window object
* @public
*/
static async define(): Promise<typeof UI5Element> {
static define(): typeof UI5Element {
this.definePromise = Promise.all([
this.fetchI18nBundles(),
this.fetchCLDR(),
boot(),
this.onDefine(),
]);
]).then(result => {
const [i18nBundles] = result;
Object.entries(this.getMetadata().getI18n()).forEach((pair, index) => {
const propertyName = pair[0];
const targetClass = pair[1].target;
(targetClass as Record<string, any>)[propertyName] = i18nBundles[index];
});
this.asyncFinished = true;
});

const tag = this.getMetadata().getTag();

Expand All @@ -1248,9 +1276,6 @@ abstract class UI5Element extends HTMLElement {
customElements.define(tag, this as unknown as CustomElementConstructor);
}

await this.definePromise;
this.asyncFinished = true;

return this;
}

Expand Down
24 changes: 24 additions & 0 deletions packages/base/src/UI5ElementMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { camelToKebabCase, kebabToCamelCase } from "./util/StringHelper.js";
import { getSlottedNodes } from "./util/SlotsHelper.js";
import { getEffectiveScopingSuffixForTag } from "./CustomElementsScopeUtils.js";
import type UI5Element from "./UI5Element.js";

type SlotInvalidation = {
properties: boolean | Array<string>,
Expand Down Expand Up @@ -30,6 +31,13 @@ type PropertyValue = boolean | number | string | object | undefined | null;

type EventData = Record<string, object>;

type I18nBundleAccessorValue = {
bundleName: string,
target: typeof UI5Element
};

type I18nBundleAccessors = Record<string, I18nBundleAccessorValue>;

type Metadata = {
tag?: string,
managedSlots?: boolean,
Expand All @@ -39,9 +47,11 @@ type Metadata = {
fastNavigation?: boolean,
themeAware?: boolean,
languageAware?: boolean,
cldr?: boolean,
formAssociated?: boolean,
shadowRootOptions?: Partial<ShadowRootInit>
features?: Array<string>
i18n?: I18nBundleAccessors
};

type State = Record<string, PropertyValue | Array<SlotValue>>;
Expand Down Expand Up @@ -236,6 +246,13 @@ class UI5ElementMetadata {
return !!this.metadata.themeAware;
}

/**
* Determines whether this UI5 Element needs CLDR assets to be fetched to work correctly
*/
needsCLDR(): boolean {
return !!this.metadata.cldr;
}

getShadowRootOptions(): Partial<ShadowRootInit> {
return this.metadata.shadowRootOptions || {};
}
Expand Down Expand Up @@ -313,6 +330,13 @@ class UI5ElementMetadata {

throw new Error("Wrong format for invalidateOnChildChange: boolean or object is expected");
}

getI18n(): I18nBundleAccessors {
if (!this.metadata.i18n) {
this.metadata.i18n = {};
}
return this.metadata.i18n;
}
}

const validateSingleSlot = (value: Node, slotData: Slot) => {
Expand Down
5 changes: 5 additions & 0 deletions packages/base/src/decorators/customElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const customElement = (tagNameOrComponentSettings: string | {
dependencies?: Array<typeof UI5Element>,
languageAware?: boolean,
themeAware?: boolean,
cldr?: boolean,
fastNavigation?: boolean,
formAssociated?: boolean,
shadowRootOptions?: Partial<ShadowRootInit>,
Expand All @@ -36,6 +37,7 @@ const customElement = (tagNameOrComponentSettings: string | {
tag,
languageAware,
themeAware,
cldr,
fastNavigation,
formAssociated,
shadowRootOptions,
Expand All @@ -46,6 +48,9 @@ const customElement = (tagNameOrComponentSettings: string | {
if (languageAware) {
target.metadata.languageAware = languageAware;
}
if (cldr) {
target.metadata.cldr = cldr;
}

if (features) {
target.metadata.features = features;
Expand Down
30 changes: 30 additions & 0 deletions packages/base/src/decorators/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type UI5Element from "../UI5Element.js";

type i18nDecorator = (target: typeof UI5Element, propertyName: string) => void;

/**
* A decorator that converts a static class member into an accessor for the i18n bundle with a specified name
*
* @param { string } bundleName name of the i18n bundle to load
* @returns { i18nDecorator }
*
* ```ts
* class MyComponnet extends UI5Element {
* @i18n('@ui5/webcomponents')
* i18nBundle: I18nBundle;
* }
* ```
*/
const i18n = (bundleName: string): i18nDecorator => {
return (target: typeof UI5Element, propertyName: string) => {
if (!target.metadata.i18n) {
target.metadata.i18n = {};
}
target.metadata.i18n[propertyName] = {
bundleName,
target,
};
};
};

export default i18n;
13 changes: 13 additions & 0 deletions packages/base/src/util/fixSafariActiveState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { isIOS, isSafari } from "../Device.js";

let listenerAttached = false;

const fixSafariActiveState = () => {
if (isSafari() && isIOS() && !listenerAttached) {
// Safari on iOS does not use the :active state unless there is a touchstart event handler on the <body> element
document.body.addEventListener("touchstart", () => {});
listenerAttached = true;
}
};

export default fixSafariActiveState;
7 changes: 2 additions & 5 deletions packages/compat/src/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
Expand All @@ -30,7 +31,6 @@ import getNormalizedTarget from "@ui5/webcomponents-base/dist/util/getNormalized
import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js";
import { getLastTabbableElement, getTabbableElements } from "@ui5/webcomponents-base/dist/util/TabbableElements.js";
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import debounce from "@ui5/webcomponents-base/dist/util/debounce.js";
import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js";
import CheckBox from "@ui5/webcomponents/dist/CheckBox.js";
Expand Down Expand Up @@ -430,10 +430,7 @@ class Table extends UI5Element {
})
columns!: Array<TableColumn>;

static async onDefine() {
Table.i18nBundle = await getI18nBundle("@ui5/webcomponents");
}

@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;

fnHandleF7: (e: CustomEvent) => void;
Expand Down
6 changes: 2 additions & 4 deletions packages/compat/src/TableCell.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import TableCellTemplate from "./generated/templates/TableCellTemplate.lit.js";

Expand Down Expand Up @@ -58,10 +58,8 @@ class TableCell extends UI5Element {
@slot({ type: HTMLElement, "default": true })
content?: Array<HTMLElement>;

@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;
static async onDefine() {
TableCell.i18nBundle = await getI18nBundle("@ui5/webcomponents");
}

get cellContent(): Array<HTMLElement> {
return this.getSlottedNodes<HTMLElement>("content");
Expand Down
7 changes: 2 additions & 5 deletions packages/compat/src/TableGroupRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import CheckBox from "@ui5/webcomponents/dist/CheckBox.js";
import type { ITableRow, TableColumnInfo } from "./Table.js";
import TableGroupRowTemplate from "./generated/templates/TableGroupRowTemplate.lit.js";
Expand Down Expand Up @@ -70,6 +70,7 @@ class TableGroupRow extends UI5Element implements ITableRow {
tabbableElements: Array<HTMLElement> = [];
_columnsInfoString = "";

@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;

_colSpan?: number;
Expand Down Expand Up @@ -104,10 +105,6 @@ class TableGroupRow extends UI5Element implements ITableRow {
_onfocusin(e: FocusEvent) {
this.fireEvent("_focused", e);
}

static async onDefine() {
TableGroupRow.i18nBundle = await getI18nBundle("@ui5/webcomponents");
}
}

TableGroupRow.define();
Expand Down
7 changes: 2 additions & 5 deletions packages/compat/src/TableRow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import type { PassiveEventListenerObject } from "@ui5/webcomponents-base/dist/types.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import {
isSpace,
Expand Down Expand Up @@ -162,6 +162,7 @@ class TableRow extends UI5Element implements ITableRow {
@slot({ type: HTMLElement, "default": true, individualSlots: true })
cells!: Array<TableCell>;

@i18n("@ui5/webcomponents")
static i18nBundle: I18nBundle;

visibleCells: Array<TableCell> = [];
Expand Down Expand Up @@ -431,10 +432,6 @@ class TableRow extends UI5Element implements ITableRow {
getNormilzedTextContent(textContent: string): string {
return textContent.replace(/[\n\r\t]/g, "").trim();
}

static async onDefine() {
TableRow.i18nBundle = await getI18nBundle("@ui5/webcomponents");
}
}

TableRow.define();
Expand Down
Loading

0 comments on commit 1f29d23

Please sign in to comment.