143 lines
5.1 KiB
TypeScript
Raw Normal View History

2024-07-13 09:07:11 +02:00
import { useEffect, useState } from "react";
2024-09-22 20:41:18 +02:00
import { kcSanitize } from "keycloakify/lib/kcSanitize";
2024-09-21 17:59:16 +02:00
import { createGetI18n, type KcContextLike } from "../noJsx/getI18n";
import type { GenericI18n_noJsx } from "../noJsx/GenericI18n_noJsx";
2024-07-13 09:07:11 +02:00
import { Reflect } from "tsafe/Reflect";
2024-09-21 17:59:16 +02:00
import type { GenericI18n } from "./GenericI18n";
import type { LanguageTag as LanguageTag_defaultSet, MessageKey as MessageKey_defaultSet } from "../messages_defaultSet/types";
export type ReturnTypeOfCreateUseI18n<MessageKey_themeDefined extends string, LanguageTag_notInDefaultSet extends string> = {
useI18n: (params: { kcContext: KcContextLike }) => {
i18n: GenericI18n<MessageKey_defaultSet | MessageKey_themeDefined, LanguageTag_defaultSet | LanguageTag_notInDefaultSet>;
};
ofTypeI18n: GenericI18n<MessageKey_defaultSet | MessageKey_themeDefined, LanguageTag_defaultSet | LanguageTag_notInDefaultSet>;
};
export { KcContextLike };
export function createUseI18n<
ThemeName extends string = string,
2024-09-21 17:59:16 +02:00
MessageKey_themeDefined extends string = never,
LanguageTag_notInDefaultSet extends string = never
>(params: {
2024-09-21 17:59:16 +02:00
extraLanguageTranslations: {
[languageTag in LanguageTag_notInDefaultSet]: {
label: string;
getMessages: () => Promise<{ default: Record<MessageKey_defaultSet, string> }>;
};
};
2024-09-21 17:59:16 +02:00
messagesByLanguageTag_themeDefined: Partial<{
[languageTag in LanguageTag_defaultSet | LanguageTag_notInDefaultSet]: {
[key in MessageKey_themeDefined]: string | Record<ThemeName, string>;
};
}>;
}): ReturnTypeOfCreateUseI18n<MessageKey_themeDefined, LanguageTag_notInDefaultSet> {
const { extraLanguageTranslations, messagesByLanguageTag_themeDefined } = params;
type LanguageTag = LanguageTag_defaultSet | LanguageTag_notInDefaultSet;
2024-07-13 09:07:11 +02:00
type MessageKey = MessageKey_defaultSet | MessageKey_themeDefined;
type I18n = GenericI18n<MessageKey, LanguageTag>;
2024-07-13 09:07:11 +02:00
2024-09-21 17:59:16 +02:00
type Result = { i18n: I18n };
2024-07-13 09:07:11 +02:00
const { withJsx } = (() => {
const cache = new WeakMap<GenericI18n_noJsx<MessageKey, LanguageTag>, GenericI18n<MessageKey, LanguageTag>>();
2024-07-13 09:07:11 +02:00
2024-07-14 08:11:17 +02:00
function renderHtmlString(params: { htmlString: string; msgKey: string }): JSX.Element {
const { htmlString, msgKey } = params;
2024-11-21 07:12:23 +01:00
const htmlString_sanitized = kcSanitize(htmlString);
const Element = (() => {
if (htmlString_sanitized.includes("<") && htmlString_sanitized.includes(">")) {
for (const tagName of ["div", "section", "article", "ul", "ol"]) {
if (htmlString_sanitized.includes(`<${tagName}`)) {
return "div";
}
}
}
return "span";
})();
2024-07-13 09:07:11 +02:00
return (
2024-11-21 07:12:23 +01:00
<Element
2024-07-14 08:11:17 +02:00
data-kc-msg={msgKey}
2024-07-13 09:07:11 +02:00
dangerouslySetInnerHTML={{
2024-11-21 07:12:23 +01:00
__html: htmlString_sanitized
2024-07-13 09:07:11 +02:00
}}
/>
);
}
function withJsx(i18n_noJsx: GenericI18n_noJsx<MessageKey, LanguageTag>): I18n {
2024-07-13 09:07:11 +02:00
use_cache: {
const i18n = cache.get(i18n_noJsx);
if (i18n === undefined) {
break use_cache;
}
return i18n;
}
const i18n: I18n = {
...i18n_noJsx,
2024-07-14 08:11:17 +02:00
msg: (msgKey, ...args) => renderHtmlString({ htmlString: i18n_noJsx.msgStr(msgKey, ...args), msgKey }),
advancedMsg: (msgKey, ...args) => renderHtmlString({ htmlString: i18n_noJsx.advancedMsgStr(msgKey, ...args), msgKey })
2024-07-13 09:07:11 +02:00
};
cache.set(i18n_noJsx, i18n);
return i18n;
}
return { withJsx };
})();
2024-07-14 08:11:17 +02:00
add_style: {
2024-11-21 07:12:23 +01:00
const attributeName = "data-kc-msg";
2024-07-14 08:11:17 +02:00
// Check if already exists in head
if (document.querySelector(`style[${attributeName}]`) !== null) {
break add_style;
}
const styleElement = document.createElement("style");
styleElement.attributes.setNamedItem(document.createAttribute(attributeName));
2024-11-21 07:12:23 +01:00
styleElement.textContent = `div[${attributeName}] { display: inline-block; }`;
2024-10-21 21:31:22 +02:00
document.head.prepend(styleElement);
2024-07-14 08:11:17 +02:00
}
2024-09-21 17:59:16 +02:00
const { getI18n } = createGetI18n({ extraLanguageTranslations, messagesByLanguageTag_themeDefined });
2024-07-13 09:07:11 +02:00
2024-09-21 17:59:16 +02:00
function useI18n(params: { kcContext: KcContextLike }): Result {
2024-07-13 09:07:11 +02:00
const { kcContext } = params;
const { i18n, prI18n_currentLanguage } = getI18n({ kcContext });
const [i18n_toReturn, setI18n_toReturn] = useState<I18n>(withJsx(i18n));
useEffect(() => {
let isActive = true;
prI18n_currentLanguage?.then(i18n => {
if (!isActive) {
return;
}
setI18n_toReturn(withJsx(i18n));
});
return () => {
isActive = false;
};
}, []);
return { i18n: i18n_toReturn };
}
return { useI18n, ofTypeI18n: Reflect<I18n>() };
}