diff --git a/client-web/src/core/infrastructure/i18n.ts b/client-web/src/core/infrastructure/i18n.ts index c7f3b91..9f2d409 100644 --- a/client-web/src/core/infrastructure/i18n.ts +++ b/client-web/src/core/infrastructure/i18n.ts @@ -37,6 +37,13 @@ const translations = { modernMessenger: 'Современный мессенджер с фокусом', onPrivacy: 'на приватность и удобство', modernMessengerShort: 'Современный мессенджер', + settingsDesc: 'Управляйте своим профилем и настройками приложения', + appSettings: 'Настройки приложения', + enterDisplayName: 'Как вас называть?', + tellAboutBio: 'Расскажите немного о себе...', + storageAndData: 'Хранилище и данные', + importTelegram: 'Импорт истории Telegram', + importTelegramDesc: 'Перенести сообщения и медиа из Telegram', // Chat chat: 'Чат', group: 'Группа', @@ -54,12 +61,16 @@ const translations = { members: 'участников', files: 'файлов', clearAll: 'Очистить всё', + maxFilesLimit: 'Максимум 20 файлов', + searchingHistory: 'Поиск сообщения в истории...', + messageNotFound: 'Сообщение не найдено', messagePlaceholder: 'Сообщение...', message: 'Сообщение...', addCaption: 'Добавьте подпись...', searchMessages: 'Поиск сообщений...', searchMessagesBtn: 'Поиск сообщений', userProfile: 'Профиль пользователя', + userProfileUpper: 'ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ', enableSound: 'Включить звук', disableSound: 'Выключить звук', deleteChat: 'Удалить чат', @@ -90,6 +101,9 @@ const translations = { incomingCall: 'Входящий звонок', incomingVideoCall: 'Входящий видеозвонок', calling: 'Вызов...', + isCalling: 'звонит...', + groupCall: 'Групповой звонок', + comingSoon: 'Скоро будет', accept: 'Принять', decline: 'Отклонить', endCall: 'Завершить', @@ -197,6 +211,39 @@ const translations = { yes: 'Да', no: 'Нет', you: 'вы', + // Stories Editor + cropVideo: 'Обрезка видео', + trimVideo: 'Обрезать видео', + trim: 'Обрезать', + reset: 'Сбросить', + startCreation: 'Начать создание', + brushTool: 'Кисти', + textTool: 'Текст', + stickersTool: 'Стикеры', + filtersTool: 'Коррекция', + textContent: 'Контент', + typography: 'Типографика', + textSize: 'Размер', + textColor: 'Цвет текста', + brushThickness: 'Толщина', + clearCanvas: 'Очистить холст', + correctionMaster: 'Мастер коррекции', + brightness: 'Яркость', + contrast: 'Контраст', + saturation: 'Насыщенность', + sepia: 'Сепия', + bgColor: 'Цвет фона', + clickToChooseColor: 'Нажмите для выбора цвета', + done: 'Готово', + stop: 'Остановить', + zoomHint: 'Колесико мыши для зума', + secSelected: 'сек выбрано', + muted: 'Без звука', + unmuted: 'Со звуком', + muteVideo: 'Отключить звук', + unmuteVideo: 'Включить звук', + interactive: 'Интерактив', + apply: 'Применить', // User profile mediaTab: 'Медиа', gifs: 'GIF', @@ -207,6 +254,7 @@ const translations = { namePlaceholder: 'Имя', notSpecified: 'Не указано', onKnotSince: 'На Knot с', + joined: 'С нами с', cancel: 'Отмена', confirm: 'Подтвердить', save: 'Сохранить', @@ -217,10 +265,13 @@ const translations = { storiesTab: 'Истории', publicationsTab: 'Публикации', noStories: 'Публикаций пока нет', + noBio: 'Информация отсутствует', goToMessage: 'Перейти к сообщению', story: 'История', commonGroups: 'Общие группы', more: 'Ещё', + empty: 'Пусто', + acceptRequest: 'Принять запрос', // Typing typingText: 'печатает', // Emoji @@ -350,6 +401,13 @@ const translations = { modernMessenger: 'A modern messenger focused', onPrivacy: 'on privacy and convenience', modernMessengerShort: 'Knot Messenger', + settingsDesc: 'Manage your profile and app settings', + appSettings: 'App Settings', + enterDisplayName: 'How should we call you?', + tellAboutBio: 'Tell us a bit about yourself...', + storageAndData: 'Storage and Data', + importTelegram: 'Import Telegram History', + importTelegramDesc: 'Move messages and media from Telegram', searchChats: 'Search chats...', chat: 'Chat', group: 'Group', @@ -366,12 +424,16 @@ const translations = { members: 'members', files: 'files', clearAll: 'Clear all', + maxFilesLimit: 'Maximum 20 files', + searchingHistory: 'Searching history...', + messageNotFound: 'Message not found', messagePlaceholder: 'Message...', message: 'Message...', addCaption: 'Add a caption...', searchMessages: 'Search messages...', searchMessagesBtn: 'Search messages', userProfile: 'User profile', + userProfileUpper: 'USER PROFILE', enableSound: 'Enable sound', disableSound: 'Mute', deleteChat: 'Delete chat', @@ -400,6 +462,9 @@ const translations = { incomingCall: 'Incoming call', incomingVideoCall: 'Incoming video call', calling: 'Calling...', + isCalling: 'is calling...', + groupCall: 'Group call', + comingSoon: 'Coming soon', accept: 'Accept', decline: 'Decline', endCall: 'End call', @@ -485,6 +550,40 @@ const translations = { yes: 'Yes', no: 'No', you: 'you', + // Stories Editor + cropVideo: 'Video Crop', + trimVideo: 'Trim Video', + trim: 'Trim', + reset: 'Reset', + startCreation: 'Start Creation', + brushTool: 'Brush', + textTool: 'Text', + stickersTool: 'Stickers', + filtersTool: 'Filters', + textContent: 'Content', + typography: 'Typography', + textSize: 'Size', + textColor: 'Color', + brushThickness: 'Thickness', + clearCanvas: 'Clear Canvas', + correctionMaster: 'Correction Master', + brightness: 'Brightness', + contrast: 'Contrast', + saturation: 'Saturation', + sepia: 'Sepia', + bgColor: 'Background', + clickToChooseColor: 'Click to choose color', + done: 'Done', + stop: 'Stop', + zoomHint: 'Mouse wheel to zoom', + secSelected: 'sec selected', + muted: 'Muted', + unmuted: 'Unmuted', + muteVideo: 'Mute Video', + unmuteVideo: 'Unmute Video', + interactive: 'Interactive', + apply: 'Apply', + // User profile mediaTab: 'Media', gifs: 'GIF', filesTab: 'Files', @@ -494,6 +593,7 @@ const translations = { namePlaceholder: 'Name', notSpecified: 'Not specified', onKnotSince: 'On Knot since', + joined: 'Joined', cancel: 'Cancel', confirm: 'Confirm', save: 'Save', @@ -504,10 +604,13 @@ const translations = { storiesTab: 'Stories', publicationsTab: 'Publications', noStories: 'No stories yet', + noBio: 'No information shared yet', goToMessage: 'Go to message', story: 'Story', commonGroups: 'Common groups', more: 'More', + empty: 'Empty', + acceptRequest: 'Accept Request', typingText: 'typing', emojiFrequent: 'Frequent', emojiGestures: 'Gestures', @@ -547,7 +650,7 @@ const translations = { formatMono: 'Monospace', formatMonoHint: '`text`', draft: 'Draft:', - datePlaceholder: 'dd.mm.yyyy', + datePlaceholder: 'mm/dd/yyyy', tenorKeyRequired: 'Tenor API key required for GIFs', openConsoleRun: 'Open console and run:', searchGifs: 'Search GIFs...', @@ -613,13 +716,10 @@ const translations = { }, } as const; -export type TranslationKey = keyof typeof translations.ru; -type TranslationValue = (typeof translations.ru)[K]; - interface LangState { lang: Lang; setLang: (lang: Lang) => void; - t: (key: K) => TranslationValue; + t: (key: keyof typeof translations['ru']) => any; } export const useLang = create((set, get) => ({ @@ -630,6 +730,6 @@ export const useLang = create((set, get) => ({ }, t: (key) => { const { lang } = get(); - return (translations[lang][key] ?? translations.ru[key] ?? key) as TranslationValue; + return translations[lang][key] || translations['ru'][key] || key; }, })); diff --git a/client-web/src/core/presentation/components/ui/DatePicker.tsx b/client-web/src/core/presentation/components/ui/DatePicker.tsx index effbac9..cc9be6f 100644 --- a/client-web/src/core/presentation/components/ui/DatePicker.tsx +++ b/client-web/src/core/presentation/components/ui/DatePicker.tsx @@ -66,9 +66,9 @@ export default function DatePicker({ value, onChange }: DatePickerProps) { } }, [open]); - const months = t('months'); - const weekDays = t('weekDays'); - const shortMonths = useMemo(() => months.map(m => m.slice(0, 3)), [months]); + const months = t('months') as string[]; + const weekDays = t('weekDays') as string[]; + const shortMonths = useMemo(() => months.map((m: string) => m.slice(0, 3)), [months]); const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate(); const firstDayRaw = new Date(viewYear, viewMonth, 1).getDay(); @@ -209,7 +209,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) { transition={{ duration: 0.12 }} >
- {weekDays.map((d) => ( + {weekDays.map((d: string) => (
{d}
))}
@@ -249,7 +249,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) { transition={{ duration: 0.12 }} className="grid grid-cols-3 gap-1 p-3" > - {shortMonths.map((m, idx) => { + {shortMonths.map((m: string, idx: number) => { const isCurrentMonth = viewYear === today.getFullYear() && idx === today.getMonth(); const isSelectedMonth = parsed && viewYear === parsed.getFullYear() && idx === parsed.getMonth(); return ( diff --git a/client-web/src/core/presentation/layouts/Sidebar.tsx b/client-web/src/core/presentation/layouts/Sidebar.tsx index c88f5a5..2e5a064 100644 --- a/client-web/src/core/presentation/layouts/Sidebar.tsx +++ b/client-web/src/core/presentation/layouts/Sidebar.tsx @@ -62,12 +62,15 @@ export default function Sidebar() { socket?.on('story_viewed', onStoryViewed); const handleOpenNewChat = () => setShowNewChat(true); + const handleOpenSideMenu = () => setShowSideMenu(true); window.addEventListener('OPEN_NEW_CHAT', handleOpenNewChat); + window.addEventListener('OPEN_SIDE_MENU', handleOpenSideMenu); return () => { clearInterval(interval); socket?.off('story_viewed', onStoryViewed); window.removeEventListener('OPEN_NEW_CHAT', handleOpenNewChat); + window.removeEventListener('OPEN_SIDE_MENU', handleOpenSideMenu); }; }, [user?.id]); @@ -105,8 +108,14 @@ export default function Sidebar() { <>
{/* Title Section (Minimal) */} -
+

{t('chats')}

+
{/* Search */} @@ -126,7 +135,7 @@ export default function Sidebar() { {/* Stories Section */}

- {t('stories') || 'ИСТОРИИ'} + {t('stories')}

diff --git a/client-web/src/modules/chats/presentation/ChatPage.tsx b/client-web/src/modules/chats/presentation/ChatPage.tsx index 083a0a8..102cb60 100644 --- a/client-web/src/modules/chats/presentation/ChatPage.tsx +++ b/client-web/src/modules/chats/presentation/ChatPage.tsx @@ -15,6 +15,7 @@ import CallModal from '../../calls/presentation/components/CallModal'; import GroupCallModal from '../../calls/presentation/components/GroupCallModal'; import ContactsSidebar from '../../friends/presentation/components/ContactsSidebar'; import SettingsPage from '../../users/presentation/components/SettingsPage'; +import UserProfile from '../../users/presentation/components/UserProfile'; export default function ChatPage() { const { @@ -419,10 +420,12 @@ export default function ChatPage() { ) : (
-

Coming soon

+

{t('comingSoon') || 'Coming soon'}

)} + +

- {incomingGroupCall.callerInfo?.displayName || incomingGroupCall.callerInfo?.username || 'User'} {t('calling' as any) || 'звонит...'} + {incomingGroupCall.callerInfo?.displayName || incomingGroupCall.callerInfo?.username || 'User'} {t('isCalling' as any) || 'звонит...'}

- {t('group' as any) || 'Групповой звонок'} + {t('groupCall' as any) || 'Групповой звонок'}

diff --git a/client-web/src/modules/chats/presentation/components/ChatListItem.tsx b/client-web/src/modules/chats/presentation/components/ChatListItem.tsx index 5980241..fd3ac93 100644 --- a/client-web/src/modules/chats/presentation/components/ChatListItem.tsx +++ b/client-web/src/modules/chats/presentation/components/ChatListItem.tsx @@ -186,7 +186,7 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {

{chat.unreadCount > 0 && !isActive && ( - + {chat.unreadCount} )} diff --git a/client-web/src/modules/chats/presentation/components/ChatView.tsx b/client-web/src/modules/chats/presentation/components/ChatView.tsx index 17f36f2..9d4c329 100644 --- a/client-web/src/modules/chats/presentation/components/ChatView.tsx +++ b/client-web/src/modules/chats/presentation/components/ChatView.tsx @@ -1171,7 +1171,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal if (!activeChat) return; const NotificationStore = await import('../../../../core/application/stores/notificationStore'); - NotificationStore.useNotificationStore.getState().addNotification('info', lang === 'ru' ? 'Поиск сообщения в истории...' : 'Searching message in history...'); + NotificationStore.useNotificationStore.getState().addNotification('info', t('searchingHistory' as any) || 'Searching message in history...'); const chatStore = useChatStore.getState(); let found = false; @@ -1209,7 +1209,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal } if (!found) { - NotificationStore.useNotificationStore.getState().addNotification('warning', lang === 'ru' ? 'Сообщение не найдено' : 'Message not found'); + NotificationStore.useNotificationStore.getState().addNotification('warning', t('messageNotFound' as any) || 'Message not found'); } }; diff --git a/client-web/src/modules/chats/presentation/components/MessageInput.tsx b/client-web/src/modules/chats/presentation/components/MessageInput.tsx index 84af953..0b91217 100644 --- a/client-web/src/modules/chats/presentation/components/MessageInput.tsx +++ b/client-web/src/modules/chats/presentation/components/MessageInput.tsx @@ -236,7 +236,7 @@ export default function MessageInput({ chatId }: MessageInputProps) { } catch (e) { console.error('Ошибка загрузки файла:', e); const { addNotification } = useNotificationStore.getState(); - addNotification('error', t('uploadError') || 'Ошибка загрузки файла'); + addNotification('error', t('uploadError') || 'Upload error'); } finally { setIsSending(false); } @@ -324,8 +324,8 @@ export default function MessageInput({ chatId }: MessageInputProps) { newAttachments.push({ file, type: isAudio ? 'audio' : 'file' }); } - if (tooLarge) addNotification('warning', t('fileTooLarge') || 'Некоторые файлы слишком большие'); - if (limitExceeded) addNotification('warning', 'Максимум 20 файлов'); + if (tooLarge) addNotification('warning', t('fileTooLarge') || 'Some files are too large'); + if (limitExceeded) addNotification('warning', t('maxFilesLimit' as any) || 'Maximum 20 files'); setAttachments(prev => [...prev, ...newAttachments]); inputRef.current?.focus(); @@ -352,7 +352,7 @@ export default function MessageInput({ chatId }: MessageInputProps) { newAttachments.push({ file, preview, type: isVideo ? 'video' : 'image' }); } - if (limitExceeded) addNotification('warning', 'Максимум 20 файлов'); + if (limitExceeded) addNotification('warning', t('maxFilesLimit' as any) || 'Maximum 20 files'); setAttachments(prev => [...prev, ...newAttachments]); inputRef.current?.focus(); @@ -567,8 +567,8 @@ export default function MessageInput({ chatId }: MessageInputProps) { newAttachments.push({ file, type, preview }); } - if (tooLarge) addNotification('warning', t('fileTooLarge') || 'Некоторые файлы слишком большие'); - if (limitExceeded) addNotification('warning', 'Максимум 20 файлов'); + if (tooLarge) addNotification('warning', t('fileTooLarge') || 'Some files are too large'); + if (limitExceeded) addNotification('warning', t('maxFilesLimit' as any) || 'Maximum 20 files'); setAttachments(prev => [...prev, ...newAttachments]); inputRef.current?.focus(); @@ -624,7 +624,7 @@ export default function MessageInput({ chatId }: MessageInputProps) { : `${t('replyTo')} ${replyTo?.sender?.displayName || replyTo?.sender?.userName || replyTo?.sender?.username || ''}`}

- {replyTo?.quote ? `«${replyTo.quote}»` : (editingMessage || replyTo)?.content || t('media') || 'Медиа'} + {replyTo?.quote ? `«${replyTo.quote}»` : (editingMessage || replyTo)?.content || t('media')}
- )} - +
@@ -464,7 +464,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) } }} className="w-10 h-10 rounded-xl bg-black/60 backdrop-blur-md flex items-center justify-center text-white border border-white/10 hover:bg-red-500/20 hover:border-red-500/40 transition-all" - title="Остановить" + title={t('stop')} > stop @@ -495,25 +495,23 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) ))} - {textObjects.map(t => ( - {setSelectedTextId(t.id); setSelectedStickerId(null); setTool('text');}}> - {t.text} - {selectedTextId === t.id && } + {textObjects.map(tobj => ( + {setSelectedTextId(tobj.id); setSelectedStickerId(null); setTool('text');}}> + {tobj.text} + {selectedTextId === tobj.id && } ))} {!mediaPreview && textObjects.length === 0 && stickers.length === 0 && ( - )} - - {/* Mobile interface components */} @@ -523,13 +521,13 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
{[ { id: 'text', icon: , label: 'Текст', action: addText }, - { id: 'stickers', icon: , label: 'Стикеры', active: tool === 'stickers' }, - { id: 'brush', icon: , label: 'Кисти', active: tool === 'brush' }, - { id: 'filters', icon: , label: 'Коррекция', active: tool === 'filters' }, + { id: 'stickers', icon: , label: t('stickersTool'), active: tool === 'stickers' }, + { id: 'brush', icon: , label: t('brushTool'), active: tool === 'brush' }, + { id: 'filters', icon: , label: t('filtersTool'), active: tool === 'filters' }, ].map(item => ( ))}
@@ -538,25 +536,25 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) {tool === 'text' && selectedTextId && (
- Контент - t.id === selectedTextId)?.text} onChange={e => setTextObjects(prev => prev.map(t => t.id === selectedTextId ? { ...t, text: e.target.value } : t))} className="bg-transparent border-b border-white/10 w-full py-2 text-2xl font-black outline-none italic" autoFocus /> + {t('textContent')} + tx.id === selectedTextId)?.text} onChange={e => setTextObjects(prev => prev.map(tx => tx.id === selectedTextId ? { ...tx, text: e.target.value } : tx))} className="bg-transparent border-b border-white/10 w-full py-2 text-2xl font-black outline-none italic" autoFocus />
- Типографика + {t('typography')}
{FONTS.map(f => ( - + ))}
-
Размер{textObjects.find(t => t.id === selectedTextId)?.size}px
- t.id === selectedTextId)?.size} onChange={e => setTextObjects(prev => prev.map(t => t.id === selectedTextId ? { ...t, size: parseInt(e.target.value) } : t))} className="w-full accent-primary h-1 bg-white/5 rounded-full" /> +
{t('textSize')}{textObjects.find(tx => tx.id === selectedTextId)?.size}px
+ tx.id === selectedTextId)?.size} onChange={e => setTextObjects(prev => prev.map(tx => tx.id === selectedTextId ? { ...tx, size: parseInt(e.target.value) } : tx))} className="w-full accent-primary h-1 bg-white/5 rounded-full" />
- Цвет текста -
t.id === selectedTextId)?.color }}> - t.id === selectedTextId)?.color} onChange={e => setTextObjects(prev => prev.map(t => t.id === selectedTextId ? { ...t, color: e.target.value } : t))} className="absolute inset-0 opacity-0 w-full h-full cursor-pointer" /> + {t('textColor')} +
tx.id === selectedTextId)?.color }}> + tx.id === selectedTextId)?.color} onChange={e => setTextObjects(prev => prev.map(tx => tx.id === selectedTextId ? { ...tx, color: e.target.value } : tx))} className="absolute inset-0 opacity-0 w-full h-full cursor-pointer" />
@@ -570,10 +568,10 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
-
Толщина{brushSize}px
+
{t('brushThickness')}{brushSize}px
setBrushSize(parseInt(e.target.value))} className="w-full accent-primary h-1 bg-white/5 rounded-full" />
- +
)} @@ -585,13 +583,13 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) {tool === 'filters' && ( -

Мастер коррекции

+

{t('correctionMaster')}

{[ - { id: 'brightness', icon: , label: 'Яркость', min: 40, max: 160 }, - { id: 'contrast', icon: , label: 'Контраст', min: 40, max: 160 }, - { id: 'saturate', icon: , label: 'Насыщенность', min: 0, max: 250 }, - { id: 'sepia', icon: , label: 'Сепия', min: 0, max: 100 }, + { id: 'brightness', icon: , label: t('brightness'), min: 40, max: 160 }, + { id: 'contrast', icon: , label: t('contrast'), min: 40, max: 160 }, + { id: 'saturate', icon: , label: t('saturation'), min: 0, max: 250 }, + { id: 'sepia', icon: , label: t('sepia'), min: 0, max: 100 }, ].map(adj => (
@@ -609,13 +607,13 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
- Цвет фона + {t('bgColor')}
setBgColor(e.target.value)} className="absolute inset-0 opacity-0 w-full h-full cursor-pointer" />
- Нажмите для выбора цвета + {t('clickToChooseColor')}
)} @@ -649,7 +647,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) }} className="w-12 h-12 rounded-full hover:bg-white/5 flex items-center justify-center text-zinc-400 transition-colors"> -

Обрезка видео

+

{t('cropVideo')}

@@ -726,12 +724,12 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
- Колесико мыши для зума ({zoom.toFixed(1)}x) + {t('zoomHint')} ({zoom.toFixed(1)}x)
{new Date(0 * 1000).toISOString().substr(14, 5)}
- {(endTime - startTime).toFixed(1)} сек выбрано + {(endTime - startTime).toFixed(1)} {t('secSelected')}
{new Date(videoDuration * 1000).toISOString().substr(14, 5)}
@@ -741,8 +739,8 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) {/* Overview Track (Mini-map) */}
- {thumbnails.map((t, i) => ( - mini + {thumbnails.map((th, i) => ( + mini ))}
{/* Selection UI on Mini-map */} @@ -753,9 +751,9 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) const rect = e.currentTarget.parentElement?.getBoundingClientRect(); if (!rect) return; const x = (e.clientX - rect.left) / rect.width; - const time = x * videoDuration; + const tval = x * videoDuration; (timelineRef.current as any)._dragging = 'range'; - (timelineRef.current as any)._offsetTime = time - startTime; + (timelineRef.current as any)._offsetTime = tval - startTime; }} style={{ left: `${(startTime / videoDuration) * 100}%`, @@ -773,9 +771,9 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) onClick={(e) => { const rect = e.currentTarget.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; - const time = x * videoDuration; - if (trimVideoRef.current) trimVideoRef.current.currentTime = time; - setCurrentPlayTime(time); + const tval = x * videoDuration; + if (trimVideoRef.current) trimVideoRef.current.currentTime = tval; + setCurrentPlayTime(tval); }} />
@@ -798,7 +796,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) onMouseDown={(e) => { const rect = e.currentTarget.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; - const time = x * videoDuration; + const tval = x * videoDuration; const startX = (startTime / videoDuration) * rect.width; const endX = (endTime / videoDuration) * rect.width; @@ -810,12 +808,12 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) (e.currentTarget as any)._dragging = 'end'; } else if (mouseX > startX && mouseX < endX) { (e.currentTarget as any)._dragging = 'range'; - (e.currentTarget as any)._offsetTime = time - startTime; + (e.currentTarget as any)._offsetTime = tval - startTime; } else { (e.currentTarget as any)._dragging = 'scrub'; if (trimVideoRef.current) { - trimVideoRef.current.currentTime = time; - setCurrentPlayTime(time); + trimVideoRef.current.currentTime = tval; + setCurrentPlayTime(tval); } } }} @@ -825,40 +823,40 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) const rect = e.currentTarget.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; - const time = Math.max(0, Math.min(videoDuration, x * videoDuration)); + const tval = Math.max(0, Math.min(videoDuration, x * videoDuration)); if (dragging === 'start') { - if (time < endTime - 0.2) { - setStartTime(time); + if (tval < endTime - 0.2) { + setStartTime(tval); if (trimVideoRef.current) { - trimVideoRef.current.currentTime = time; - setCurrentPlayTime(time); + trimVideoRef.current.currentTime = tval; + setCurrentPlayTime(tval); } } } else if (dragging === 'end') { - if (time > startTime + 0.2) { - setEndTime(time); + if (tval > startTime + 0.2) { + setEndTime(tval); if (trimVideoRef.current) { - trimVideoRef.current.currentTime = time; - setCurrentPlayTime(time); + trimVideoRef.current.currentTime = tval; + setCurrentPlayTime(tval); } } } else if (dragging === 'range') { const offset = (e.currentTarget as any)._offsetTime; - const duration = endTime - startTime; - let newStart = time - offset; + const dr = endTime - startTime; + let newStart = tval - offset; if (newStart < 0) newStart = 0; - if (newStart + duration > videoDuration) newStart = videoDuration - duration; + if (newStart + dr > videoDuration) newStart = videoDuration - dr; setStartTime(newStart); - setEndTime(newStart + duration); + setEndTime(newStart + dr); if (trimVideoRef.current) { trimVideoRef.current.currentTime = newStart; setCurrentPlayTime(newStart); } } else if (dragging === 'scrub') { if (trimVideoRef.current) { - trimVideoRef.current.currentTime = time; - setCurrentPlayTime(time); + trimVideoRef.current.currentTime = tval; + setCurrentPlayTime(tval); } } }} @@ -867,8 +865,8 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) > {/* Thumbnails Track */}
- {thumbnails.map((t, i) => ( - frame + {thumbnails.map((th, i) => ( + frame ))}
@@ -916,7 +914,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
- Начало + {lang === 'ru' ? 'Начало' : 'Start'}
{new Date(startTime * 1000).toISOString().substr(14, 5)}.{(startTime % 1).toFixed(1).substr(2)} @@ -925,7 +923,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
- Конец + {lang === 'ru' ? 'Конец' : 'End'}
{new Date(endTime * 1000).toISOString().substr(14, 5)}.{(endTime % 1).toFixed(1).substr(2)} @@ -951,7 +949,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps) disabled={videoDuration > 120 && (endTime - startTime) > 120} className="px-24 py-7 bg-white text-black rounded-full font-black uppercase tracking-[0.2em] text-sm hover:scale-105 active:scale-95 disabled:opacity-30 transition-all shadow-2xl" > - {videoDuration > 120 && (endTime - startTime) > 120 ? 'Слишком длинное' : 'Применить'} + {videoDuration > 120 && (endTime - startTime) > 120 ? (lang === 'ru' ? 'Слишком длинное' : 'Too Long') : t('apply')}
diff --git a/client-web/src/modules/users/presentation/components/SettingsPage.tsx b/client-web/src/modules/users/presentation/components/SettingsPage.tsx index a106e55..866203a 100644 --- a/client-web/src/modules/users/presentation/components/SettingsPage.tsx +++ b/client-web/src/modules/users/presentation/components/SettingsPage.tsx @@ -106,9 +106,9 @@ export default function SettingsPage() {

- {t('settings') || 'Settings'} + {t('myProfile') || 'My Profile'}

-

Управляйте своим профилем и настройками приложения

+

{t('settingsDesc')}

{!editing ? ( ) : (
@@ -124,7 +124,7 @@ export default function SettingsPage() { onClick={() => setEditing(false)} className="px-6 py-2.5 rounded-2xl text-on-surface-variant text-xs font-black uppercase tracking-widest hover:text-white transition-colors" > - {t('cancel') || 'Отмена'} + {t('cancel')}
)} @@ -194,14 +194,14 @@ export default function SettingsPage() {
- + {editing ? ( setFormData(p => ({ ...p, displayName: e.target.value }))} className="w-full bg-white/5 border border-white/10 rounded-2xl px-5 py-3 text-lg font-bold text-white focus:border-primary/50 transition-all outline-none" - placeholder="Как вас называть?" + placeholder={t('enterDisplayName')} /> ) : (

{user?.displayName}

@@ -211,7 +211,7 @@ export default function SettingsPage() {
- +

@{user?.username || user?.userName}

@@ -223,18 +223,18 @@ export default function SettingsPage() {
- {t('about') || 'О себе'} + {t('aboutMe')}
{editing ? (