Files
3x-ui/frontend/src/components/PromptModal.tsx
T
MHSanaei c5b5606bf5 i18n(panel): translate Copy/Cancel buttons, Stream/Sniffing tabs, and All-Inbounds filenames
- TextModal: route the Copy button label and the post-copy toast
  through t('copy')/t('copied') instead of hardcoded English.
- PromptModal: route cancelText through t('cancel') and default okText
  through t('confirm') so the import-inbound prompt stops showing
  "Cancel" in non-English UI.
- InboundsPage: pass the All-Inbounds and All-Inbounds-Subs download
  filenames through t(...) so each locale can localize them.
- en-US.json: add pages.inbounds.exportAllLinksFileName and
  pages.inbounds.exportAllSubsFileName.
- All 12 non-English locales: translate streamTab and sniffingTab
  (previously left as literal English) and add the two new filename
  keys with appropriate translations.

All 13 locale files now have 1541 lines.
2026-05-28 18:45:59 +02:00

85 lines
2.2 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import { Input, Modal } from 'antd';
import type { InputRef } from 'antd';
import { useTranslation } from 'react-i18next';
interface PromptModalProps {
open: boolean;
onClose: () => void;
title: string;
okText?: string;
type?: 'input' | 'textarea';
initialValue?: string;
loading?: boolean;
onConfirm: (value: string) => void;
}
export default function PromptModal({
open,
onClose,
title,
okText,
type = 'input',
initialValue = '',
loading = false,
onConfirm,
}: PromptModalProps) {
const { t } = useTranslation();
const [value, setValue] = useState('');
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const inputRef = useRef<InputRef | null>(null);
useEffect(() => {
if (open) {
setValue(initialValue);
setTimeout(() => {
if (type === 'textarea') textareaRef.current?.focus();
else inputRef.current?.focus();
}, 50);
}
}, [open, initialValue, type]);
function onKeydown(e: React.KeyboardEvent<HTMLTextAreaElement | HTMLInputElement>) {
if (type !== 'textarea' && e.key === 'Enter') {
e.preventDefault();
onConfirm(value);
return;
}
if (type === 'textarea' && e.ctrlKey && e.key.toLowerCase() === 's') {
e.preventDefault();
onConfirm(value);
}
}
return (
<Modal
open={open}
title={title}
okText={okText ?? t('confirm')}
cancelText={t('cancel')}
mask={{ closable: false }}
confirmLoading={loading}
onOk={() => onConfirm(value)}
onCancel={onClose}
destroyOnHidden
>
{type === 'textarea' ? (
<Input.TextArea
ref={(el) => { textareaRef.current = (el as unknown as { resizableTextArea?: { textArea: HTMLTextAreaElement } })?.resizableTextArea?.textArea ?? null; }}
value={value}
onChange={(e) => setValue(e.target.value)}
autoSize={{ minRows: 10, maxRows: 20 }}
onKeyDown={onKeydown}
/>
) : (
<Input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={onKeydown}
/>
)}
</Modal>
);
}