Skip to content

Commit

Permalink
fix: condition to register kolibri once (#6107)
Browse files Browse the repository at this point in the history
  • Loading branch information
deleonio authored Mar 1, 2024
2 parents f5818e5 + cb9a6c2 commit f8723ff
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 161 deletions.
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
23 changes: 23 additions & 0 deletions packages/components/src/core/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Generic, LoaderCallback, RegisterOptions } from 'adopted-style-sheets';
import { register as coreRegister } from 'adopted-style-sheets';
import { configI18n, initI18n } from './i18n';

type KoliBriOptions = RegisterOptions & {
// transformTagName?: (tagName: string) => string;
};

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?: KoliBriOptions
): Promise<void[]> => {
await initI18n(options?.translation?.name);
await configI18n(options?.translation?.name ?? 'de', options?.translations);
// configTransformTagName(options.transformTagName);
return await coreRegister(themes, loaders, options);
};

export const register = bootstrap;
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);
};
25 changes: 0 additions & 25 deletions packages/components/src/core/index.ts

This file was deleted.

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

0 comments on commit f8723ff

Please sign in to comment.