Fixed persisting translations for attribute groups and improved errors for empty translations on attribute/attribute groups save (#33943)
* added fix for attribute groups Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * Improved translations for attributes Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * improvement Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * improvement Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * improvement Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * improvement Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * improved fetching translations in NewAttributeSettings Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * improved fetching translations in NewAttributeSettings Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * cleanup Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> * cleanup Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com> --------- Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>
This commit is contained in:
parent
abb7c414ab
commit
c64e0ad583
4 changed files with 133 additions and 152 deletions
|
@ -3281,3 +3281,4 @@ termsAndConditionsDeclined=You need to accept the Terms and Conditions to contin
|
||||||
somethingWentWrong=Something went wrong
|
somethingWentWrong=Something went wrong
|
||||||
somethingWentWrongDescription=Sorry, an unexpected error has occurred.
|
somethingWentWrongDescription=Sorry, an unexpected error has occurred.
|
||||||
tryAgain=Try again
|
tryAgain=Try again
|
||||||
|
errorSavingTranslations=Error saving translations\: '{{error}}'
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import type {
|
import type {
|
||||||
UserProfileAttribute,
|
UserProfileAttribute,
|
||||||
UserProfileConfig,
|
UserProfileConfig,
|
||||||
|
@ -61,6 +62,7 @@ type UserProfileAttributeFormFields = Omit<
|
||||||
annotations: IndexedAnnotations[];
|
annotations: IndexedAnnotations[];
|
||||||
hasSelector: boolean;
|
hasSelector: boolean;
|
||||||
hasRequiredScopes: boolean;
|
hasRequiredScopes: boolean;
|
||||||
|
translations?: TranslationForm[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Attribute = {
|
type Attribute = {
|
||||||
|
@ -172,7 +174,8 @@ export default function NewAttributeSettings() {
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
const translationsToSave: any[] = [];
|
const translationsToSave: Translations[] = [];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
combinedLocales.map(async (selectedLocale) => {
|
combinedLocales.map(async (selectedLocale) => {
|
||||||
try {
|
try {
|
||||||
|
@ -183,55 +186,50 @@ export default function NewAttributeSettings() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const formData = form.getValues();
|
const formData = form.getValues();
|
||||||
const formattedKey = formData.displayName?.substring(
|
const formattedKey =
|
||||||
2,
|
formData.displayName?.substring(
|
||||||
formData.displayName.length - 1,
|
2,
|
||||||
);
|
formData.displayName.length - 1,
|
||||||
const filteredTranslations: Array<{
|
) || "";
|
||||||
locale: string;
|
|
||||||
value: string;
|
const filteredTranslations: TranslationForm[] = Object.entries(
|
||||||
}> = [];
|
translations,
|
||||||
const allTranslations = Object.entries(translations).map(
|
)
|
||||||
([key, value]) => ({
|
.filter(([key]) => key === formattedKey)
|
||||||
key,
|
.map(([_, value]) => ({
|
||||||
|
locale: selectedLocale,
|
||||||
value,
|
value,
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
allTranslations.forEach((translation) => {
|
if (filteredTranslations.length > 0) {
|
||||||
if (translation.key === formattedKey) {
|
translationsToSave.push({
|
||||||
filteredTranslations.push({
|
key: formattedKey,
|
||||||
locale: selectedLocale,
|
translations: filteredTranslations,
|
||||||
value: translation.value,
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const translationToSave: any = {
|
|
||||||
key: formattedKey,
|
|
||||||
translations: filteredTranslations,
|
|
||||||
};
|
|
||||||
|
|
||||||
translationsToSave.push(translationToSave);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
addError("errorSavingTranslations", error);
|
||||||
`Error fetching translations for ${selectedLocale}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return translationsToSave;
|
return translationsToSave;
|
||||||
},
|
},
|
||||||
(translationsToSaveData) => {
|
(translationsToSave) => {
|
||||||
setTranslationsData(() => ({
|
if (translationsToSave && translationsToSave.length > 0) {
|
||||||
key: translationsToSaveData[0].key,
|
const allTranslations = translationsToSave.flatMap(
|
||||||
translations: translationsToSaveData.flatMap(
|
(translation) => translation.translations,
|
||||||
(translationData) => translationData.translations,
|
);
|
||||||
),
|
|
||||||
}));
|
setTranslationsData({
|
||||||
|
key: translationsToSave[0].key,
|
||||||
|
translations: allTranslations,
|
||||||
|
});
|
||||||
|
|
||||||
|
form.setValue("translations", allTranslations);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[combinedLocales],
|
[combinedLocales, realmName, form],
|
||||||
);
|
);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
|
@ -282,8 +280,9 @@ export default function NewAttributeSettings() {
|
||||||
|
|
||||||
const saveTranslations = async () => {
|
const saveTranslations = async () => {
|
||||||
try {
|
try {
|
||||||
const nonEmptyTranslations = translationsData.translations.map(
|
const nonEmptyTranslations = translationsData.translations
|
||||||
async (translation) => {
|
.filter((translation) => translation.value.trim() !== "")
|
||||||
|
.map(async (translation) => {
|
||||||
try {
|
try {
|
||||||
await adminClient.realms.addLocalization(
|
await adminClient.realms.addLocalization(
|
||||||
{
|
{
|
||||||
|
@ -293,11 +292,11 @@ export default function NewAttributeSettings() {
|
||||||
},
|
},
|
||||||
translation.value,
|
translation.value,
|
||||||
);
|
);
|
||||||
} catch {
|
} catch (error) {
|
||||||
console.error(`Error saving translation for ${translation.locale}`);
|
addError(t("errorSavingTranslations"), error);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
await Promise.all(nonEmptyTranslations);
|
await Promise.all(nonEmptyTranslations);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error saving translations: ${error}`);
|
console.error(`Error saving translations: ${error}`);
|
||||||
|
@ -377,7 +376,7 @@ export default function NewAttributeSettings() {
|
||||||
(translation) => translation.value.trim() !== "",
|
(translation) => translation.value.trim() !== "",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasNonEmptyTranslations && !formFields.displayName) {
|
if (!hasNonEmptyTranslations) {
|
||||||
addError("createAttributeError", t("translationError"));
|
addError("createAttributeError", t("translationError"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||||
import {
|
import {
|
||||||
HelpItem,
|
HelpItem,
|
||||||
|
@ -17,7 +18,6 @@ import {
|
||||||
TextContent,
|
TextContent,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { GlobeRouteIcon } from "@patternfly/react-icons";
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
FormProvider,
|
FormProvider,
|
||||||
|
@ -27,7 +27,6 @@ import {
|
||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
import { useAdminClient } from "../../admin-client";
|
|
||||||
import { FormAccess } from "../../components/form/FormAccess";
|
import { FormAccess } from "../../components/form/FormAccess";
|
||||||
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
||||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||||
|
@ -44,6 +43,8 @@ import {
|
||||||
AddTranslationsDialog,
|
AddTranslationsDialog,
|
||||||
TranslationsType,
|
TranslationsType,
|
||||||
} from "./attribute/AddTranslationsDialog";
|
} from "./attribute/AddTranslationsDialog";
|
||||||
|
import { GlobeRouteIcon } from "@patternfly/react-icons";
|
||||||
|
import { useAdminClient } from "../../admin-client";
|
||||||
|
|
||||||
function parseAnnotations(input: Record<string, unknown>): KeyValueType[] {
|
function parseAnnotations(input: Record<string, unknown>): KeyValueType[] {
|
||||||
return Object.entries(input).reduce((p, [key, value]) => {
|
return Object.entries(input).reduce((p, [key, value]) => {
|
||||||
|
@ -77,11 +78,6 @@ type Translations = {
|
||||||
translations: TranslationForm[];
|
translations: TranslationForm[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type TranslationsSets = {
|
|
||||||
displayHeader: Translations;
|
|
||||||
displayDescription: Translations;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultValues: FormFields = {
|
const defaultValues: FormFields = {
|
||||||
annotations: [],
|
annotations: [],
|
||||||
displayDescription: "",
|
displayDescription: "",
|
||||||
|
@ -112,20 +108,21 @@ export default function AttributesGroupForm() {
|
||||||
const [addTranslationsModalOpen, toggleModal] = useToggle();
|
const [addTranslationsModalOpen, toggleModal] = useToggle();
|
||||||
const regexPattern = /\$\{([^}]+)\}/;
|
const regexPattern = /\$\{([^}]+)\}/;
|
||||||
const [type, setType] = useState<TranslationsType>();
|
const [type, setType] = useState<TranslationsType>();
|
||||||
const [translationsData, setTranslationsData] = useState<TranslationsSets>({
|
|
||||||
|
const [translationsData, setTranslationsData] = useState({
|
||||||
displayHeader: {
|
displayHeader: {
|
||||||
key: "",
|
key: "",
|
||||||
translations: [],
|
translations: [] as TranslationForm[],
|
||||||
},
|
},
|
||||||
displayDescription: {
|
displayDescription: {
|
||||||
key: "",
|
key: "",
|
||||||
translations: [],
|
translations: [] as TranslationForm[],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const matchingGroup = useMemo(
|
const matchingGroup = useMemo(
|
||||||
() => config?.groups?.find(({ name }) => name === params.name),
|
() => config?.groups?.find(({ name }) => name === params.name),
|
||||||
[config?.groups],
|
[config?.groups, params.name],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -138,120 +135,98 @@ export default function AttributesGroupForm() {
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
form.reset({ ...defaultValues, ...matchingGroup, annotations });
|
form.reset({ ...defaultValues, ...matchingGroup, annotations });
|
||||||
}, [matchingGroup]);
|
}, [matchingGroup, form]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.setValue(
|
form.setValue(
|
||||||
"displayHeader",
|
"displayHeader",
|
||||||
matchingGroup
|
matchingGroup?.displayHeader || generatedAttributesGroupDisplayName || "",
|
||||||
? matchingGroup.displayHeader!
|
|
||||||
: generatedAttributesGroupDisplayName,
|
|
||||||
);
|
);
|
||||||
form.setValue(
|
form.setValue(
|
||||||
"displayDescription",
|
"displayDescription",
|
||||||
matchingGroup
|
matchingGroup?.displayDescription ||
|
||||||
? matchingGroup.displayDescription!
|
generatedAttributesGroupDisplayDescription ||
|
||||||
: generatedAttributesGroupDisplayDescription,
|
"",
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
generatedAttributesGroupDisplayName,
|
generatedAttributesGroupDisplayName,
|
||||||
generatedAttributesGroupDisplayDescription,
|
generatedAttributesGroupDisplayDescription,
|
||||||
|
matchingGroup,
|
||||||
|
form,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () => {
|
async () => {
|
||||||
const translationsToSaveDisplayHeader: Translations[] = [];
|
const translationsToSaveDisplayHeader: Translations[] = [];
|
||||||
const translationsToSaveDisplayDescription: Translations[] = [];
|
const translationsToSaveDisplayDescription: Translations[] = [];
|
||||||
const formData = form.getValues();
|
|
||||||
|
|
||||||
const translationsResults = await Promise.all(
|
await Promise.all(
|
||||||
combinedLocales.map(async (selectedLocale) => {
|
combinedLocales.map(async (locale: string) => {
|
||||||
try {
|
try {
|
||||||
const translations =
|
const translations =
|
||||||
await adminClient.realms.getRealmLocalizationTexts({
|
await adminClient.realms.getRealmLocalizationTexts({
|
||||||
realm: realmName,
|
realm: realmName,
|
||||||
selectedLocale,
|
selectedLocale: locale,
|
||||||
});
|
});
|
||||||
|
|
||||||
const formattedDisplayHeaderKey = formData.displayHeader?.substring(
|
const formData = form.getValues();
|
||||||
2,
|
const extractKey = (value: string | undefined) => {
|
||||||
formData.displayHeader.length - 1,
|
const match = value?.match(/\$\{(.*?)\}/);
|
||||||
);
|
return match ? match[1] : "";
|
||||||
const formattedDisplayDescriptionKey =
|
|
||||||
formData.displayDescription?.substring(
|
|
||||||
2,
|
|
||||||
formData.displayDescription.length - 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
locale: selectedLocale,
|
|
||||||
headerTranslation: translations[formattedDisplayHeaderKey] ?? "",
|
|
||||||
descriptionTranslation:
|
|
||||||
translations[formattedDisplayDescriptionKey] ?? "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const displayHeaderKey = extractKey(formData.displayHeader) || "";
|
||||||
|
const displayDescriptionKey =
|
||||||
|
extractKey(formData.displayDescription) || "";
|
||||||
|
|
||||||
|
const headerTranslation = translations[displayHeaderKey] || "";
|
||||||
|
const descriptionTranslation =
|
||||||
|
translations[displayDescriptionKey] || "";
|
||||||
|
|
||||||
|
if (headerTranslation) {
|
||||||
|
translationsToSaveDisplayHeader.push({
|
||||||
|
key: displayHeaderKey,
|
||||||
|
translations: [{ locale, value: headerTranslation }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descriptionTranslation) {
|
||||||
|
translationsToSaveDisplayDescription.push({
|
||||||
|
key: displayDescriptionKey,
|
||||||
|
translations: [{ locale, value: descriptionTranslation }],
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(`Error fetching translations for ${locale}:`, error);
|
||||||
`Error fetching translations for ${selectedLocale}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
translationsResults.forEach((translationsResult) => {
|
const translationsDataNew = {
|
||||||
if (translationsResult) {
|
|
||||||
const { locale, headerTranslation, descriptionTranslation } =
|
|
||||||
translationsResult;
|
|
||||||
translationsToSaveDisplayHeader.push({
|
|
||||||
key: formData.displayHeader?.substring(
|
|
||||||
2,
|
|
||||||
formData.displayHeader.length - 1,
|
|
||||||
),
|
|
||||||
translations: [
|
|
||||||
{
|
|
||||||
locale,
|
|
||||||
value: headerTranslation,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
translationsToSaveDisplayDescription.push({
|
|
||||||
key: formData.displayDescription?.substring(
|
|
||||||
2,
|
|
||||||
formData.displayDescription.length - 1,
|
|
||||||
),
|
|
||||||
translations: [
|
|
||||||
{
|
|
||||||
locale,
|
|
||||||
value: descriptionTranslation,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
translationsToSaveDisplayHeader,
|
|
||||||
translationsToSaveDisplayDescription,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
(data) => {
|
|
||||||
setTranslationsData({
|
|
||||||
displayHeader: {
|
displayHeader: {
|
||||||
key: data.translationsToSaveDisplayHeader[0].key,
|
key:
|
||||||
translations: data.translationsToSaveDisplayHeader.flatMap(
|
translationsToSaveDisplayHeader.length > 0
|
||||||
(translationData) => translationData.translations,
|
? translationsToSaveDisplayHeader[0].key
|
||||||
|
: "",
|
||||||
|
translations: translationsToSaveDisplayHeader.flatMap(
|
||||||
|
(data) => data.translations,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
displayDescription: {
|
displayDescription: {
|
||||||
key: data.translationsToSaveDisplayDescription[0].key,
|
key:
|
||||||
translations: data.translationsToSaveDisplayDescription.flatMap(
|
translationsToSaveDisplayDescription.length > 0
|
||||||
(translationData) => translationData.translations,
|
? translationsToSaveDisplayDescription[0].key
|
||||||
|
: "",
|
||||||
|
translations: translationsToSaveDisplayDescription.flatMap(
|
||||||
|
(data) => data.translations,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
|
setTranslationsData(translationsDataNew);
|
||||||
},
|
},
|
||||||
[combinedLocales],
|
() => {},
|
||||||
|
[combinedLocales, realmName, form],
|
||||||
);
|
);
|
||||||
|
|
||||||
const saveTranslations = async () => {
|
const saveTranslations = async () => {
|
||||||
|
@ -278,29 +253,33 @@ export default function AttributesGroupForm() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
translationsData.displayHeader &&
|
translationsData &&
|
||||||
translationsData.displayHeader.translations.length > 0
|
translationsData.displayHeader.translations.length > 0
|
||||||
) {
|
) {
|
||||||
for (const translation of translationsData.displayHeader.translations) {
|
for (const translation of translationsData.displayHeader.translations) {
|
||||||
await addLocalization(
|
if (translation.locale && translation.value) {
|
||||||
translationsData.displayHeader.key,
|
await addLocalization(
|
||||||
translation.locale,
|
translationsData.displayHeader.key,
|
||||||
translation.value,
|
translation.locale,
|
||||||
);
|
translation.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
translationsData.displayDescription &&
|
translationsData &&
|
||||||
translationsData.displayDescription.translations.length > 0
|
translationsData.displayDescription.translations.length > 0
|
||||||
) {
|
) {
|
||||||
for (const translation of translationsData.displayDescription
|
for (const translation of translationsData.displayDescription
|
||||||
.translations) {
|
.translations) {
|
||||||
await addLocalization(
|
if (translation.locale && translation.value) {
|
||||||
translationsData.displayDescription.key,
|
await addLocalization(
|
||||||
translation.locale,
|
translationsData.displayDescription.key,
|
||||||
translation.value,
|
translation.locale,
|
||||||
);
|
translation.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -331,7 +310,6 @@ export default function AttributesGroupForm() {
|
||||||
translationsData.displayHeader.translations.some(
|
translationsData.displayHeader.translations.some(
|
||||||
(translation) => translation.value.trim() !== "",
|
(translation) => translation.value.trim() !== "",
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasNonEmptyDisplayDescriptionTranslations =
|
const hasNonEmptyDisplayDescriptionTranslations =
|
||||||
translationsData.displayDescription.translations.some(
|
translationsData.displayDescription.translations.some(
|
||||||
(translation) => translation.value.trim() !== "",
|
(translation) => translation.value.trim() !== "",
|
||||||
|
|
|
@ -142,10 +142,13 @@ export const AddTranslationsDialog = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
combinedLocales.forEach((locale, rowIndex) => {
|
combinedLocales.forEach((locale, rowIndex) => {
|
||||||
setValue(`translations.${rowIndex}.locale`, locale);
|
setValue(`translations.${rowIndex}.locale`, locale);
|
||||||
|
|
||||||
|
const translationExists =
|
||||||
|
translations.translations[rowIndex] !== undefined;
|
||||||
setValue(
|
setValue(
|
||||||
`translations.${rowIndex}.value`,
|
`translations.${rowIndex}.value`,
|
||||||
translations.translations.length > 0
|
translationExists
|
||||||
? translations.translations[rowIndex].value
|
? translations.translations[rowIndex]?.value
|
||||||
: defaultTranslations[locale] || "",
|
: defaultTranslations[locale] || "",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue