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

refactor: remove Modal and I18n from Window #6119

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
14 changes: 7 additions & 7 deletions packages/components/src/components/modal/component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import type { KoliBriModalEventCallbacks, LabelPropType, ModalAPI, ModalStates } from '@public-ui/schema';
import { featureHint, setState, validateLabel, watchString, watchValidator } from '@public-ui/schema';
import { Component, h, Host, Prop, State, Watch } from '@stencil/core';

import { getKoliBri } from '../../utils/dev.utils';
import { Component, Host, Prop, State, Watch, h } from '@stencil/core';

import type { JSX } from '@stencil/core';
import type { ModalService } from './service';
import { ModalService } from './service';

const modalService = new ModalService();

/**
* https://en.wikipedia.org/wiki/Modal_window
Expand All @@ -29,16 +29,16 @@ export class KolModal implements ModalAPI {
public componentDidRender(): void {
if (this.hostElement /* SSR instanceof HTMLElement */) {
if (this.state._activeElement /* SSR instanceof HTMLElement */) {
(getKoliBri().Modal as ModalService).openModal(this.hostElement, this.state._activeElement);
modalService.openModal(this.hostElement, this.state._activeElement);
} else {
(getKoliBri().Modal as ModalService).closeModal(this.hostElement);
modalService.closeModal(this.hostElement);
}
}
}

public disconnectedCallback(): void {
if (this.hostElement /* SSR instanceof HTMLElement */) {
(getKoliBri().Modal as ModalService).closeModal(this.hostElement);
modalService.closeModal(this.hostElement);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import type { Generic, LoaderCallback, RegisterOptions } from 'adopted-style-sheets';
import { register as coreRegister } from 'adopted-style-sheets';
import { configKoliBri } from './config';
import { initI18n } from './i18n';

import { getKoliBri } from '../utils/dev.utils';
import { I18nextService } from './i18n';

import type { II18nService } from './i18n';
export const register = async (
export const bootstrap = async (
themes:
| Generic.Theming.RegisterPatch<string, string, string>
| Generic.Theming.RegisterPatch<string, string, string>[]
| Set<Generic.Theming.RegisterPatch<string, string, string>>,
loaders: LoaderCallback | LoaderCallback[] | Set<LoaderCallback>,
options?: RegisterOptions
): Promise<void[]> => {
if (getKoliBri().I18n === undefined) {
Object.defineProperty(getKoliBri(), 'I18n', {
value: await I18nextService.createInstance(options?.translation?.name ?? 'de', options?.translations),
writable: false,
});
}
await initI18n(options?.translation?.name);
await configKoliBri({
translation: options?.translation,
translations: options?.translations,
});
return await coreRegister(themes, loaders, options);
};

export const getI18nService: () => II18nService | undefined = () => getKoliBri().I18n as II18nService;
export const register = bootstrap;
11 changes: 11 additions & 0 deletions packages/components/src/core/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { RegisterOptions } from 'adopted-style-sheets';
import { configI18n } from './i18n';

type KoliBriConfigOptions = Omit<RegisterOptions, 'theme'> & {
// transformTagName?: (tagName: string) => string;
};

export const configKoliBri = async (options: KoliBriConfigOptions) => {
await configI18n(options.translation?.name ?? 'de', options?.translations);
// configTransformTagName(options.transformTagName);
};
111 changes: 71 additions & 40 deletions packages/components/src/core/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,72 +14,78 @@ interface ITranslationOptions {
placeholders?: { [K: string]: string };
}

export interface II18nService {
/**
* Adds a resource bundle for the specified language.
* @param lng the language the bundle is for
* @param translationMap the translations of the given language
*/
addResourceBundle: (lng: Generic.I18n.Locale.ISO_639_1, translationMap: Generic.I18n.Map<string, string>) => void;
/**
* Determines a human-readable translated text for the given resource key.
* @param key the resource key
* @param options optional translation parameters
* @returns the translated text
*/
translate: (key: string, options?: ITranslationOptions) => string;
}

export class I18nextService implements II18nService {
private static instance: II18nService;
export class I18nextService {
private static instance: I18nextService;
private static namespace = 'KoliBri';
private _initialized = false;

get initialized() {
return this._initialized;
}

private constructor() {}

public static async createInstance(
lng: Generic.I18n.Locale.ISO_639_1,
translations?:
| Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>
| Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>[]
| Set<Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>>
): Promise<II18nService> {
if (Array.isArray(translations)) {
translations = new Set(translations);
} else if (typeof translations === 'function') {
translations = new Set([translations]);
public static async getInstance(lng?: Generic.I18n.Locale.ISO_639_1): Promise<I18nextService> {
if (I18nextService.instance instanceof I18nextService) {
return I18nextService.instance;
}

I18nextService.instance = new I18nextService();

if (!i18next.isInitialized) {
// https://www.i18next.com/overview/api#init
await i18next.init({
ns: [I18nextService.namespace],
lng,
lng: lng ?? 'de',
});
} else {
// https://www.i18next.com/overview/api#loadnamespaces
await i18next.loadNamespaces(I18nextService.namespace);
}

return I18nextService.instance;
}

/**
* Adds a resource bundle for the specified language.
* @param lng the language the bundle is for
* @param translationMap the translations of the given language
*/
public addTranslations(
translations?:
| Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>
| Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>[]
| Set<Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>>
) {
if (Array.isArray(translations)) {
translations = new Set(translations);
} else if (typeof translations === 'function') {
translations = new Set([translations]);
}

if (translations !== undefined) {
translations.forEach((t) =>
t((l, t) => {
I18nextService.instance.addResourceBundle(l, t);
i18next.addResourceBundle(l, I18nextService.namespace, t, true);
return l;
})
);
}

return this.instance;
}

public static addResourceBundle(lng: Generic.I18n.Locale.ISO_639_1, translationMap: Generic.I18n.Map<string, string>) {
i18next.addResourceBundle(lng, I18nextService.namespace, translationMap, true);
}

public addResourceBundle(lng: Generic.I18n.Locale.ISO_639_1, translationMap: Generic.I18n.Map<string, string>) {
I18nextService.addResourceBundle(lng, translationMap);
/**
* Set the current language.
* @param lng the language the bundle is for
*/
public async setLanguage(lng: Generic.I18n.Locale.ISO_639_1) {
await i18next.changeLanguage(lng);
}

/**
* Determines a human-readable translated text for the given resource key.
* @param key the resource key
* @param options optional translation parameters
* @returns the translated text
*/
public translate(key: string, options?: ITranslationOptions) {
return i18next.t(key, {
ns: I18nextService.namespace,
Expand All @@ -88,3 +94,28 @@ export class I18nextService implements II18nService {
});
}
}

let i18n: I18nextService;

export const getI18nInstance = () => {
if (!(i18n instanceof I18nextService)) {
throw new Error('i18n not initialized yet');
}
return i18n;
};

export const initI18n = async (lng?: Generic.I18n.Locale.ISO_639_1): Promise<I18nextService> => {
i18n = await I18nextService.getInstance(lng);
return i18n;
};

export const configI18n = async (
lng: Generic.I18n.Locale.ISO_639_1,
translations?:
| Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>
| Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>[]
| Set<Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, string, string>>
) => {
await initI18n(lng);
i18n.addTranslations(translations);
};
15 changes: 14 additions & 1 deletion packages/components/src/global/devtools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getDevMode,
getDocument,
getExperimentalMode,
getWindow,
koliBriA11yColorContrast,
koliBriQuerySelector,
koliBriQuerySelectorAll,
Expand All @@ -12,7 +13,19 @@ import {
parseJson,
stringifyJson,
} from '@public-ui/schema';
import { getKoliBri, initKoliBri, renderDevAdvice } from '../utils/dev.utils';
import { initKoliBri, renderDevAdvice } from '../utils/dev.utils';

const getKoliBri = (): Record<string, unknown> => {
let kolibri = getWindow().KoliBri;
if (kolibri === undefined) {
kolibri = {};
Object.defineProperty(getWindow(), 'KoliBri', {
value: kolibri,
writable: false,
});
}
return kolibri;
};

function prototypeKoliBri<T>(name: string, cb: T) {
try {
Expand Down
57 changes: 25 additions & 32 deletions packages/components/src/global/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,32 @@ import { getThemeDetails, setThemeStyle } from 'adopted-style-sheets';
import { Log, processEnv } from '@public-ui/schema';
import { setMode } from '@stencil/core';

import { register } from '../core';
setMode((elm) => {
try {
if (elm.shadowRoot instanceof ShadowRoot) {
setThemeStyle(elm, getThemeDetails(elm));
}
} catch (error) {
/**
* Try is needed for SSR.
* - no HTMLElement is available
* - no ShadowRoot is available
*/
}
return 'default';
});

// ts-prune-ignore-next
export default async (): Promise<void> => {
setMode((elm) => {
try {
if (elm.shadowRoot instanceof ShadowRoot) {
setThemeStyle(elm, getThemeDetails(elm));
}
} catch (error) {
/**
* Try is needed for SSR.
* - no HTMLElement is available
* - no ShadowRoot is available
*/
import('./devtools')
.then((devTools) => {
if (typeof devTools === 'object' && devTools !== null && typeof devTools.initialize === 'function') {
devTools.initialize();
}
return 'default';
})
.catch((error) => {
Log.error(error);
});

await register([], []);

import('./devtools')
.then((devTools) => {
if (typeof devTools === 'object' && devTools !== null && typeof devTools.initialize === 'function') {
devTools.initialize();
}
})
.catch((error) => {
Log.error(error);
});

/* Import scripts necessary for the development server, i.e. the /dev/*.html files. Only include in development environment. */
if (processEnv) {
import('../dev');
}
};
/* Import scripts necessary for the development server, i.e. the /dev/*.html files. Only include in development environment. */
if (processEnv) {
import('../dev');
}
36 changes: 10 additions & 26 deletions packages/components/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { processEnv } from '@public-ui/schema';
import type { Generic } from 'adopted-style-sheets';

/**
* Issue #2456: Don't use json files
* - https://github.com/public-ui/kolibri/issues/2456
* - use instead ts files
*/
import { devHint } from '@public-ui/schema';

import { getI18nService } from './core';
import { getI18nInstance } from './core/i18n';
import locale_de from './locales/de';
import locale_en from './locales/en';

Expand All @@ -17,7 +10,7 @@ type ComponentKeys = keyof typeof locale_de;
const mapLocaleKeys = (locale: { [K in ComponentKeys]: string }) =>
(Object.keys(locale) as ComponentKeys[]).reduce((a, c) => ((a[`${'kol'}-${c}`] = locale[c]), a), {} as Generic.I18n.Map<ResourcePrefix, ComponentKeys>);

export const translations = new Set<Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, ResourcePrefix, ComponentKeys>>([
const translations = new Set<Generic.I18n.RegisterPatch<Generic.I18n.Locale.ISO_639_1, ResourcePrefix, ComponentKeys>>([
(t: (language: 'en', translationMap: Generic.I18n.Map<ResourcePrefix, ComponentKeys>) => Generic.I18n.Locale.ISO_639_1) => t('en', mapLocaleKeys(locale_en)),
(t: (language: 'de', translationMap: Generic.I18n.Map<ResourcePrefix, ComponentKeys>) => Generic.I18n.Locale.ISO_639_1) => t('de', mapLocaleKeys(locale_de)),
]);
Expand All @@ -27,25 +20,16 @@ type Options = {
placeholders?: { [K: string]: string };
};

export const translate = (key: `${Lowercase<ResourcePrefix>}-${Lowercase<ComponentKeys>}`, options?: Options) => {
const i18n = getI18nService();
if (i18n === undefined) {
devHint('[I18n] I18nService not available! Please call the global register function.');
return key;
}

export let translate = (key: `${Lowercase<ResourcePrefix>}-${Lowercase<ComponentKeys>}`, options?: Options) => {
const i18n = getI18nInstance();
let text = i18n.translate(key, options);
if (text === key) {
devHint('[I18n] Locales not initialized! Initialize default locales automatically.');

translations.forEach((t) =>
t((l, t) => {
i18n.addResourceBundle(l, t);
return l;
})
);

i18n.addTranslations(translations);
text = i18n.translate(key, options);
}
return text;
};

if (processEnv === 'test') {
translate = (key): string => key;
}
4 changes: 1 addition & 3 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
export { setCurrentLocation } from './components/link/ariaCurrentService';
export * from './components.d';
export { register } from './core';
export { bootstrap, register } from './core/bootstrap';
export * from './enums/bund';
export { translations } from './i18n';
export * from './kolibri';
export { ToasterService } from './components/toaster/toaster';
export type { Toast, ToasterOptions } from '@public-ui/schema';
export { Optgroup, Option, SelectOption } from '@public-ui/schema';
export { configKoliBri } from './utils/dev.utils';
export { KoliBriDevHelper } from '@public-ui/schema';
Loading
Loading