mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-02 12:07:34 +00:00
feat: add view to see custom user profile fields
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
.mx_ExtendedProfileView {
|
||||
/* Anpassung an das bestehende UserInfo Panel */
|
||||
font-size: var(--cpd-font-size-body-sm);
|
||||
padding: 0;
|
||||
|
||||
/* Styling für das Accordion */
|
||||
.mx_ExtendedProfileView_accordion {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Abstand zwischen den Feldern */
|
||||
.mx_ExtendedProfileView_content {
|
||||
padding: var(--cpd-space-2x) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-4x);
|
||||
}
|
||||
|
||||
/* Styling für die Felder */
|
||||
.mx_ExtendedProfileView_field {
|
||||
margin-bottom: var(--cpd-space-2x);
|
||||
|
||||
.mx_ExtendedProfileView_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--cpd-color-icon-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Labels und Werte */
|
||||
.mx_ExtendedProfileView_label {
|
||||
color: var(--cpd-color-text-secondary);
|
||||
font-size: var(--cpd-font-size-body-xs);
|
||||
margin-bottom: var(--cpd-space-1x);
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileView_value {
|
||||
color: var(--cpd-color-text-primary);
|
||||
word-break: break-word;
|
||||
|
||||
&.mx_ExtendedProfileView_multiline {
|
||||
white-space: pre-line;
|
||||
margin-top: var(--cpd-space-1x);
|
||||
}
|
||||
}
|
||||
|
||||
/* Spezielle Styles für Website-Links */
|
||||
.mx_ExtendedProfileView_link {
|
||||
color: var(--cpd-color-text-action-accent);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Für Bereich "Weitere Informationen" */
|
||||
.mx_ExtendedProfileView_further_info {
|
||||
padding: var(--cpd-space-1x) 0;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.mx_ExtendedProfileView_loading {
|
||||
padding: var(--cpd-space-4x) 0;
|
||||
}
|
||||
|
||||
/* Error State */
|
||||
.mx_ExtendedProfileView_error {
|
||||
margin: var(--cpd-space-2x) 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
*/
|
||||
|
||||
.mx_UserExtendedProfileInfo {
|
||||
padding: 16px;
|
||||
background-color: $system;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_heading {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_row .mx_UserExtendedProfileInfo_field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_field {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_label {
|
||||
margin-bottom: 3px;
|
||||
color: $tertiary-content;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_value {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Dark theme specific styles */
|
||||
.mx_UserExtendedProfileInfo_value a {
|
||||
color: $accent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mx_UserExtendedProfileInfo_value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Alert } from "@vector-im/compound-web";
|
||||
import GlobeIcon from "@vector-im/compound-design-tokens/assets/web/icons/globe";
|
||||
import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location";
|
||||
import BadgeIcon from "@vector-im/compound-design-tokens/assets/web/icons/badge";
|
||||
import StarIcon from "@vector-im/compound-design-tokens/assets/web/icons/star";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import { Container } from "../right_panel/UserInfo";
|
||||
import InlineSpinner from "../elements/InlineSpinner";
|
||||
import {
|
||||
EXT_PROFILE_KEY_ADDRESS,
|
||||
EXT_PROFILE_KEY_FURTHER_INFO,
|
||||
EXT_PROFILE_KEY_SPECIALIZATION,
|
||||
EXT_PROFILE_KEY_TITLE,
|
||||
EXT_PROFILE_KEY_WEBSITE,
|
||||
} from "../settings/ExtendedProfileSettings";
|
||||
import { Flex } from "../../utils/Flex";
|
||||
|
||||
interface ExtendedProfileViewProps {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
type ExtendedProfileData = {
|
||||
title: string | null;
|
||||
specialization: string | null;
|
||||
address: string | null;
|
||||
website: string | null;
|
||||
furtherInfo: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Komponente zur Anzeige erweiterter Profilfelder eines anderen Benutzers
|
||||
*/
|
||||
const ExtendedProfileView: React.FC<ExtendedProfileViewProps> = ({ userId }) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
const [profileData, setProfileData] = useState<ExtendedProfileData | null>(null);
|
||||
const [hasData, setHasData] = useState<boolean>(false);
|
||||
|
||||
//const client = useMatrixClientContext();
|
||||
const userProfilesStore = SdkContextClass.instance.userProfilesStore;
|
||||
|
||||
// Laden der erweiterten Profilfelder für den angegebenen Benutzer
|
||||
const fetchExtendedProfileData = useCallback(async () => {
|
||||
if (!userId) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
try {
|
||||
const [
|
||||
titleValue,
|
||||
specializationValue,
|
||||
addressValue,
|
||||
websiteValue,
|
||||
furtherInfoValue,
|
||||
] = await Promise.all([
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_ADDRESS),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_WEBSITE),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO),
|
||||
]);
|
||||
|
||||
const data = {
|
||||
title: titleValue as string | null,
|
||||
specialization: specializationValue as string | null,
|
||||
address: addressValue as string | null,
|
||||
website: websiteValue as string | null,
|
||||
furtherInfo: furtherInfoValue as string | null,
|
||||
};
|
||||
|
||||
setProfileData(data);
|
||||
|
||||
// Prüfen, ob mindestens ein Feld ausgefüllt ist
|
||||
const hasAnyData = Object.values(data).some((value) => value !== null && value !== "");
|
||||
setHasData(hasAnyData);
|
||||
} catch (e) {
|
||||
logger.error("Fehler beim Laden der erweiterten Profildaten", e);
|
||||
setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [userId, userProfilesStore]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchExtendedProfileData();
|
||||
}, [fetchExtendedProfileData]);
|
||||
|
||||
// Wenn keine Daten vorhanden sind, zeigen wir nichts an
|
||||
if (!loading && !hasData && !error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="mx_ExtendedProfileView">
|
||||
{loading ? (
|
||||
<Flex justify="center" align="center" className="mx_ExtendedProfileView_loading">
|
||||
<InlineSpinner />
|
||||
</Flex>
|
||||
) : error ? (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={_t("common|error")}
|
||||
className="mx_ExtendedProfileView_error"
|
||||
>
|
||||
{_t("Fehler beim Laden der Profilinformationen")}
|
||||
</Alert>
|
||||
) : hasData ? (
|
||||
<div className="mx_ExtendedProfileView_content">
|
||||
{(profileData?.title || profileData?.specialization) && (
|
||||
<div className="mx_ExtendedProfileView_section mx_ExtendedProfileView_professional">
|
||||
{profileData.title && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<BadgeIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|professional_title")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">{profileData.title}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData.specialization && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<StarIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|specialization")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">{profileData.specialization}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData?.address && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<LocationIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|address")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">{profileData.address}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData?.website && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<GlobeIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|website")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">
|
||||
<a
|
||||
href={profileData.website.startsWith("http") ? profileData.website : `https://${profileData.website}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="mx_ExtendedProfileView_link"
|
||||
>
|
||||
{profileData.website}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData?.furtherInfo && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<div className="mx_ExtendedProfileView_further_info">
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|further_information")}</div>
|
||||
<div className="mx_ExtendedProfileView_value mx_ExtendedProfileView_multiline">{profileData.furtherInfo}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtendedProfileView;
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Text } from "@vector-im/compound-web";
|
||||
|
||||
import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { _t } from "../../../languageHandler";
|
||||
// Extended profile property keys
|
||||
import {
|
||||
EXT_PROFILE_KEY_TITLE,
|
||||
EXT_PROFILE_KEY_SPECIALIZATION,
|
||||
EXT_PROFILE_KEY_ADDRESS,
|
||||
EXT_PROFILE_KEY_WEBSITE,
|
||||
EXT_PROFILE_KEY_FURTHER_INFO,
|
||||
} from "../settings/ExtendedProfileSettings";
|
||||
|
||||
// Helper function to preserve line breaks
|
||||
const renderMultilineText = (text: string): React.ReactNode[] => {
|
||||
const lines = text.split("\n");
|
||||
return lines.map((line, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{line}
|
||||
{i < lines.length - 1 && <br />}
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
|
||||
interface UserExtendedProfileInfoProps {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
const UserExtendedProfileInfo: React.FC<UserExtendedProfileInfoProps> = ({ userId }) => {
|
||||
//const client = useMatrixClientContext();
|
||||
const userProfilesStore = SdkContextClass.instance.userProfilesStore;
|
||||
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [specialization, setSpecialization] = useState<string>("");
|
||||
const [address, setAddress] = useState<string>("");
|
||||
const [website, setWebsite] = useState<string>("");
|
||||
const [furtherInfo, setFurtherInfo] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProfileData = async () => {
|
||||
try {
|
||||
setError(false);
|
||||
setLoading(true);
|
||||
|
||||
const titleValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE);
|
||||
const specializationValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION);
|
||||
const addressValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_ADDRESS);
|
||||
const websiteValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_WEBSITE);
|
||||
const furtherInfoValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO);
|
||||
|
||||
setTitle(titleValue as string || "");
|
||||
setSpecialization(specializationValue as string || "");
|
||||
setAddress(addressValue as string || "");
|
||||
setWebsite(websiteValue as string || "");
|
||||
setFurtherInfo(furtherInfoValue as string || "");
|
||||
} catch {
|
||||
setError(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProfileData();
|
||||
}, [userId, userProfilesStore]);
|
||||
|
||||
if (loading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="mx_UserExtendedProfileInfo_error">{_t("user_info|extended_profile|error_loading_profile")}</div>;
|
||||
}
|
||||
|
||||
// Don't render anything if there's no data
|
||||
if (!title && !specialization && !address && !website && !furtherInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_UserInfo_container mx_UserInfo_profileInfo">
|
||||
{/* <div className="mx_UserInfo_container_header">
|
||||
<Text weight="semibold">{_t("user_info|extended_profile|header")}</Text>
|
||||
</div> */}
|
||||
{title && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|title")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">{title}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{specialization && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|specialization")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">{specialization}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{address && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|address")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">{renderMultilineText(address)}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{website && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|website")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">
|
||||
<a href={website.startsWith('http') ? website : `https://${website}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
{website}
|
||||
</a>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{furtherInfo && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|further_info")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue mx_UserInfo_fieldValue_multiline">
|
||||
<Text size="sm">{renderMultilineText(furtherInfo)}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserExtendedProfileInfo;
|
||||
@@ -28,6 +28,7 @@ import { type IRightPanelCardState } from "../../../stores/right-panel/RightPane
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { UserInfoHeaderView } from "./user_info/UserInfoHeaderView";
|
||||
import { UserInfoBasicView } from "./user_info/UserInfoBasicView";
|
||||
import UserExtendedProfileInfo from "./UserExtendedProfileInfo";
|
||||
|
||||
export interface IDevice extends Device {
|
||||
ambiguous?: boolean;
|
||||
@@ -252,6 +253,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
|
||||
devices={devices}
|
||||
roomId={room?.roomId}
|
||||
/>
|
||||
{phase === RightPanelPhases.MemberInfo && <UserExtendedProfileInfo userId={member.userId} />}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -3751,6 +3751,16 @@
|
||||
}
|
||||
},
|
||||
"user_info": {
|
||||
"extended_profile": {
|
||||
"header": "Weitere Informationen",
|
||||
"title": "Titel",
|
||||
"specialization": "Fachgebiet",
|
||||
"address": "Adresse",
|
||||
"website": "Webseite",
|
||||
"further_info": "Weitere Infos / Qualifikationen",
|
||||
"not_provided": "Nicht angegeben",
|
||||
"error_loading_profile": "Fehler beim Laden des Profils"
|
||||
},
|
||||
"admin_tools_section": "Administrationswerkzeuge",
|
||||
"ban_button_room": "Bannen",
|
||||
"ban_button_space": "Bannen",
|
||||
|
||||
@@ -3807,6 +3807,16 @@
|
||||
}
|
||||
},
|
||||
"user_info": {
|
||||
"extended_profile": {
|
||||
"header": "Additional infos",
|
||||
"title": "Title",
|
||||
"specialization": "Speciality",
|
||||
"address": "Address",
|
||||
"website": "Website",
|
||||
"further_info": "Further infos / qualifications",
|
||||
"not_provided": "Not provided",
|
||||
"error_loading_profile": "Error loading profile"
|
||||
},
|
||||
"admin_tools_section": "Admin Tools",
|
||||
"ban_button_room": "Ban from room",
|
||||
"ban_button_space": "Ban from space",
|
||||
|
||||
@@ -3811,6 +3811,16 @@
|
||||
}
|
||||
},
|
||||
"user_info": {
|
||||
"extended_profile": {
|
||||
"header": "Informations supplémentaires",
|
||||
"title": "Titre",
|
||||
"specialization": "Spécialisation",
|
||||
"address": "Adresse",
|
||||
"website": "Site web",
|
||||
"further_info": "Informations supplémentaires / qualifications",
|
||||
"not_provided": "Non fourni",
|
||||
"error_loading_profile": "Erreur lors du chargement du profil"
|
||||
},
|
||||
"admin_tools_section": "Outils d’administration",
|
||||
"ban_button_room": "Bannir du salon",
|
||||
"ban_button_space": "Bannir de l'espace",
|
||||
|
||||
@@ -2194,6 +2194,16 @@
|
||||
}
|
||||
},
|
||||
"user_info": {
|
||||
"extended_profile": {
|
||||
"header": "Informazioni aggiuntive",
|
||||
"title": "Titolo",
|
||||
"specialization": "Specializzazione",
|
||||
"address": "Indirizzo",
|
||||
"website": "Sito web",
|
||||
"further_info": "Ulteriori informazioni / qualifiche",
|
||||
"not_provided": "Non fornito",
|
||||
"error_loading_profile": "Errore nel caricamento del profilo"
|
||||
},
|
||||
"admin_tools_section": "Administratoriaus įrankiai",
|
||||
"ban_button_room": "Užblokuoti iš kambario",
|
||||
"ban_button_space": "Užblokuoti iš erdvės",
|
||||
|
||||
Reference in New Issue
Block a user