#549 Done
This commit is contained in:
parent
956b8260e7
commit
2f42732deb
@ -1,11 +1,9 @@
|
|||||||
import "minimal-polyfills/Object.fromEntries";
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
|
||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import fallbackMessages from "./baseMessages/en";
|
import fallbackMessages from "./baseMessages/en";
|
||||||
import { getMessages } from "./baseMessages";
|
import { getMessages } from "./baseMessages";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { KcContext } from "../kcContext/KcContext";
|
import type { KcContext } from "../kcContext/KcContext";
|
||||||
import { Markdown } from "keycloakify/tools/Markdown";
|
|
||||||
|
|
||||||
export const fallbackLanguageTag = "en";
|
export const fallbackLanguageTag = "en";
|
||||||
|
|
||||||
@ -53,16 +51,31 @@ export type GenericI18n<MessageKey extends string> = {
|
|||||||
*/
|
*/
|
||||||
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
||||||
/**
|
/**
|
||||||
|
* This is meant to be used when the key argument is variable, something that might have been configured by the user
|
||||||
|
* in the Keycloak admin for example.
|
||||||
|
*
|
||||||
* Examples assuming currentLanguageTag === "en"
|
* Examples assuming currentLanguageTag === "en"
|
||||||
* advancedMsg("${access-denied} foo bar") === <span>${msgStr("access-denied")} foo bar<span> === <span>Access denied foo bar</span>
|
* {
|
||||||
|
* en: {
|
||||||
|
* "access-denied": "Access denied",
|
||||||
|
* "foo": "Foo {0} {1}",
|
||||||
|
* "bar": "Bar {0}"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* advancedMsg("${access-denied} foo bar") === <span>{msgStr("access-denied")} foo bar<span> === <span>Access denied foo bar</span>
|
||||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied") === <span>Access denied</span>
|
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied") === <span>Access denied</span>
|
||||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === <span>not-a-message-key</span>
|
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === <span>not-a-message-key</span>
|
||||||
|
* advancedMsg("${bar}", "<strong>c</strong>")
|
||||||
|
* === <span>{msgStr("bar", "<strong>XXX</strong>")}<span>
|
||||||
|
* === <span>Bar <strong>XXX</strong></span> (The html in the arg is partially escaped for security reasons, it might be untrusted)
|
||||||
|
* advancedMsg("${foo} xx ${bar}", "a", "b", "c")
|
||||||
|
* === <span>{msgStr("foo", "a", "b")} xx {msgStr("bar")}<span>
|
||||||
|
* === <span>Foo a b xx Bar {0}</span> (The substitution are only applied in the first message)
|
||||||
*/
|
*/
|
||||||
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
||||||
/**
|
/**
|
||||||
* Examples assuming currentLanguageTag === "en"
|
* See advancedMsg() but instead of returning a JSX.Element it returns a string.
|
||||||
* advancedMsg("${access-denied} foo bar") === msg("access-denied") + " foo bar" === "Access denied foo bar"
|
|
||||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
|
||||||
*/
|
*/
|
||||||
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
||||||
};
|
};
|
||||||
@ -133,8 +146,8 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
|||||||
}): Pick<GenericI18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
|
}): Pick<GenericI18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
|
||||||
const { fallbackMessages, messages } = params;
|
const { fallbackMessages, messages } = params;
|
||||||
|
|
||||||
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderMarkdown: boolean }): string | JSX.Element | undefined {
|
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
|
||||||
const { key, args, doRenderMarkdown } = props;
|
const { key, args, doRenderAsHtml } = props;
|
||||||
|
|
||||||
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
|
const messageOrUndefined: string | undefined = (messages as any)[key] ?? (fallbackMessages as any)[key];
|
||||||
|
|
||||||
@ -163,51 +176,67 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
messageWithArgsInjected = messageWithArgsInjected.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
messageWithArgsInjected = messageWithArgsInjected.replace(
|
||||||
|
new RegExp(`\\{${i + startIndex}\\}`, "g"),
|
||||||
|
arg.replace(/</g, "<").replace(/>/g, ">")
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return messageWithArgsInjected;
|
return messageWithArgsInjected;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return doRenderMarkdown ? (
|
return doRenderAsHtml ? (
|
||||||
<Markdown allowDangerousHtml renderers={{ paragraph: "span" }}>
|
<span
|
||||||
{messageWithArgsInjectedIfAny}
|
// NOTE: The message is trusted. The arguments are not but are escaped.
|
||||||
</Markdown>
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: messageWithArgsInjectedIfAny
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
messageWithArgsInjectedIfAny
|
messageWithArgsInjectedIfAny
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderMarkdown: boolean }): JSX.Element | string {
|
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
|
||||||
const { key, args, doRenderMarkdown } = props;
|
const { key, args, doRenderAsHtml } = props;
|
||||||
|
|
||||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
if (!/\$\{[^}]+\}/.test(key)) {
|
||||||
|
const resolvedMessage = resolveMsg({ key, args, doRenderAsHtml });
|
||||||
|
|
||||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
if (resolvedMessage === undefined) {
|
||||||
|
return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: key }} /> : key;
|
||||||
|
}
|
||||||
|
|
||||||
const out = resolveMsg({
|
return resolvedMessage;
|
||||||
key: keyUnwrappedFromCurlyBraces,
|
}
|
||||||
args,
|
|
||||||
doRenderMarkdown
|
let isFirstMatch = true;
|
||||||
|
|
||||||
|
const resolvedComplexMessage = key.replace(/\$\{([^}]+)\}/g, (...[, key_i]) => {
|
||||||
|
const replaceBy = resolveMsg({ key: key_i, args: isFirstMatch ? args : [], doRenderAsHtml: false }) ?? key_i;
|
||||||
|
|
||||||
|
isFirstMatch = false;
|
||||||
|
|
||||||
|
return replaceBy;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: resolvedComplexMessage }} /> : resolvedComplexMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
msgStr: (key, ...args) => resolveMsg({ key, args, doRenderMarkdown: false }) as string,
|
msgStr: (key, ...args) => resolveMsg({ key, args, doRenderAsHtml: false }) as string,
|
||||||
msg: (key, ...args) => resolveMsg({ key, args, doRenderMarkdown: true }) as JSX.Element,
|
msg: (key, ...args) => resolveMsg({ key, args, doRenderAsHtml: true }) as JSX.Element,
|
||||||
advancedMsg: (key, ...args) =>
|
advancedMsg: (key, ...args) =>
|
||||||
resolveMsgAdvanced({
|
resolveMsgAdvanced({
|
||||||
key,
|
key,
|
||||||
args,
|
args,
|
||||||
doRenderMarkdown: true
|
doRenderAsHtml: true
|
||||||
}) as JSX.Element,
|
}) as JSX.Element,
|
||||||
advancedMsgStr: (key, ...args) =>
|
advancedMsgStr: (key, ...args) =>
|
||||||
resolveMsgAdvanced({
|
resolveMsgAdvanced({
|
||||||
key,
|
key,
|
||||||
args,
|
args,
|
||||||
doRenderMarkdown: false
|
doRenderAsHtml: false
|
||||||
}) as string
|
}) as string
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import type { PageProps } from "keycloakify/account/pages/PageProps";
|
|||||||
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
import { MessageKey } from "keycloakify/account/i18n/i18n";
|
|
||||||
|
|
||||||
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>, I18n>) {
|
export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
@ -15,7 +14,7 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
|
|||||||
|
|
||||||
const { totp, mode, url, messagesPerField, stateChecker } = kcContext;
|
const { totp, mode, url, messagesPerField, stateChecker } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr, advancedMsg } = i18n;
|
||||||
|
|
||||||
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
|
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
|
||||||
HmacSHA1: "SHA1",
|
HmacSHA1: "SHA1",
|
||||||
@ -78,9 +77,7 @@ export default function Totp(props: PageProps<Extract<KcContext, { pageId: "totp
|
|||||||
<li>
|
<li>
|
||||||
<p>{msg("totpStep1")}</p>
|
<p>{msg("totpStep1")}</p>
|
||||||
|
|
||||||
<ul id="kc-totp-supported-apps">
|
<ul id="kc-totp-supported-apps">{totp.supportedApplications?.map(app => <li key={app}>{advancedMsg(app)}</li>)}</ul>
|
||||||
{totp.supportedApplications?.map(app => <li key={app}>{msg(app as MessageKey)}</li>)}
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{mode && mode == "manual" ? (
|
{mode && mode == "manual" ? (
|
||||||
|
@ -52,16 +52,31 @@ export type GenericI18n<MessageKey extends string> = {
|
|||||||
*/
|
*/
|
||||||
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
||||||
/**
|
/**
|
||||||
|
* This is meant to be used when the key argument is variable, something that might have been configured by the user
|
||||||
|
* in the Keycloak admin for example.
|
||||||
|
*
|
||||||
* Examples assuming currentLanguageTag === "en"
|
* Examples assuming currentLanguageTag === "en"
|
||||||
* advancedMsg("${access-denied} foo bar") === <span>${msgStr("access-denied")} foo bar<span> === <span>Access denied foo bar</span>
|
* {
|
||||||
|
* en: {
|
||||||
|
* "access-denied": "Access denied",
|
||||||
|
* "foo": "Foo {0} {1}",
|
||||||
|
* "bar": "Bar {0}"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* advancedMsg("${access-denied} foo bar") === <span>{msgStr("access-denied")} foo bar<span> === <span>Access denied foo bar</span>
|
||||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied") === <span>Access denied</span>
|
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied") === <span>Access denied</span>
|
||||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === <span>not-a-message-key</span>
|
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === <span>not-a-message-key</span>
|
||||||
|
* advancedMsg("${bar}", "<strong>c</strong>")
|
||||||
|
* === <span>{msgStr("bar", "<strong>XXX</strong>")}<span>
|
||||||
|
* === <span>Bar <strong>XXX</strong></span> (The html in the arg is partially escaped for security reasons, it might be untrusted)
|
||||||
|
* advancedMsg("${foo} xx ${bar}", "a", "b", "c")
|
||||||
|
* === <span>{msgStr("foo", "a", "b")} xx {msgStr("bar")}<span>
|
||||||
|
* === <span>Foo a b xx Bar {0}</span> (The substitution are only applied in the first message)
|
||||||
*/
|
*/
|
||||||
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
advancedMsg: (key: string, ...args: (string | undefined)[]) => JSX.Element;
|
||||||
/**
|
/**
|
||||||
* Examples assuming currentLanguageTag === "en"
|
* See advancedMsg() but instead of returning a JSX.Element it returns a string.
|
||||||
* advancedMsg("${access-denied} foo bar") === msg("access-denied") + " foo bar" === "Access denied foo bar"
|
|
||||||
* advancedMsg("${not-a-message-key}") === advancedMsg("not-a-message-key") === "not-a-message-key"
|
|
||||||
*/
|
*/
|
||||||
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
||||||
};
|
};
|
||||||
@ -132,7 +147,7 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
|||||||
messages: Record<MessageKey, string>;
|
messages: Record<MessageKey, string>;
|
||||||
__localizationRealmOverridesUserProfile: Record<string, string>;
|
__localizationRealmOverridesUserProfile: Record<string, string>;
|
||||||
}): Pick<GenericI18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
|
}): Pick<GenericI18n<MessageKey>, "msg" | "msgStr" | "advancedMsg" | "advancedMsgStr"> {
|
||||||
const { fallbackMessages, messages /*__localizationRealmOverridesUserProfile*/ } = params;
|
const { fallbackMessages, messages, __localizationRealmOverridesUserProfile } = params;
|
||||||
|
|
||||||
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
|
function resolveMsg(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): string | JSX.Element | undefined {
|
||||||
const { key, args, doRenderAsHtml } = props;
|
const { key, args, doRenderAsHtml } = props;
|
||||||
@ -188,26 +203,42 @@ function createI18nTranslationFunctions<MessageKey extends string>(params: {
|
|||||||
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
|
function resolveMsgAdvanced(props: { key: string; args: (string | undefined)[]; doRenderAsHtml: boolean }): JSX.Element | string {
|
||||||
const { key, args, doRenderAsHtml } = props;
|
const { key, args, doRenderAsHtml } = props;
|
||||||
|
|
||||||
// TODO:
|
if (key in __localizationRealmOverridesUserProfile) {
|
||||||
/*
|
const resolvedMessage = __localizationRealmOverridesUserProfile[key];
|
||||||
if( key in __localizationRealmOverridesUserProfile ){
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return doRenderAsHtml ? (
|
||||||
|
<span
|
||||||
|
// NOTE: The message is trusted. The arguments are not but are escaped.
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: resolvedMessage
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
resolvedMessage
|
||||||
|
);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
if (!/\$\{[^}]+\}/.test(key)) {
|
||||||
|
const resolvedMessage = resolveMsg({ key, args, doRenderAsHtml });
|
||||||
|
|
||||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
if (resolvedMessage === undefined) {
|
||||||
|
return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: key }} /> : key;
|
||||||
|
}
|
||||||
|
|
||||||
const out = resolveMsg({
|
return resolvedMessage;
|
||||||
key: keyUnwrappedFromCurlyBraces,
|
}
|
||||||
args,
|
|
||||||
doRenderAsHtml
|
let isFirstMatch = true;
|
||||||
|
|
||||||
|
const resolvedComplexMessage = key.replace(/\$\{([^}]+)\}/g, (...[, key_i]) => {
|
||||||
|
const replaceBy = resolveMsg({ key: key_i, args: isFirstMatch ? args : [], doRenderAsHtml: false }) ?? key_i;
|
||||||
|
|
||||||
|
isFirstMatch = false;
|
||||||
|
|
||||||
|
return replaceBy;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (out !== undefined ? out : doRenderAsHtml ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
return doRenderAsHtml ? <span dangerouslySetInnerHTML={{ __html: resolvedComplexMessage }} /> : resolvedComplexMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -3,7 +3,6 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
|
|||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
import { MessageKey } from "keycloakify/login/i18n/i18n";
|
|
||||||
|
|
||||||
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {
|
export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pageId: "login-config-totp.ftl" }>, I18n>) {
|
||||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||||
@ -15,7 +14,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
|
|
||||||
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
|
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr, advancedMsg } = i18n;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("loginTotpTitle")}>
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("loginTotpTitle")}>
|
||||||
@ -26,7 +25,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
|||||||
|
|
||||||
<ul id="kc-totp-supported-apps">
|
<ul id="kc-totp-supported-apps">
|
||||||
{totp.supportedApplications.map(app => (
|
{totp.supportedApplications.map(app => (
|
||||||
<li>{msg(app as MessageKey)}</li>
|
<li>{advancedMsg(app)}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { useEffect, Fragment } from "react";
|
import { useEffect, Fragment } from "react";
|
||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import type { MessageKey } from "keycloakify/login/i18n/i18n";
|
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
@ -29,7 +28,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
shouldDisplayAuthenticators
|
shouldDisplayAuthenticators
|
||||||
} = kcContext;
|
} = kcContext;
|
||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr, advancedMsg } = i18n;
|
||||||
|
|
||||||
const { insertScriptTags } = useInsertScriptTags({
|
const { insertScriptTags } = useInsertScriptTags({
|
||||||
scriptTags: [
|
scriptTags: [
|
||||||
@ -198,7 +197,7 @@ export default function WebauthnAuthenticate(props: PageProps<Extract<KcContext,
|
|||||||
id="kc-webauthn-authenticator-label"
|
id="kc-webauthn-authenticator-label"
|
||||||
className={getClassName("kcSelectAuthListItemHeadingClass")}
|
className={getClassName("kcSelectAuthListItemHeadingClass")}
|
||||||
>
|
>
|
||||||
{msg(authenticator.label as MessageKey)}
|
{advancedMsg(authenticator.label)}
|
||||||
</div>
|
</div>
|
||||||
{authenticator.transports.displayNameProperties?.length && (
|
{authenticator.transports.displayNameProperties?.length && (
|
||||||
<div
|
<div
|
||||||
|
Loading…
x
Reference in New Issue
Block a user