fix: improve form accessibility with proper label associations
Added missing htmlFor attributes to labels and corresponding IDs to form elements throughout the web UI to enhance accessibility. This ensures screen readers can correctly identify form controls and improves browser autofill functionality. Changes include: - Fixed label associations in login form - Added proper IDs to form elements in Settings component - Replaced decorative labels with semantic headings in PropertiesView - Added screen reader accessible labels in RetrievalTesting - Improved checkbox accessibility in QuerySettings
This commit is contained in:
@@ -184,9 +184,11 @@ const PropertyRow = ({
|
|||||||
return translation === translationKey ? name : translation
|
return translation === translationKey ? name : translation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since Text component uses a label internally, we'll use a span here instead of a label
|
||||||
|
// to avoid nesting labels which is not recommended for accessibility
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="text-primary/60 tracking-wide whitespace-nowrap">{getPropertyNameTranslation(name)}</label>:
|
<span className="text-primary/60 tracking-wide whitespace-nowrap">{getPropertyNameTranslation(name)}</span>:
|
||||||
<Text
|
<Text
|
||||||
className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
|
className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
|
||||||
tooltipClassName="max-w-80"
|
tooltipClassName="max-w-80"
|
||||||
@@ -213,7 +215,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<label className="text-md pl-1 font-bold tracking-wide text-blue-700">{t('graphPanel.propertiesView.node.title')}</label>
|
<h3 className="text-md pl-1 font-bold tracking-wide text-blue-700">{t('graphPanel.propertiesView.node.title')}</h3>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -246,7 +248,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
|
|||||||
/>
|
/>
|
||||||
<PropertyRow name={t('graphPanel.propertiesView.node.degree')} value={node.degree} />
|
<PropertyRow name={t('graphPanel.propertiesView.node.degree')} value={node.degree} />
|
||||||
</div>
|
</div>
|
||||||
<label className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.node.properties')}</label>
|
<h3 className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.node.properties')}</h3>
|
||||||
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
||||||
{Object.keys(node.properties)
|
{Object.keys(node.properties)
|
||||||
.sort()
|
.sort()
|
||||||
@@ -256,9 +258,9 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
|
|||||||
</div>
|
</div>
|
||||||
{node.relationships.length > 0 && (
|
{node.relationships.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<label className="text-md pl-1 font-bold tracking-wide text-emerald-700">
|
<h3 className="text-md pl-1 font-bold tracking-wide text-emerald-700">
|
||||||
{t('graphPanel.propertiesView.node.relationships')}
|
{t('graphPanel.propertiesView.node.relationships')}
|
||||||
</label>
|
</h3>
|
||||||
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
||||||
{node.relationships.map(({ type, id, label }) => {
|
{node.relationships.map(({ type, id, label }) => {
|
||||||
return (
|
return (
|
||||||
@@ -283,7 +285,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label className="text-md pl-1 font-bold tracking-wide text-violet-700">{t('graphPanel.propertiesView.edge.title')}</label>
|
<h3 className="text-md pl-1 font-bold tracking-wide text-violet-700">{t('graphPanel.propertiesView.edge.title')}</h3>
|
||||||
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
||||||
<PropertyRow name={t('graphPanel.propertiesView.edge.id')} value={edge.id} />
|
<PropertyRow name={t('graphPanel.propertiesView.edge.id')} value={edge.id} />
|
||||||
{edge.type && <PropertyRow name={t('graphPanel.propertiesView.edge.type')} value={edge.type} />}
|
{edge.type && <PropertyRow name={t('graphPanel.propertiesView.edge.type')} value={edge.type} />}
|
||||||
@@ -302,7 +304,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<label className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.edge.properties')}</label>
|
<h3 className="text-md pl-1 font-bold tracking-wide text-amber-700">{t('graphPanel.propertiesView.edge.properties')}</h3>
|
||||||
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
<div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
|
||||||
{Object.keys(edge.properties)
|
{Object.keys(edge.properties)
|
||||||
.sort()
|
.sort()
|
||||||
|
@@ -23,11 +23,14 @@ const LabeledCheckBox = ({
|
|||||||
onCheckedChange: () => void
|
onCheckedChange: () => void
|
||||||
label: string
|
label: string
|
||||||
}) => {
|
}) => {
|
||||||
|
// Create unique ID using the label text converted to lowercase with spaces removed
|
||||||
|
const id = `checkbox-${label.toLowerCase().replace(/\s+/g, '-')}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Checkbox checked={checked} onCheckedChange={onCheckedChange} />
|
<Checkbox id={id} checked={checked} onCheckedChange={onCheckedChange} />
|
||||||
<label
|
<label
|
||||||
htmlFor="terms"
|
htmlFor={id}
|
||||||
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -56,6 +59,8 @@ const LabeledNumberInput = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [currentValue, setCurrentValue] = useState<number | null>(value)
|
const [currentValue, setCurrentValue] = useState<number | null>(value)
|
||||||
|
// Create unique ID using the label text converted to lowercase with spaces removed
|
||||||
|
const id = `input-${label.toLowerCase().replace(/\s+/g, '-')}`;
|
||||||
|
|
||||||
const onValueChange = useCallback(
|
const onValueChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -94,13 +99,14 @@ const LabeledNumberInput = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="terms"
|
htmlFor={id}
|
||||||
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Input
|
<Input
|
||||||
|
id={id}
|
||||||
type="number"
|
type="number"
|
||||||
value={currentValue === null ? '' : currentValue}
|
value={currentValue === null ? '' : currentValue}
|
||||||
onChange={onValueChange}
|
onChange={onValueChange}
|
||||||
@@ -295,11 +301,12 @@ export default function Settings() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
<label htmlFor="edge-size-min" className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
|
||||||
{t('graphPanel.sideBar.settings.edgeSizeRange')}
|
{t('graphPanel.sideBar.settings.edgeSizeRange')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
|
id="edge-size-min"
|
||||||
type="number"
|
type="number"
|
||||||
value={minEdgeSize}
|
value={minEdgeSize}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -315,6 +322,7 @@ export default function Settings() {
|
|||||||
<span>-</span>
|
<span>-</span>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Input
|
<Input
|
||||||
|
id="edge-size-max"
|
||||||
type="number"
|
type="number"
|
||||||
value={maxEdgeSize}
|
value={maxEdgeSize}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@@ -93,6 +93,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.topKTooltip')}
|
tooltip={t('retrievePanel.querySettings.topKTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="top_k" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.topK')}
|
||||||
|
</label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="top_k"
|
id="top_k"
|
||||||
stepper={1}
|
stepper={1}
|
||||||
@@ -101,6 +105,7 @@ export default function QuerySettings() {
|
|||||||
min={1}
|
min={1}
|
||||||
placeholder={t('retrievePanel.querySettings.topKPlaceholder')}
|
placeholder={t('retrievePanel.querySettings.topKPlaceholder')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
{/* Max Tokens */}
|
{/* Max Tokens */}
|
||||||
@@ -112,6 +117,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.maxTokensTextUnitTooltip')}
|
tooltip={t('retrievePanel.querySettings.maxTokensTextUnitTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="max_token_for_text_unit" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.maxTokensTextUnit')}
|
||||||
|
</label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="max_token_for_text_unit"
|
id="max_token_for_text_unit"
|
||||||
stepper={500}
|
stepper={500}
|
||||||
@@ -120,6 +129,7 @@ export default function QuerySettings() {
|
|||||||
min={1}
|
min={1}
|
||||||
placeholder={t('retrievePanel.querySettings.maxTokensTextUnit')}
|
placeholder={t('retrievePanel.querySettings.maxTokensTextUnit')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
@@ -128,6 +138,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.maxTokensGlobalContextTooltip')}
|
tooltip={t('retrievePanel.querySettings.maxTokensGlobalContextTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="max_token_for_global_context" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.maxTokensGlobalContext')}
|
||||||
|
</label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="max_token_for_global_context"
|
id="max_token_for_global_context"
|
||||||
stepper={500}
|
stepper={500}
|
||||||
@@ -136,6 +150,7 @@ export default function QuerySettings() {
|
|||||||
min={1}
|
min={1}
|
||||||
placeholder={t('retrievePanel.querySettings.maxTokensGlobalContext')}
|
placeholder={t('retrievePanel.querySettings.maxTokensGlobalContext')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
@@ -145,6 +160,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.maxTokensLocalContextTooltip')}
|
tooltip={t('retrievePanel.querySettings.maxTokensLocalContextTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="max_token_for_local_context" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.maxTokensLocalContext')}
|
||||||
|
</label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
id="max_token_for_local_context"
|
id="max_token_for_local_context"
|
||||||
stepper={500}
|
stepper={500}
|
||||||
@@ -153,6 +172,7 @@ export default function QuerySettings() {
|
|||||||
min={1}
|
min={1}
|
||||||
placeholder={t('retrievePanel.querySettings.maxTokensLocalContext')}
|
placeholder={t('retrievePanel.querySettings.maxTokensLocalContext')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
@@ -164,6 +184,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.historyTurnsTooltip')}
|
tooltip={t('retrievePanel.querySettings.historyTurnsTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="history_turns" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.historyTurns')}
|
||||||
|
</label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
className="!border-input"
|
className="!border-input"
|
||||||
id="history_turns"
|
id="history_turns"
|
||||||
@@ -174,6 +198,7 @@ export default function QuerySettings() {
|
|||||||
min={0}
|
min={0}
|
||||||
placeholder={t('retrievePanel.querySettings.historyTurnsPlaceholder')}
|
placeholder={t('retrievePanel.querySettings.historyTurnsPlaceholder')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
{/* Keywords */}
|
{/* Keywords */}
|
||||||
@@ -185,6 +210,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.hlKeywordsTooltip')}
|
tooltip={t('retrievePanel.querySettings.hlKeywordsTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="hl_keywords" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.hlKeywords')}
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
id="hl_keywords"
|
id="hl_keywords"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -198,6 +227,7 @@ export default function QuerySettings() {
|
|||||||
}}
|
}}
|
||||||
placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
|
placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
@@ -207,6 +237,10 @@ export default function QuerySettings() {
|
|||||||
tooltip={t('retrievePanel.querySettings.llKeywordsTooltip')}
|
tooltip={t('retrievePanel.querySettings.llKeywordsTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="ll_keywords" className="sr-only">
|
||||||
|
{t('retrievePanel.querySettings.llKeywords')}
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
id="ll_keywords"
|
id="ll_keywords"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -220,19 +254,21 @@ export default function QuerySettings() {
|
|||||||
}}
|
}}
|
||||||
placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
|
placeholder={t('retrievePanel.querySettings.hlkeywordsPlaceHolder')}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
{/* Toggle Options */}
|
{/* Toggle Options */}
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<label htmlFor="only_need_context" className="flex-1">
|
||||||
<Text
|
<Text
|
||||||
className="ml-1"
|
className="ml-1"
|
||||||
text={t('retrievePanel.querySettings.onlyNeedContext')}
|
text={t('retrievePanel.querySettings.onlyNeedContext')}
|
||||||
tooltip={t('retrievePanel.querySettings.onlyNeedContextTooltip')}
|
tooltip={t('retrievePanel.querySettings.onlyNeedContextTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
<div className="grow" />
|
</label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="mr-1 cursor-pointer"
|
className="mr-1 cursor-pointer"
|
||||||
id="only_need_context"
|
id="only_need_context"
|
||||||
@@ -242,13 +278,14 @@ export default function QuerySettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<label htmlFor="only_need_prompt" className="flex-1">
|
||||||
<Text
|
<Text
|
||||||
className="ml-1"
|
className="ml-1"
|
||||||
text={t('retrievePanel.querySettings.onlyNeedPrompt')}
|
text={t('retrievePanel.querySettings.onlyNeedPrompt')}
|
||||||
tooltip={t('retrievePanel.querySettings.onlyNeedPromptTooltip')}
|
tooltip={t('retrievePanel.querySettings.onlyNeedPromptTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
<div className="grow" />
|
</label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="mr-1 cursor-pointer"
|
className="mr-1 cursor-pointer"
|
||||||
id="only_need_prompt"
|
id="only_need_prompt"
|
||||||
@@ -258,13 +295,14 @@ export default function QuerySettings() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<label htmlFor="stream" className="flex-1">
|
||||||
<Text
|
<Text
|
||||||
className="ml-1"
|
className="ml-1"
|
||||||
text={t('retrievePanel.querySettings.streamResponse')}
|
text={t('retrievePanel.querySettings.streamResponse')}
|
||||||
tooltip={t('retrievePanel.querySettings.streamResponseTooltip')}
|
tooltip={t('retrievePanel.querySettings.streamResponseTooltip')}
|
||||||
side="left"
|
side="left"
|
||||||
/>
|
/>
|
||||||
<div className="grow" />
|
</label>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="mr-1 cursor-pointer"
|
className="mr-1 cursor-pointer"
|
||||||
id="stream"
|
id="stream"
|
||||||
|
@@ -507,8 +507,14 @@ export default function DocumentManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm text-gray-500">{t('documentPanel.documentManager.fileNameLabel')}</span>
|
<label
|
||||||
|
htmlFor="toggle-filename-btn"
|
||||||
|
className="text-sm text-gray-500"
|
||||||
|
>
|
||||||
|
{t('documentPanel.documentManager.fileNameLabel')}
|
||||||
|
</label>
|
||||||
<Button
|
<Button
|
||||||
|
id="toggle-filename-btn"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setShowFileName(!showFileName)}
|
onClick={() => setShowFileName(!showFileName)}
|
||||||
|
@@ -147,13 +147,19 @@ export default function RetrievalTesting() {
|
|||||||
<EraserIcon />
|
<EraserIcon />
|
||||||
{t('retrievePanel.retrieval.clear')}
|
{t('retrievePanel.retrieval.clear')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<div className="flex-1 relative">
|
||||||
|
<label htmlFor="query-input" className="sr-only">
|
||||||
|
{t('retrievePanel.retrieval.placeholder')}
|
||||||
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="flex-1"
|
id="query-input"
|
||||||
|
className="w-full"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
placeholder={t('retrievePanel.retrieval.placeholder')}
|
placeholder={t('retrievePanel.retrieval.placeholder')}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<Button type="submit" variant="default" disabled={isLoading} size="sm">
|
<Button type="submit" variant="default" disabled={isLoading} size="sm">
|
||||||
<SendIcon />
|
<SendIcon />
|
||||||
{t('retrievePanel.retrieval.send')}
|
{t('retrievePanel.retrieval.send')}
|
||||||
|
Reference in New Issue
Block a user