Don't worry, this is not like v9 to v10, this update is very easy.
See release note:
Pay close attention. The syntax hylighting of gitbook for diffs is buggy. Don't look only at the colors. Look at the +
and -
symbols at the start of each line.
-import { createUseI18n } from "keycloakify/login";
+import { i18nBuilder } from "keycloakify/login";
+import type { ThemeName } from "../kc.gen";
-export const { useI18n, ofTypeI18n } = createUseI18n({
+const { useI18n, ofTypeI18n } = i18nBuilder
+ .withThemeName<ThemeName>()
+ .withCustomTranslations({
en: {
backToLogin: "⏪ Back to <strong>Login page</strong>",
myCustomKey: "My custom message",
},
fr: {
backToLogin: "⏪ Retour à la <strong>page de Login</strong>",
myCustomKey: "Mon message personalisé",
},
})
+ .build()
-export type I18n = typeof ofTypeI18n;
+type I18n = typeof ofTypeI18n;
+export { useI18n, type I18n };
If you have ejected the Template.tsx the language selector code has changed a little:
- const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
+ const { realm, auth, url, message, isAppInitiatedAction } = kcContext;
- const { msg, msgStr, currentLanguageTag } = i18n;
+ const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;
// ...
- useEffect(() => {
- const { currentLanguageTag } = locale ?? {};
- if (currentLanguageTag === undefined) {
- return;
- }
- const html = document.querySelector("html");
- assert(html !== null);
- html.lang = currentLanguageTag;
- }, []);
return (
<div className={kcClsx("kcLoginClass")}>
<div id="kc-header" className={kcClsx("kcHeaderClass")}>
<div id="kc-header-wrapper" className={kcClsx("kcHeaderWrapperClass")}>
{msg("loginTitleHtml", realm.displayNameHtml)}
</div>
</div>
<div className={kcClsx("kcFormCardClass")}>
<header className={kcClsx("kcFormHeaderClass")}>
- {realm.internationalizationEnabled && (assert(locale !== undefined), locale.supported.length > 1) && (
+ {enabledLanguages.length > 1 && (
<div className={kcClsx("kcLocaleMainClass")} id="kc-locale">
<div id="kc-locale-wrapper" className={kcClsx("kcLocaleWrapperClass")}>
<div id="kc-locale-dropdown" className={clsx("menu-button-links", kcClsx("kcLocaleDropDownClass"))}>
<button
tabIndex={1}
id="kc-current-locale-link"
aria-label={msgStr("languages")}
aria-haspopup="true"
aria-expanded="false"
aria-controls="language-switch1"
>
- {labelBySupportedLanguageTag[currentLanguageTag]}
+ {currentLanguage.label}
</button>
<ul
role="menu"
tabIndex={-1}
aria-labelledby="kc-current-locale-link"
aria-activedescendant=""
id="language-switch1"
className={kcClsx("kcLocaleListClass")}
>
- {locale.supported.map(({ languageTag }, i) => (
- <li key={languageTag} className={kcClsx("kcLocaleListItemClass")} role="none">
- <a
- role="menuitem"
- id={`language-${i + 1}`}
- className={kcClsx("kcLocaleItemClass")}
- href={getChangeLocaleUrl(languageTag)}
- >
- {labelBySupportedLanguageTag[languageTag]}
+ {enabledLanguages.map(({ languageTag, label, href }, i) => (
+ <li key={languageTag} className={kcClsx("kcLocaleListItemClass")} role="none">
+ <a role="menuitem" id={`language-${i + 1}`} className={kcClsx("kcLocaleItemClass")} href={href}>
+ {label}
</a>
</li>
))}
</ul>
</div>
</div>
</div>
)}
This only applies if you have ejected the mentioned components.
import { useEffect } from "react";
import { assert } from "keycloakify/tools/assert";
import { clsx } from "keycloakify/tools/clsx";
import type { TemplateProps } from "keycloakify/login/TemplateProps";
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
-import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
-import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
+import { useInitialize } from "keycloakify/login/Template.useInitialize";
import { useSetClassName } from "keycloakify/tools/useSetClassName";
import type { I18n } from "./i18n";
import type { KcContext } from "./KcContext";
export default function Template(props: TemplateProps<KcContext, I18n>) {
const {
displayInfo = false,
displayMessage = true,
displayRequiredFields = false,
headerNode,
socialProvidersNode = null,
infoNode = null,
documentTitle,
bodyClassName,
kcContext,
i18n,
doUseDefaultCss,
classes,
children
} = props;
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
const { msg, msgStr, getChangeLocaleUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;
useEffect(() => {
document.title = documentTitle ?? msgStr("loginTitle", kcContext.realm.displayName);
}, []);
useSetClassName({
qualifiedName: "html",
className: kcClsx("kcHtmlClass")
});
useSetClassName({
qualifiedName: "body",
className: bodyClassName ?? kcClsx("kcBodyClass")
});
- const { areAllStyleSheetsLoaded } = useInsertLinkTags({
- componentOrHookName: "Template",
- hrefs: !doUseDefaultCss
- ? []
- : [
- `${url.resourcesCommonPath}/node_modules/@patternfly/patternfly/patternfly.min.css`,
- `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css`,
- `${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly-additions.min.css`,
- `${url.resourcesCommonPath}/lib/pficon/pficon.css`,
- `${url.resourcesPath}/css/login.css`
- ]
- });
- const { insertScriptTags } = useInsertScriptTags({
- componentOrHookName: "Template",
- scriptTags: [
- {
- type: "module",
- src: `${url.resourcesPath}/js/menu-button-links.js`
- },
- ...(authenticationSession === undefined
- ? []
- : [
- {
- type: "module",
- textContent: [
- `import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";`,
- ``,
- `checkCookiesAndSetTimer(`,
- ` "${authenticationSession.authSessionId}",`,
- ` "${authenticationSession.tabId}",`,
- ` "${url.ssoLoginInOtherTabsUrl}"`,
- `);`
- ].join("\n")
- } as const
- ]),
- ...scripts.map(
- script =>
- ({
- type: "text/javascript",
- src: script
- }) as const
- )
- ]
- });
- useEffect(() => {
- if (areAllStyleSheetsLoaded) {
- insertScriptTags();
- }
- }, [areAllStyleSheetsLoaded]);
- if (!areAllStyleSheetsLoaded) {
- return null;
- }
+ const { isReadyToRender } = useInitialize({ kcContext, doUseDefaultCss });
+ if (!isReadyToRender) {
+ return null;
+ }
return (
<div className={kcClsx("kcLoginClass")}>
Similar changes have been made to the following pages:
Keycloakify now implements a kcSanitize function analogous to the one used by the default Keycloak theme.
You can search/replace in your codebase for any dangerouslySetInnerHTML
occurence and wrap the html string into the kcSanitize
method:
+import { kcSanitize } from "keycloakify/lib/kcSanitize";
// ...
<span
className="kc-feedback-text"
dangerouslySetInnerHTML={{
- __html: message.summary
+ __html: kcSanitize(message.summary)
}}
/>
This change is only required for Webpack users. Not Vite users.
{
"scripts": {
"prestart": "keycloakify update-kc-gen && keycloakify copy-keycloak-resources-to-public",
"start": "react-scripts start",
"prestorybook": "npm run prestart",
"storybook": "storybook dev -p 6006",
"prebuild": "keycloakify update-kc-gen",
"build": "react-scripts build",
- "postbuild": "rimraf build/keycloakify-resources",
+ "postbuild": "rimraf build/keycloakify-dev-resources",
"build-keycloak-theme": "npm run build && keycloakify build",
"format": "prettier . --write"
// ...
},