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

move lit properties to UmbLitElement #832

Merged
merged 12 commits into from
Aug 11, 2023
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en-us">
<html lang="en-us" dir="ltr">
<head>
<base href="/" />
<meta charset="UTF-8" />
Expand Down
18 changes: 3 additions & 15 deletions src/apps/app/app.element.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { umbTranslationRegistry } from '@umbraco-cms/backoffice/localization';
import type { UmbAppErrorElement } from './app-error.element.js';
import { umbTranslationRegistry } from '@umbraco-cms/backoffice/localization';
import { UMB_AUTH, UmbAuthFlow, UmbAuthContext } from '@umbraco-cms/backoffice/auth';
import { UMB_APP, UmbAppContext } from '@umbraco-cms/backoffice/context';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
Expand All @@ -23,19 +23,6 @@
@property({ type: String })
serverUrl = window.location.origin;

/**
* The default culture to use for localization.
*
* When the current user is resolved, the culture will be set to the user's culture.
*
* @attr
* @remarks This is the default culture to use for localization, not the current culture.
* @example "en-us"
* @example "en"
*/
@property({ type: String, attribute: 'default-culture' })
culture: string = 'en-us';

/**
* The base path of the backoffice.
*
Expand Down Expand Up @@ -89,7 +76,8 @@
}

#setLanguage() {
umbTranslationRegistry.loadLanguage(this.culture);
const initialLanguage = this.lang || document.documentElement.lang || 'en-us';
umbTranslationRegistry.loadLanguage(initialLanguage);
}

#listenForLanguageChange(authContext: UmbAuthContext) {
Expand Down Expand Up @@ -154,7 +142,7 @@

// TODO: wrap all debugging logic in a separate class. Maybe this could be part of the context-api? When we create a new root, we could attach the debugger to it?
// Listen for the debug event from the <umb-debug> component
this.addEventListener(umbDebugContextEventType, (event: any) => {

Check warning on line 145 in src/apps/app/app.element.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected any. Specify a different type
// Once we got to the outter most component <umb-app>
// we can send the event containing all the contexts
// we have collected whilst coming up through the DOM
Expand Down
1 change: 1 addition & 0 deletions src/external/rxjs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export {
lastValueFrom,
firstValueFrom,
switchMap,
filter,
} from 'rxjs';
5 changes: 0 additions & 5 deletions src/libs/element-api/element.mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
UmbContextProviderController,
} from '@umbraco-cms/backoffice/context-api';
import { ObserverCallback, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import { property } from '@umbraco-cms/backoffice/external/lit';

export declare class UmbElement extends UmbControllerHostElement {
/**
Expand All @@ -34,10 +33,6 @@ export declare class UmbElement extends UmbControllerHostElement {

export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T) => {
class UmbElementMixinClass extends UmbControllerHostElementMixin(superClass) implements UmbElement {
// Make `dir` and `lang` reactive properties so they react to language changes:
@property() dir = '';
@property() lang = '';

localize: UmbLocalizeController = new UmbLocalizeController(this);

/**
Expand Down
54 changes: 32 additions & 22 deletions src/libs/extension-api/registry/extension.registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,34 +111,17 @@ export class UmbExtensionRegistry<
}

register(manifest: ManifestTypes | ManifestKind<ManifestTypes>): void {
if (!manifest.type) {
console.error(`Extension is missing type`, manifest);
const isValid = this.checkExtension(manifest);
if (!isValid) {
return;
}

if (!manifest.alias) {
console.error(`Extension is missing alias`, manifest);
return;
}

if (manifest.type === 'kind') {
this.defineKind(manifest as ManifestKind<ManifestTypes>);
return;
}

const extensionsValues = this._extensions.getValue();
const extension = extensionsValues.find((extension) => extension.alias === (manifest as ManifestTypes).alias);

if (extension) {
console.error(`Extension with alias ${(manifest as ManifestTypes).alias} is already registered`);
return;
}

this._extensions.next([...extensionsValues, manifest as ManifestTypes]);
this._extensions.next([...this._extensions.getValue(), manifest as ManifestTypes]);
}

registerMany(manifests: Array<ManifestTypes | ManifestKind<ManifestTypes>>): void {
manifests.forEach((manifest) => this.register(manifest));
const validManifests = manifests.filter(this.checkExtension.bind(this));
this._extensions.next([...this._extensions.getValue(), ...(validManifests as Array<ManifestTypes>)]);
}

unregisterMany(aliases: Array<string>): void {
Expand Down Expand Up @@ -172,6 +155,33 @@ export class UmbExtensionRegistry<
}
*/

private checkExtension(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
if (!manifest.type) {
console.error(`Extension is missing type`, manifest);
return false;
}

if (!manifest.alias) {
console.error(`Extension is missing alias`, manifest);
return false;
}

if (manifest.type === 'kind') {
this.defineKind(manifest as ManifestKind<ManifestTypes>);
return false;
}

const extensionsValues = this._extensions.getValue();
const extension = extensionsValues.find((extension) => extension.alias === (manifest as ManifestTypes).alias);

if (extension) {
console.error(`Extension with alias ${(manifest as ManifestTypes).alias} is already registered`);
return false;
}

return true;
}

private _kindsOfType<Key extends keyof ManifestTypeMap<ManifestTypes> | string>(type: Key) {
return this.kinds.pipe(
map((kinds) => kinds.filter((kind) => kind.matchType === type)),
Expand Down
96 changes: 53 additions & 43 deletions src/packages/core/localization/registry/translation.registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@umbraco-cms/backoffice/localization-api';
import { hasDefaultExport, loadExtension } from '@umbraco-cms/backoffice/extension-api';
import { UmbBackofficeExtensionRegistry, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { Subject, combineLatest, map, distinctUntilChanged, Observable } from '@umbraco-cms/backoffice/external/rxjs';
import { Subject, combineLatest, map, distinctUntilChanged, filter } from '@umbraco-cms/backoffice/external/rxjs';

export class UmbTranslationRegistry {
/**
Expand All @@ -18,61 +18,71 @@ export class UmbTranslationRegistry {
}

#currentLanguage = new Subject<string>();
#currentLanguageUnique: Observable<string> = this.#currentLanguage.pipe(
map((x) => x.toLowerCase()),
distinctUntilChanged()
);

constructor(extensionRegistry: UmbBackofficeExtensionRegistry) {
combineLatest([this.#currentLanguageUnique, extensionRegistry.extensionsOfType('translations')]).subscribe(
async ([userCulture, extensions]) => {
const locale = new Intl.Locale(userCulture);
const translations = await Promise.all(
extensions
.filter(
(x) =>
x.meta.culture.toLowerCase() === locale.baseName.toLowerCase() ||
x.meta.culture.toLowerCase() === locale.language.toLowerCase()
)
.map(async (extension) => {
const innerDictionary: UmbTranslationsFlatDictionary = {};
const currentLanguage$ = this.#currentLanguage.pipe(
map((x) => x.toLowerCase()),
distinctUntilChanged()
);

const currentExtensions$ = extensionRegistry.extensionsOfType('translations').pipe(
filter((x) => x.length > 0),
distinctUntilChanged((prev, curr) => prev.length === curr.length && prev.every((x) => curr.includes(x)))
);

// If extension contains a dictionary, add it to the inner dictionary.
if (extension.meta.translations) {
for (const [dictionaryName, dictionary] of Object.entries(extension.meta.translations)) {
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
}
combineLatest([currentLanguage$, currentExtensions$]).subscribe(async ([userCulture, extensions]) => {
const locale = new Intl.Locale(userCulture);
const translations = await Promise.all(
extensions
.filter(
(x) =>
x.meta.culture.toLowerCase() === locale.baseName.toLowerCase() ||
x.meta.culture.toLowerCase() === locale.language.toLowerCase()
)
.map(async (extension) => {
const innerDictionary: UmbTranslationsFlatDictionary = {};

// If extension contains a dictionary, add it to the inner dictionary.
if (extension.meta.translations) {
for (const [dictionaryName, dictionary] of Object.entries(extension.meta.translations)) {
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
}
}

// If extension contains a js file, load it and add the default dictionary to the inner dictionary.
const loadedExtension = await loadExtension(extension);
// If extension contains a js file, load it and add the default dictionary to the inner dictionary.
const loadedExtension = await loadExtension(extension);

if (loadedExtension && hasDefaultExport<UmbTranslationsDictionary>(loadedExtension)) {
for (const [dictionaryName, dictionary] of Object.entries(loadedExtension.default)) {
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
}
if (loadedExtension && hasDefaultExport<UmbTranslationsDictionary>(loadedExtension)) {
for (const [dictionaryName, dictionary] of Object.entries(loadedExtension.default)) {
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
}
}

// Notify subscribers that the inner dictionary has changed.
return {
$code: extension.meta.culture.toLowerCase(),
$dir: extension.meta.direction ?? 'ltr',
...innerDictionary,
} satisfies TranslationSet;
})
);
// Notify subscribers that the inner dictionary has changed.
return {
$code: extension.meta.culture.toLowerCase(),
$dir: extension.meta.direction ?? 'ltr',
...innerDictionary,
} satisfies TranslationSet;
})
);

if (translations.length) {
registerTranslation(...translations);
if (translations.length) {
registerTranslation(...translations);

// Set the document language
document.documentElement.lang = locale.baseName.toLowerCase();
// Set the document language
const newLang = locale.baseName.toLowerCase();
if (document.documentElement.lang.toLowerCase() !== newLang) {
document.documentElement.lang = newLang;
}

// Set the document direction to the direction of the primary language
document.documentElement.dir = translations[0].$dir ?? 'ltr';
// Set the document direction to the direction of the primary language
const newDir = translations[0].$dir ?? 'ltr';
if (document.documentElement.dir !== newDir) {
document.documentElement.dir = newDir;
}
}
);
});
}

/**
Expand Down
33 changes: 31 additions & 2 deletions src/shared/lit-element/lit-element.element.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
import { LitElement } from '@umbraco-cms/backoffice/external/lit';
import { LitElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';

export class UmbLitElement extends UmbElementMixin(LitElement) {}
// TODO: Currently we don't check if the `lang` is registered in the backoffice. We should do that. We can do that by checking if the `lang` is in the `languages` array of the `language` resource and potentially make sure that UmbTranslationRegistry only loads the translations and some other mechanism reloads to another language (currently it does both)

/**
* The base class for all Umbraco LitElement elements.
*
* @abstract
* @remarks This class is a wrapper around the LitElement class.
* @remarks The `dir` and `lang` properties are defined here as reactive properties so they react to language changes.
*/
export class UmbLitElement extends UmbElementMixin(LitElement) {
/**
* The direction of the element.
*
* @attr
* @remarks This is the direction of the element, not the direction of the backoffice.
* @example 'ltr'
* @example 'rtl'
*/
@property() dir: 'rtl' | 'ltr' | '' = '';

/**
* The language of the element.
*
* @attr
* @remarks This is the language of the element, not the language of the backoffice.
* @example 'en-us'
* @example 'en'
*/
@property() lang = '';
}
Loading