полная локализация проекта

This commit is contained in:
kazachilo 2025-04-14 15:00:53 +03:00
parent 2bf8cb05b2
commit 334d77a2ef
34 changed files with 1165 additions and 326 deletions

View File

@ -6,6 +6,7 @@ import { initializeUserId, getCurrentUserId } from './constants/user';
import { trackScreenView } from './services/analyticsService';
import customAnalyticsService from './services/customAnalyticsService';
import { BalanceProvider } from './contexts/BalanceContext';
import { getTranslation } from './constants/translations';
// Ленивая загрузка компонентов
const OnboardingWelcome = lazy(() => import('./screens/onboarding/OnboardingWelcome'));
@ -29,7 +30,7 @@ const LoadingScreen = () => (
alignItems: 'center',
height: '100vh'
}}>
Загрузка...
{getTranslation('loading')}
</div>
);

View File

@ -1,5 +1,6 @@
import React from 'react';
import styles from './GenerateButton.module.css';
import { getTranslation } from '../../constants/translations';
interface GenerateButtonProps {
onGenerate: () => void;
@ -13,8 +14,8 @@ const GenerateButton: React.FC<GenerateButtonProps> = ({ onGenerate, tokenCount
onClick={onGenerate}
>
<span className={styles.generateButtonText}>
Начать генерацию
<span className={styles.tokenCount}>{tokenCount} токенов</span>
{getTranslation('start_generation')}
<span className={styles.tokenCount}>{getTranslation('tokens_count', tokenCount)}</span>
</span>
</button>
);

View File

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { ButtonBlock } from '../../types/blocks';
import SquareButton from './SquareButton';
import styles from './GridButtonsBlock.module.css';
import { getTranslation } from '../../constants/translations';
interface GridButtonsBlockProps {
block: ButtonBlock;
@ -61,7 +62,7 @@ const GridButtonsBlock: React.FC<GridButtonsBlockProps> = ({ block, onAction, is
className={styles.showMoreButton}
onClick={() => setExpanded(!expanded)}
>
{expanded ? 'Свернуть' : 'Показать больше'}
{expanded ? getTranslation('collapse') : getTranslation('show_more')}
</button>
)}
</div>

View File

@ -1,6 +1,7 @@
import React, { useState, useRef } from 'react';
import styles from './TextInputBlock.module.css';
import { TextInputBlock as TextInputBlockType } from '../../types/blocks';
import { getTranslation } from '../../constants/translations';
interface TextInputBlockProps {
block: TextInputBlockType;
@ -40,7 +41,7 @@ const TextInputBlock: React.FC<TextInputBlockProps> = ({ block, visible, onTextC
<textarea
ref={textareaRef}
className={styles.input}
placeholder="Опишите, какой стикер вы хотите получить..."
placeholder={getTranslation('sticker_description_placeholder')}
rows={3}
value={text}
onChange={handleChange}

View File

@ -1,6 +1,7 @@
import React, { useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import styles from './UploadPhotoBlock.module.css';
import { getTranslation } from '../../constants/translations';
interface UploadPhotoBlockProps {
onPhotoSelect?: (file: File) => void;
@ -64,17 +65,17 @@ const UploadPhotoBlock: React.FC<UploadPhotoBlockProps> = ({ onPhotoSelect, prev
>
{previewUrl ? (
<div className={styles.preview}>
<img src={previewUrl} alt="Preview" className={styles.previewImage} />
<img src={previewUrl} alt={getTranslation('preview_alt')} className={styles.previewImage} />
<div className={styles.changeOverlay}>
<span className={styles.changeText}>Изменить фото</span>
<span className={styles.changeText}>{getTranslation('change_photo')}</span>
</div>
</div>
) : (
<>
<div className={styles.icon}>📷</div>
<div className={styles.text}>
<span className={styles.title}>Загрузите фото</span>
<span className={styles.subtitle}>Перетащите или нажмите для выбора</span>
<span className={styles.title}>{getTranslation('upload_photo_block_title')}</span>
<span className={styles.subtitle}>{getTranslation('upload_photo_block_subtitle')}</span>
</div>
</>
)}

View File

@ -1,4 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import BlockRenderer from '../blocks/BlockRenderer';
import { homeScreenConfig } from '../../config/homeScreen';
import customAnalyticsService from '../../services/customAnalyticsService';
@ -16,6 +17,7 @@ const MainActions: React.FC<MainActionsProps> = ({
onSendFeedback,
onOpenTelegramBot
}) => {
const navigate = useNavigate();
// Находим блок верхних кнопок и разделитель в конфигурации
const actionsBlock = homeScreenConfig.homeScreen.blocks.find(block => block.id === 'mainActions');
const dividerBlock = homeScreenConfig.homeScreen.blocks.find(block => block.id === 'mainDivider');
@ -45,6 +47,15 @@ const MainActions: React.FC<MainActionsProps> = ({
});
onOpenTelegramBot();
}
} else if (actionType === 'route') {
// Отслеживаем событие нажатия на кнопку навигации
customAnalyticsService.trackEvent({
telegram_id: getCurrentUserId(),
event_category: 'ui_interaction',
event_name: 'instruction_button_click',
unit: actionValue
});
navigate(actionValue);
}
};

View File

@ -9,6 +9,7 @@ import { tokenPacks } from '../../constants/tokenPacks';
import NotificationModal from '../shared/NotificationModal';
import { useBalance } from '../../contexts/BalanceContext';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getTranslation } from '../../constants/translations';
import styles from './Header.module.css';
const Header: React.FC = () => {
@ -55,7 +56,7 @@ const Header: React.FC = () => {
<div className={styles.avatar}>
<img
src={user.avatarUrl}
alt="Avatar"
alt={getTranslation('avatar_alt')}
className={styles.avatarImage}
onError={(e) => {
e.currentTarget.onerror = null;
@ -80,10 +81,10 @@ const Header: React.FC = () => {
});
setShowTokensModal(true);
}}
title="Нажмите чтобы пополнить баланс"
title={getTranslation('add_balance_title')}
>
<span className={styles.balanceIcon}>
<img src={images.tokenIcon} alt="Токены" className={styles.tokenImage} />
<img src={images.tokenIcon} alt={getTranslation('tokens_alt')} className={styles.tokenImage} />
</span>
<span className={styles.balanceValue}>
{balance}
@ -111,8 +112,8 @@ const Header: React.FC = () => {
updateBalance();
// Показываем модальное окно с информацией об успешной оплате
setNotificationTitle('Оплата успешна!');
setNotificationMessage(`Вы успешно приобрели ${pack.tokens + pack.bonusTokens} токенов.`);
setNotificationTitle(getTranslation('payment_success'));
setNotificationMessage(getTranslation('tokens_purchased', pack.tokens + pack.bonusTokens));
setShowNotificationModal(true);
} else {
// Если данные не получены, делаем запрос на получение данных пользователя
@ -124,8 +125,8 @@ const Header: React.FC = () => {
updateBalance();
// Показываем модальное окно с информацией об успешной оплате
setNotificationTitle('Оплата успешна!');
setNotificationMessage(`Вы успешно приобрели ${pack.tokens + pack.bonusTokens} токенов. Ваш текущий баланс: ${balance} токенов.`);
setNotificationTitle(getTranslation('payment_success'));
setNotificationMessage(getTranslation('profile_tokens_purchased', pack.tokens + pack.bonusTokens, balance));
setShowNotificationModal(true);
} catch (error) {
console.error('Ошибка при обновлении данных пользователя:', error);
@ -143,7 +144,7 @@ const Header: React.FC = () => {
onGalleryClick={() => setShowNotificationModal(false)}
onContinueClick={() => setShowNotificationModal(false)}
showGalleryButton={false}
continueButtonText="Закрыть"
continueButtonText={getTranslation('close')}
/>
</header>
);

View File

@ -3,6 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
import styles from './Navigation.module.css';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import { getTranslation } from '../../constants/translations';
const NavigationComponent: React.FC = () => {
const navigate = useNavigate();
@ -32,7 +33,7 @@ const NavigationComponent: React.FC = () => {
<path d="M9 22V12h6v10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<span className={styles.label}>Главная</span>
<span className={styles.label}>{getTranslation('nav_home')}</span>
</button>
<button
@ -54,7 +55,7 @@ const NavigationComponent: React.FC = () => {
<path d="M20 16l-4-4L6 20" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<span className={styles.label}>Галерея</span>
<span className={styles.label}>{getTranslation('nav_gallery')}</span>
</button>
<button
@ -75,7 +76,7 @@ const NavigationComponent: React.FC = () => {
<path d="M4 8l8 4 8-4M12 12v8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<span className={styles.label}>Стикерпаки</span>
<span className={styles.label}>{getTranslation('nav_sticker_packs')}</span>
</button>
<button
@ -96,7 +97,7 @@ const NavigationComponent: React.FC = () => {
<circle cx="12" cy="7" r="4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<span className={styles.label}>Профиль</span>
<span className={styles.label}>{getTranslation('nav_profile')}</span>
</button>
</div>
</div>

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import styles from './NotificationModal.module.css';
import emojiStyles from './EmojiPickerModal.module.css';
import { getTranslation } from '../../constants/translations';
interface EmojiPickerModalProps {
isVisible: boolean;
@ -63,7 +64,7 @@ const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
<div className={styles.overlay}>
<div className={styles.modal}>
<div className={styles.header}>
<div className={styles.title}>Выберите эмодзи</div>
<div className={styles.title}>{getTranslation('emoji_picker_title')}</div>
</div>
<div className={emojiStyles.emojiContainer}>
@ -85,13 +86,13 @@ const EmojiPickerModal: React.FC<EmojiPickerModalProps> = ({
className={`${styles.button} ${styles.secondaryButton}`}
onClick={onCancel}
>
Отмена
{getTranslation('emoji_picker_cancel')}
</button>
<button
className={`${styles.button} ${styles.primaryButton}`}
onClick={handleApply}
>
Применить
{getTranslation('emoji_picker_apply')}
</button>
</div>
</div>

View File

@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect, memo } from 'react';
import styles from './NotificationModal.module.css'; // Используем существующие стили
import feedbackStyles from './FeedbackModal.module.css'; // Дополнительные стили для формы
import { getTranslation } from '../../constants/translations';
export interface FeedbackData {
text: string;
@ -41,9 +42,9 @@ const ImagePreview = memo(({
return (
<div className={feedbackStyles.imagePreview}>
{url ? (
<img src={url} alt={`Preview ${index}`} />
<img src={url} alt={getTranslation('preview_image_alt', index)} />
) : (
<div className={feedbackStyles.imageLoading}>Загрузка...</div>
<div className={feedbackStyles.imageLoading}>{getTranslation('loading_image')}</div>
)}
<button
className={feedbackStyles.removeImageButton}
@ -101,7 +102,7 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
setImages(prevImages => [...prevImages, file]);
} else {
console.error('Выбранный файл не является изображением');
setError('Пожалуйста, выберите файл изображения (JPEG, PNG и т.д.)');
setError(getTranslation('select_image_file'));
}
};
@ -121,7 +122,7 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
}
} catch (err) {
console.error('Ошибка при обработке выбранного файла:', err);
setError('Произошла ошибка при выборе файла. Пожалуйста, попробуйте еще раз.');
setError(getTranslation('file_selection_error'));
}
};
@ -134,7 +135,7 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
const handleSubmit = async () => {
// Проверяем, что есть текст или изображения
if (!text.trim() && images.length === 0) {
setError('Пожалуйста, введите текст или добавьте изображение');
setError(getTranslation('enter_text_or_add_image'));
return;
}
@ -150,11 +151,11 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
if (result) {
handleClose();
} else {
setError('Не удалось отправить обратную связь. Пожалуйста, попробуйте позже.');
setError(getTranslation('failed_to_send_feedback'));
}
} catch (err) {
console.error('Ошибка при отправке обратной связи:', err);
setError('Произошла ошибка при отправке. Пожалуйста, попробуйте позже.');
setError(getTranslation('error_sending_feedback'));
} finally {
setIsLoading(false);
}
@ -168,16 +169,16 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
<div className={styles.header}>
<div className={styles.title}>
{isLoading && <span className={styles.spinner}></span>}
Обратная связь
{getTranslation('feedback_title')}
</div>
<div className={styles.message}>Расскажите нам о проблеме или предложении</div>
<div className={styles.message}>{getTranslation('feedback_subtitle')}</div>
</div>
{/* Поле для ввода текста */}
<textarea
ref={textareaRef}
className={feedbackStyles.textarea}
placeholder="Опишите вашу проблему или предложение..."
placeholder={getTranslation('feedback_placeholder')}
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handleKeyDown}
@ -193,7 +194,7 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
type="button"
disabled={isLoading}
>
Добавить изображение
{getTranslation('add_image')}
</button>
<input
ref={fileInputRef}
@ -235,7 +236,7 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
type="button"
disabled={isLoading}
>
Отмена
{getTranslation('cancel')}
</button>
<button
className={`${styles.button} ${styles.primaryButton}`}
@ -243,7 +244,7 @@ const FeedbackModal: React.FC<FeedbackModalProps> = ({ isVisible, onClose, onSub
type="button"
disabled={isLoading}
>
{isLoading ? 'Отправка...' : 'Отправить'}
{isLoading ? getTranslation('generation_sending') : getTranslation('accept')}
</button>
</div>
</div>

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import { getTranslation } from '../../constants/translations';
import styles from './ImageViewer.module.css';
interface ImageViewerProps {
@ -96,7 +97,7 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ imageUrl, onClose }) => {
</button>
<img
src={imageUrl}
alt="Полноэкранный просмотр"
alt={getTranslation('fullscreen_view_alt')}
className={styles.fullImage}
style={{ transform: `translateY(-${translateY}px)` }}
/>

View File

@ -1,5 +1,6 @@
import React from 'react';
import styles from './NotificationModal.module.css';
import { getTranslation } from '../../constants/translations';
interface NotificationModalProps {
isVisible: boolean;
@ -28,8 +29,8 @@ const NotificationModal: React.FC<NotificationModalProps> = ({
onContinueClick,
showGalleryButton = true, // По умолчанию кнопка "В галерею" видима
showButtons = true, // По умолчанию все кнопки видимы
continueButtonText = 'Продолжить', // По умолчанию текст кнопки "Продолжить"
galleryButtonText = 'В галерею', // По умолчанию текст кнопки "В галерею"
continueButtonText = getTranslation('continue'), // По умолчанию текст кнопки "Продолжить"
galleryButtonText = getTranslation('gallery'), // По умолчанию текст кнопки "В галерею"
isPrimaryGalleryButton = true, // По умолчанию кнопка "В галерею" синяя
taskId,
generatedImageUrl,
@ -52,13 +53,13 @@ const NotificationModal: React.FC<NotificationModalProps> = ({
{isGenerating && queuePosition !== undefined && queuePosition <= 2 && (
<div className={styles.generationPreview}>
{generatedImageUrl ? (
<img src={generatedImageUrl} alt="Сгенерированный стикер" className={styles.generatedImage} />
<img src={generatedImageUrl} alt={getTranslation('generated_sticker')} className={styles.generatedImage} />
) : (
<>
<div className={styles.magicRipple}>
{/* Анимация магической ряби */}
</div>
<div className={styles.generationText}>Генерация...</div>
<div className={styles.generationText}>{getTranslation('generating')}</div>
</>
)}
</div>

View File

@ -4,6 +4,7 @@ import { TokenPack } from '../../constants/tokenPacks';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import styles from './TokenPackCard.module.css';
import { getTranslation } from '../../constants/translations';
interface TokenPackCardProps extends TokenPack {
onBuy: () => void;
@ -13,6 +14,7 @@ interface TokenPackCardProps extends TokenPack {
}
const TokenPackCard: React.FC<TokenPackCardProps> = ({
id,
title,
tokens,
bonusTokens,
@ -43,38 +45,38 @@ const TokenPackCard: React.FC<TokenPackCardProps> = ({
>
{isPopular && (
<div className={`${styles.badge} ${styles.popularBadge}`}>
Популярный выбор
{getTranslation('token_pack_popular')}
</div>
)}
{isBestValue && (
<div className={`${styles.badge} ${styles.bestValueBadge}`}>
Максимальная выгода
{getTranslation('token_pack_best_value')}
</div>
)}
<h3 className={styles.title}>{title}</h3>
<h3 className={styles.title}>{getTranslation(`token_pack_${id}_title`)}</h3>
<div className={styles.tokenInfo}>
<img src={images.tokenIcon} alt="Токены" className={styles.tokenIcon} />
<img src={images.tokenIcon} alt={getTranslation('token_pack_tokens')} className={styles.tokenIcon} />
<div className={styles.tokenCount}>
<span className={styles.baseTokens}>{tokens}</span>
{bonusTokens > 0 && (
<span className={styles.bonusTokens}>+{bonusTokens} БОНУС</span>
<span className={styles.bonusTokens}>{getTranslation('token_pack_bonus', bonusTokens)}</span>
)}
</div>
</div>
<div className={styles.stickersCount}>
{stickersCount} стикеров
{getTranslation('token_pack_stickers_count', stickersCount)}
</div>
{description && (
<p className={styles.description}>{description}</p>
<p className={styles.description}>{getTranslation(`token_pack_${id}_description`)}</p>
)}
<div className={styles.priceSection}>
<div className={styles.price}>
{price} Stars
{price} {getTranslation('token_pack_stars')}
</div>
<button
className={styles.buyButton}
@ -90,7 +92,7 @@ const TokenPackCard: React.FC<TokenPackCardProps> = ({
onBuy();
}}
>
КУПИТЬ
{getTranslation('token_pack_buy')}
</button>
</div>
</div>

View File

@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react';
import { tokenPacks } from '../../constants/tokenPacks';
import TokenPackCard from './TokenPackCard';
import styles from './TokenPacksList.module.css';
import { getTranslation } from '../../constants/translations';
interface TokenPacksListProps {
onBuyPack: (packId: string) => void;
@ -32,7 +33,7 @@ const TokenPacksList: React.FC<TokenPacksListProps> = ({
return (
<div className={`${styles.container} ${className}`}>
<h2 className={styles.title}>Пакеты токенов</h2>
<h2 className={styles.title}>{getTranslation('token_packs_title')}</h2>
<div
className={`${styles.list} ${compact ? styles.horizontalScroll : ''}`}
ref={listRef}

View File

@ -2,6 +2,7 @@ import React from 'react';
import { tokenPacks, TokenPack } from '../../constants/tokenPacks';
import styles from './TokenPacksModal.module.css';
import TokenPacksList from './TokenPacksList';
import { getTranslation } from '../../constants/translations';
interface TokenPacksModalProps {
isVisible: boolean;
@ -35,7 +36,7 @@ const TokenPacksModal: React.FC<TokenPacksModalProps> = ({
return (
<div className={styles.overlay} onClick={onClose}>
<div className={styles.modal} onClick={e => e.stopPropagation()}>
<h2 className={styles.title}>Недостаточно токенов для генерации</h2>
<h2 className={styles.title}>{getTranslation('token_modal_title')}</h2>
<TokenPacksList
onBuyPack={onBuyPack}
@ -50,7 +51,7 @@ const TokenPacksModal: React.FC<TokenPacksModalProps> = ({
onShowAllPacks();
}}
>
Показать все пакеты
{getTranslation('token_modal_show_all')}
</button>
<button

View File

@ -1,5 +1,6 @@
import { AppConfig } from '../types/blocks';
import { images } from '../assets';
import { getTranslation } from '../constants/translations';
export const homeScreenConfig: AppConfig = {
homeScreen: {
@ -15,7 +16,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Обратная связь',
title: getTranslation('feedback'),
imageUrl: images.feedback,
action: {
type: 'function',
@ -29,7 +30,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#2B9CFF', '#1E88E5']
},
title: 'Инструкция',
title: getTranslation('instruction'),
imageUrl: images.faq,
action: {
type: 'route',
@ -43,7 +44,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#8A2BE2', '#9400D3']
},
title: 'ShortsLoader',
title: getTranslation('shortsLoader'),
imageUrl: images.shorts,
action: {
type: 'function',
@ -68,7 +69,7 @@ export const homeScreenConfig: AppConfig = {
type: 'stepTitle',
id: 'step1',
number: 1,
text: 'Загрузи фото с лицом'
text: getTranslation('uploadPhoto')
},
{
type: 'uploadPhoto',
@ -88,7 +89,7 @@ export const homeScreenConfig: AppConfig = {
type: 'color',
colors: ['#E0E0E0']
},
title: 'Авто',
title: getTranslation('auto'),
action: {
type: 'selectGenderDetection',
value: 'auto'
@ -101,7 +102,7 @@ export const homeScreenConfig: AppConfig = {
type: 'color',
colors: ['#E0E0E0']
},
title: 'Мужчина',
title: getTranslation('man'),
action: {
type: 'selectGender',
value: 'man'
@ -114,7 +115,7 @@ export const homeScreenConfig: AppConfig = {
type: 'color',
colors: ['#E0E0E0']
},
title: 'Женщина',
title: getTranslation('woman'),
action: {
type: 'selectGender',
value: 'woman'
@ -132,7 +133,7 @@ export const homeScreenConfig: AppConfig = {
type: 'stepTitle',
id: 'step2',
number: 2,
text: 'Выбери стиль'
text: getTranslation('chooseStyle')
},
{
type: 'scrollableButtons',
@ -145,7 +146,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#2B9CFF', '#1E88E5']
},
title: 'Эмоции',
title: getTranslation('emotions'),
imageUrl: images.emotions,
action: {
type: 'selectStyle',
@ -159,7 +160,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Чиби',
title: getTranslation('chibi'),
imageUrl: images.balerina,
action: {
type: 'selectStyle',
@ -173,7 +174,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Реализм',
title: getTranslation('realism'),
imageUrl: images.realism,
action: {
type: 'selectStyle',
@ -192,7 +193,7 @@ export const homeScreenConfig: AppConfig = {
type: 'stepTitle',
id: 'step3',
number: 3,
text: 'Выбери образ'
text: getTranslation('chooseImage')
},
{
type: 'gridButtons',
@ -205,7 +206,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#2B9CFF', '#1E88E5']
},
title: 'Коллекция',
title: getTranslation('collection'),
action: {
type: 'selectEmotionType',
value: 'prompts'
@ -218,7 +219,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Мемы',
title: getTranslation('memes'),
action: {
type: 'selectEmotionType',
value: 'memes'
@ -243,7 +244,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Мем 1',
title: getTranslation('meme1'),
imageUrl: images.meme1,
action: {
type: 'selectMeme',
@ -257,7 +258,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#2B9CFF', '#1E88E5']
},
title: 'Мем 2',
title: getTranslation('meme2'),
imageUrl: images.meme2,
action: {
type: 'selectMeme',
@ -271,7 +272,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Мем 3',
title: getTranslation('meme3'),
imageUrl: images.meme3,
action: {
type: 'selectMeme',
@ -285,7 +286,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Мем 4',
title: getTranslation('meme4'),
imageUrl: images.meme4,
action: {
type: 'selectMeme',
@ -299,7 +300,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Мем 5',
title: getTranslation('meme5'),
imageUrl: images.meme5,
action: {
type: 'selectMeme',
@ -313,7 +314,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#FF9800', '#FB8C00']
},
title: 'Мем 6',
title: getTranslation('meme6'),
imageUrl: images.meme6,
action: {
type: 'selectMeme',
@ -327,7 +328,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#607D8B', '#455A64']
},
title: 'Мем 7',
title: getTranslation('meme7'),
imageUrl: images.meme7,
action: {
type: 'selectMeme',
@ -341,7 +342,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Мем 8',
title: getTranslation('meme8'),
imageUrl: images.meme8,
action: {
type: 'selectMeme',
@ -355,7 +356,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#009688', '#00796B']
},
title: 'Мем 9',
title: getTranslation('meme9'),
imageUrl: images.meme9,
action: {
type: 'selectMeme',
@ -369,7 +370,7 @@ export const homeScreenConfig: AppConfig = {
type: 'gradient',
colors: ['#673AB7', '#512DA8']
},
title: 'Мем 10',
title: getTranslation('meme10'),
imageUrl: images.meme10,
action: {
type: 'selectMeme',

View File

@ -1,5 +1,6 @@
import { BlockButton } from '../types/blocks';
import { images } from '../assets';
import { getTranslation } from '../constants/translations';
interface StylePresets {
[key: string]: {
@ -17,7 +18,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Спорткар',
title: getTranslation('preset_sportscar'),
imageUrl: images.sportcar,
action: {
type: 'selectPreset',
@ -31,7 +32,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Скейтборд',
title: getTranslation('preset_skateboard'),
imageUrl: images.skateboard,
action: {
type: 'selectPreset',
@ -45,7 +46,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Кофе',
title: getTranslation('preset_coffee'),
imageUrl: images.coffee,
action: {
type: 'selectPreset',
@ -59,7 +60,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Цветы',
title: getTranslation('preset_flowers'),
imageUrl: images.flowers,
action: {
type: 'selectPreset',
@ -73,7 +74,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#2196F3', '#1976D2']
},
title: 'Шарик',
title: getTranslation('preset_balloon'),
imageUrl: images.balloon,
action: {
type: 'selectPreset',
@ -87,7 +88,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Книга',
title: getTranslation('preset_book'),
imageUrl: images.book,
action: {
type: 'selectPreset',
@ -101,7 +102,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Мороженое',
title: getTranslation('preset_icecream'),
imageUrl: images.icecream,
action: {
type: 'selectPreset',
@ -115,7 +116,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#3F51B5', '#303F9F']
},
title: 'Зонт',
title: getTranslation('preset_umbrella'),
imageUrl: images.umbrella,
action: {
type: 'selectPreset',
@ -129,7 +130,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#E91E63', '#C2185B']
},
title: 'Коктейль',
title: getTranslation('preset_cocktail'),
imageUrl: images.cocktail,
action: {
type: 'selectPreset',
@ -143,7 +144,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Подарок',
title: getTranslation('preset_gift'),
imageUrl: images.gift,
action: {
type: 'selectPreset',
@ -157,7 +158,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Собака',
title: getTranslation('preset_dog'),
imageUrl: images.dog,
action: {
type: 'selectPreset',
@ -171,7 +172,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#607D8B', '#455A64']
},
title: 'Газета',
title: getTranslation('preset_newspaper'),
imageUrl: images.newspaper,
action: {
type: 'selectPreset',
@ -185,7 +186,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Велосипед',
title: getTranslation('preset_bicycle'),
imageUrl: images.bicycle,
action: {
type: 'selectPreset',
@ -199,7 +200,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#00BCD4', '#0097A7']
},
title: 'Серфер',
title: getTranslation('preset_surfer'),
imageUrl: images.surfing,
action: {
type: 'selectPreset',
@ -213,7 +214,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9E9E9E', '#757575']
},
title: 'Детектив',
title: getTranslation('preset_detective'),
imageUrl: images.detective,
action: {
type: 'selectPreset',
@ -227,7 +228,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#212121', '#000000']
},
title: 'Байкер',
title: getTranslation('preset_biker'),
imageUrl: images.moto,
action: {
type: 'selectPreset',
@ -241,7 +242,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Фея',
title: getTranslation('preset_fairy'),
imageUrl: images.fairy,
action: {
type: 'selectPreset',
@ -255,7 +256,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#3F51B5', '#303F9F']
},
title: 'Ученый',
title: getTranslation('preset_scientist'),
imageUrl: images.science,
action: {
type: 'selectPreset',
@ -269,7 +270,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Ковбой',
title: getTranslation('preset_cowboy'),
imageUrl: images.cowboy,
action: {
type: 'selectPreset',
@ -283,7 +284,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#607D8B', '#455A64']
},
title: 'Рыцарь',
title: getTranslation('preset_knight'),
imageUrl: images.knight,
action: {
type: 'selectPreset',
@ -297,7 +298,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Балерина',
title: getTranslation('preset_ballerina'),
imageUrl: images.balerina,
action: {
type: 'selectPreset',
@ -311,7 +312,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Пожарный',
title: getTranslation('preset_firefighter'),
imageUrl: images.fire,
action: {
type: 'selectPreset',
@ -325,7 +326,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Шеф-повар',
title: getTranslation('preset_chef'),
imageUrl: images.cook,
action: {
type: 'selectPreset',
@ -343,7 +344,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#2196F3', '#1976D2']
},
title: 'Деловой',
title: getTranslation('preset_business'),
icon: '💼',
action: {
type: 'selectPreset',
@ -357,7 +358,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Повседневный',
title: getTranslation('preset_casual'),
icon: '👕',
action: {
type: 'selectPreset',
@ -371,7 +372,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Спортивный',
title: getTranslation('preset_sport'),
icon: '⚽',
action: {
type: 'selectPreset',
@ -385,7 +386,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Вечерний',
title: getTranslation('preset_evening'),
icon: '🌙',
action: {
type: 'selectPreset',
@ -399,7 +400,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#00BCD4', '#0097A7']
},
title: 'Пляжный',
title: getTranslation('preset_beach'),
icon: '🏖️',
action: {
type: 'selectPreset',
@ -413,7 +414,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#90CAF9', '#64B5F6']
},
title: 'Зимний',
title: getTranslation('preset_winter'),
icon: '❄️',
action: {
type: 'selectPreset',
@ -427,7 +428,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Ретро',
title: getTranslation('preset_retro'),
icon: '🎸',
action: {
type: 'selectPreset',
@ -441,7 +442,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#E91E63', '#9C27B0']
},
title: 'Киберпанк',
title: getTranslation('preset_cyberpunk'),
icon: '🤖',
action: {
type: 'selectPreset',
@ -455,7 +456,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#2E7D32']
},
title: 'Военный',
title: getTranslation('preset_military'),
icon: '🪖',
action: {
type: 'selectPreset',
@ -469,7 +470,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#3E2723']
},
title: 'Стимпанк',
title: getTranslation('preset_steampunk'),
icon: '⚙️',
action: {
type: 'selectPreset',
@ -488,7 +489,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Привет',
title: getTranslation('preset_greeting'),
imageUrl: images.emoGreeting,
action: {
type: 'selectPreset',
@ -502,7 +503,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#2196F3', '#1976D2']
},
title: 'Класс',
title: getTranslation('preset_thumbsup'),
imageUrl: images.emoThumbsUp,
action: {
type: 'selectPreset',
@ -516,7 +517,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Сердце',
title: getTranslation('preset_heart'),
imageUrl: images.emoHeart,
action: {
type: 'selectPreset',
@ -530,7 +531,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FFD700', '#FFA500']
},
title: 'Смех',
title: getTranslation('preset_laugh'),
imageUrl: images.emoLaugh,
action: {
type: 'selectPreset',
@ -544,7 +545,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Шок',
title: getTranslation('preset_shock'),
imageUrl: images.emoShock,
action: {
type: 'selectPreset',
@ -558,7 +559,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Угроза',
title: getTranslation('preset_threat'),
imageUrl: images.emoThreat,
action: {
type: 'selectPreset',
@ -572,7 +573,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#2E7D32']
},
title: 'Халк',
title: getTranslation('preset_hulk'),
imageUrl: images.emoHulk,
action: {
type: 'selectPreset',
@ -586,7 +587,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Огонь',
title: getTranslation('preset_fire'),
imageUrl: images.emoFire,
action: {
type: 'selectPreset',
@ -600,7 +601,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Чай',
title: getTranslation('preset_tea'),
imageUrl: images.emoTea,
action: {
type: 'selectPreset',
@ -614,7 +615,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9575CD', '#7E57C2']
},
title: 'Танец',
title: getTranslation('preset_dance'),
imageUrl: images.emoDance,
action: {
type: 'selectPreset',
@ -629,7 +630,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FFD700', '#FFA500']
},
title: 'Трофей',
title: getTranslation('preset_trophy'),
imageUrl: images.emoTrophy,
action: {
type: 'selectPreset',
@ -643,7 +644,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Мишка',
title: getTranslation('preset_teddybear'),
imageUrl: images.emoTeddyBear,
action: {
type: 'selectPreset',
@ -657,7 +658,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Роза',
title: getTranslation('preset_rose'),
imageUrl: images.emoRose,
action: {
type: 'selectPreset',
@ -671,7 +672,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Подарок',
title: getTranslation('preset_gift'),
imageUrl: images.emoGift,
action: {
type: 'selectPreset',
@ -685,7 +686,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#45A049']
},
title: 'Букет',
title: getTranslation('preset_flowers_bouquet'),
imageUrl: images.emoFlowers,
action: {
type: 'selectPreset',
@ -699,7 +700,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Караоке',
title: getTranslation('preset_karaoke'),
imageUrl: images.emoKaraoke,
action: {
type: 'selectPreset',
@ -713,7 +714,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#2E7D32']
},
title: 'Зомби',
title: getTranslation('preset_zombie'),
imageUrl: images.emoZombie,
action: {
type: 'selectPreset',
@ -727,7 +728,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#607D8B', '#455A64']
},
title: 'Бездомный',
title: getTranslation('preset_homeless'),
imageUrl: images.emoHomeless,
action: {
type: 'selectPreset',
@ -741,7 +742,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF9800', '#F57C00']
},
title: 'Пиво',
title: getTranslation('preset_beer'),
imageUrl: images.emoBeer,
action: {
type: 'selectPreset',
@ -755,7 +756,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Бокс',
title: getTranslation('preset_boxing'),
imageUrl: images.emoBoxing,
action: {
type: 'selectPreset',
@ -769,7 +770,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF69B4', '#FF1493']
},
title: 'Котёнок',
title: getTranslation('preset_kitten'),
imageUrl: images.emoKitten,
action: {
type: 'selectPreset',
@ -783,7 +784,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Щенок',
title: getTranslation('preset_puppy'),
imageUrl: images.emoPuppy,
action: {
type: 'selectPreset',
@ -797,7 +798,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#3F51B5', '#303F9F']
},
title: 'Супергерой',
title: getTranslation('preset_superhero'),
imageUrl: images.emoSuperhero,
action: {
type: 'selectPreset',
@ -811,7 +812,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#00BCD4', '#0097A7']
},
title: 'Мороженое',
title: getTranslation('preset_icecream'),
imageUrl: images.emoIceCream,
action: {
type: 'selectPreset',
@ -825,7 +826,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Волшебник',
title: getTranslation('preset_wizard'),
imageUrl: images.emoWizard,
action: {
type: 'selectPreset',
@ -839,7 +840,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#FF5722', '#F4511E']
},
title: 'Дракон',
title: getTranslation('preset_dragon'),
imageUrl: images.emoDragon,
action: {
type: 'selectPreset',
@ -853,7 +854,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#212121', '#000000']
},
title: 'Киберпанк',
title: getTranslation('preset_cyberpunk'),
imageUrl: images.emoCyberpunk,
action: {
type: 'selectPreset',
@ -867,7 +868,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#3F51B5', '#303F9F']
},
title: 'Маг',
title: getTranslation('preset_mage'),
imageUrl: images.emoMage,
action: {
type: 'selectPreset',
@ -881,7 +882,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Пират',
title: getTranslation('preset_pirate'),
imageUrl: images.emoPirate,
action: {
type: 'selectPreset',
@ -895,7 +896,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#212121', '#000000']
},
title: 'Самурай',
title: getTranslation('preset_samurai'),
imageUrl: images.emoSamurai,
action: {
type: 'selectPreset',
@ -909,7 +910,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#607D8B', '#455A64']
},
title: 'Учёный',
title: getTranslation('preset_scientist'),
imageUrl: images.emoScientist,
action: {
type: 'selectPreset',
@ -923,7 +924,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#9C27B0', '#7B1FA2']
},
title: 'Вино',
title: getTranslation('preset_wine'),
imageUrl: images.emoWine,
action: {
type: 'selectPreset',
@ -937,7 +938,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#4CAF50', '#2E7D32']
},
title: 'Слизь',
title: getTranslation('preset_slime'),
imageUrl: images.emoSlime,
action: {
type: 'selectPreset',
@ -951,7 +952,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#795548', '#5D4037']
},
title: 'Всадник',
title: getTranslation('preset_rider'),
imageUrl: images.emoRider,
action: {
type: 'selectPreset',
@ -965,7 +966,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#3F51B5', '#303F9F']
},
title: 'Доктор Стрэндж',
title: getTranslation('preset_drstrange'),
imageUrl: images.emoDrStrange,
action: {
type: 'selectPreset',
@ -979,7 +980,7 @@ export const stylePresets: StylePresets = {
type: 'gradient',
colors: ['#3F51B5', '#303F9F']
},
title: 'Капитан Америка',
title: getTranslation('preset_captainamerica'),
imageUrl: images.emoCaptainAmerica,
action: {
type: 'selectPreset',

View File

@ -0,0 +1,797 @@
/**
* Файл с переводами и функциями для локализации
*/
// Импортируем функцию для получения информации о пользователе
import { getUserInfo } from './user';
// Типы для объектов переводов
type TranslationDictionary = {
[key: string]: string;
};
type Translations = {
[language: string]: TranslationDictionary;
};
// Объект с переводами
export const translations: Translations = {
ru: {
// Тексты из homeScreen.ts
'feedback': 'Обратная связь',
'instruction': 'Инструкция',
'shortsLoader': 'ShortsLoader',
'uploadPhoto': 'Загрузи фото с лицом',
'auto': 'Авто',
'man': 'Мужчина',
'woman': 'Женщина',
'chooseStyle': 'Выбери стиль',
'emotions': 'Эмоции',
'chibi': 'Чиби',
'realism': 'Реализм',
'chooseImage': 'Выбери образ',
'collection': 'Коллекция',
'memes': 'Мемы',
'loading': 'Загрузка...',
'generate': 'Сгенерировать',
'meme1': 'Мем 1',
'meme2': 'Мем 2',
'meme3': 'Мем 3',
'meme4': 'Мем 4',
'meme5': 'Мем 5',
'meme6': 'Мем 6',
'meme7': 'Мем 7',
'meme8': 'Мем 8',
'meme9': 'Мем 9',
'meme10': 'Мем 10',
// Тексты из OnboardingWelcome.tsx
'welcome_title': 'Добро пожаловать в Sticker Generator',
'welcome_description': 'Создавайте уникальные стикеры из ваших фотографий с помощью искусственного интеллекта',
'next': 'Далее',
'skip': 'Пропустить',
// Тексты из OnboardingHowTo.tsx
'how_to_title': 'Как создать стикер',
'upload_photo_title': 'Загрузите фото',
'upload_photo_description': 'Выберите фотографию и обрежьте её в квадрат',
'choose_style_title': 'Выберите стиль',
'choose_style_description': 'Подберите подходящий стиль для вашего стикера',
'create_sticker_title': 'Создайте стикер',
'create_sticker_description': 'Дождитесь генерации и сохраните результат',
// Тексты из OnboardingStickerPacks.tsx
'sticker_packs_title': 'Создавайте стикерпаки',
'start': 'Начать',
'select_stickers_title': 'Выберите стикеры',
'select_stickers_description': 'Отберите лучшие стикеры из вашей галереи',
'organize_pack_title': 'Организуйте набор',
'organize_pack_description': 'Расположите стикеры в нужном порядке',
'publish_title': 'Опубликуйте',
'publish_description': 'Создайте стикерпак в Telegram одним нажатием',
// Тексты из TermsAndConditions.tsx
'terms_title': 'Условия и Конфиденциальность',
'terms_description': 'Продолжая использовать это приложение, вы соглашаетесь с нашими Условиями использования и Политикой конфиденциальности.',
'terms_of_use': 'Условия использования',
'privacy_policy': 'Политика конфиденциальности',
'accept': 'Принять',
'decline': 'Отклонить',
// Тексты для NotificationModal
'continue': 'Продолжить',
'gallery': 'В галерею',
'generating': 'Генерация...',
'generated_sticker': 'Сгенерированный стикер',
// Тексты для useNotifications
'cancel': 'Отменить',
'close': 'Закрыть',
'connection_error': 'Возникли проблемы с подключением. Пожалуйста, проверьте интернет-соединение.\n\nЗапрос все еще обрабатывается, но это может занять больше времени, чем обычно.',
'feedback_thanks': 'Спасибо за обратную связь',
'feedback_sent': 'Ваше сообщение успешно отправлено',
'payment_success': 'Оплата успешна!',
'tokens_purchased': 'Вы успешно приобрели {0} токенов.',
// Тексты для UploadPhotoBlock
'change_photo': 'Изменить фото',
'upload_photo_block_title': 'Загрузите фото',
'upload_photo_block_subtitle': 'Перетащите или нажмите для выбора',
// Тексты для GridButtonsBlock
'show_more': 'Показать больше',
'collapse': 'Свернуть',
// Тексты для stylePresets (Чиби)
'preset_sportscar': 'Спорткар',
'preset_skateboard': 'Скейтборд',
'preset_coffee': 'Кофе',
'preset_flowers': 'Цветы',
'preset_balloon': 'Шарик',
'preset_book': 'Книга',
'preset_icecream': 'Мороженое',
'preset_umbrella': 'Зонт',
'preset_cocktail': 'Коктейль',
'preset_gift': 'Подарок',
'preset_dog': 'Собака',
'preset_newspaper': 'Газета',
'preset_bicycle': 'Велосипед',
'preset_surfer': 'Серфер',
'preset_detective': 'Детектив',
'preset_biker': 'Байкер',
'preset_fairy': 'Фея',
'preset_scientist': 'Ученый',
'preset_cowboy': 'Ковбой',
'preset_knight': 'Рыцарь',
'preset_ballerina': 'Балерина',
'preset_firefighter': 'Пожарный',
'preset_chef': 'Шеф-повар',
// Тексты для stylePresets (Реализм)
'preset_business': 'Деловой',
'preset_casual': 'Повседневный',
'preset_sport': 'Спортивный',
'preset_evening': 'Вечерний',
'preset_beach': 'Пляжный',
'preset_winter': 'Зимний',
'preset_retro': 'Ретро',
'preset_cyberpunk': 'Киберпанк',
'preset_military': 'Военный',
'preset_steampunk': 'Стимпанк',
// Тексты для stylePresets (Эмоции)
'preset_greeting': 'Привет',
'preset_thumbsup': 'Класс',
'preset_heart': 'Сердце',
'preset_laugh': 'Смех',
'preset_shock': 'Шок',
'preset_threat': 'Угроза',
'preset_hulk': 'Халк',
'preset_fire': 'Огонь',
'preset_tea': 'Чай',
'preset_dance': 'Танец',
'preset_trophy': 'Трофей',
'preset_teddybear': 'Мишка',
'preset_rose': 'Роза',
'preset_flowers_bouquet': 'Букет',
'preset_karaoke': 'Караоке',
'preset_zombie': 'Зомби',
'preset_homeless': 'Бездомный',
'preset_beer': 'Пиво',
'preset_boxing': 'Бокс',
'preset_kitten': 'Котёнок',
'preset_puppy': 'Щенок',
'preset_superhero': 'Супергерой',
'preset_wizard': 'Волшебник',
'preset_dragon': 'Дракон',
'preset_mage': 'Маг',
'preset_pirate': 'Пират',
'preset_samurai': 'Самурай',
'preset_wine': 'Вино',
'preset_slime': 'Слизь',
'preset_rider': 'Всадник',
'preset_drstrange': 'Доктор Стрэндж',
'preset_captainamerica': 'Капитан Америка',
// Тексты для GenerateButton
'start_generation': 'Начать генерацию',
'tokens_count': '{0} токенов',
// Тексты для Gallery
'gallery_title': 'Галерея стикеров',
'gallery_generating_section': 'В процессе генерации',
'gallery_loading': 'Загрузка изображений...',
'gallery_empty': 'У вас пока нет сгенерированных стикеров',
'gallery_refreshing': 'Обновление...',
'gallery_queue_position': 'В очереди: {0}',
'gallery_generating': 'Генерация...',
'gallery_time_left': 'Осталось: {0}',
'gallery_sticker_alt': 'Стикер {0}',
'gallery_create_pack': 'Создать стикерпак',
// Тексты для модального окна удаления
'gallery_delete_title': 'Удаление стикера',
'gallery_delete_message': 'Вы уверены, что хотите удалить этот стикер?',
'gallery_delete_cancel': 'Отмена',
'gallery_delete_confirm': 'Удалить',
// Тексты для расчета времени ожидания
'gallery_generation_started': 'Генерация началась',
'gallery_seconds': '{0} сек',
'gallery_minutes_seconds': '{0} мин {1} сек',
// Тексты для StickerPacks
'stickerpacks_title': 'Мои стикерпаки',
'stickerpacks_subtitle': 'Создавайте и публикуйте наборы стикеров в Telegram',
'stickerpacks_pack_title_prefix': 'Стикерпак',
'stickerpacks_pack_stats': '{0} / 49 стикеров',
'stickerpacks_loading': 'Загрузка стикерпаков...',
'stickerpacks_empty': 'У вас пока нет стикерпаков',
'stickerpacks_error': 'Не удалось загрузить список стикерпаков',
'stickerpacks_sticker_id_error': 'Не удалось определить ID стикера',
'stickerpacks_delete_sticker_error': 'Не удалось удалить стикер',
'stickerpacks_delete_pack_error': 'Не удалось удалить стикерпак',
'stickerpacks_create_button': 'Создать стикерпак',
'stickerpacks_retry_button': 'Повторить',
'stickerpacks_delete_button': 'Удалить',
'stickerpacks_back_button': '← Назад',
'stickerpacks_add_sticker_button': 'Добавить стикер',
'stickerpacks_open_telegram_button': 'Открыть в Telegram',
'stickerpacks_sticker_alt': 'Стикер {0}',
// Тексты для модальных окон удаления стикерпаков
'stickerpacks_delete_pack_title': 'Удаление стикерпака',
'stickerpacks_delete_pack_message': 'Вы уверены, что хотите удалить этот стикерпак?',
'stickerpacks_delete_sticker_title': 'Удаление стикера',
'stickerpacks_delete_sticker_message': 'Вы уверены, что хотите удалить этот стикер?',
'stickerpacks_cancel_button': 'Отмена',
'stickerpacks_confirm_delete_button': 'Удалить',
// Тексты для Profile
'profile_title': 'Профиль',
'profile_subtitle': 'Ваша статистика и настройки',
'profile_stickers_created': 'Стикеров создано',
'profile_sticker_packs': 'Стикерпаков',
'profile_tokens': 'Токенов',
'profile_payment_success': 'Оплата успешна!',
'profile_tokens_purchased': 'Вы успешно приобрели {0} токенов. Ваш текущий баланс: {1} токенов.',
'profile_close': 'Закрыть',
// Тексты для TokenPacksList
'token_packs_title': 'Пакеты токенов',
// Тексты для TokenPackCard
'token_pack_popular': 'Популярный выбор',
'token_pack_best_value': 'Максимальная выгода',
'token_pack_tokens': 'Токены',
'token_pack_bonus': '+{0} БОНУС',
'token_pack_stickers_count': '{0} стикеров',
'token_pack_buy': 'КУПИТЬ',
'token_pack_stars': 'Stars ⭐',
// Названия и описания пакетов токенов
'token_pack_basic_title': 'Стартовый набор стикеромана',
'token_pack_basic_description': 'Идеальный вариант для начала! Сгенерируйте 15 уникальных стикеров для вашего Telegram.',
'token_pack_optimal_title': 'Стикерный запас',
'token_pack_optimal_description': 'Создавайте стикеры для всех случаев жизни с оптимальным запасом токенов.',
'token_pack_advanced_title': 'Стикерный энтузиаст',
'token_pack_advanced_description': 'Расширьте свои возможности! Создавайте стикеры без ограничений.',
'token_pack_super_title': 'Стикерный магнат',
'token_pack_super_description': 'Для самых требовательных. Создавайте целые коллекции стикеров с максимальной выгодой.',
'token_pack_unlimited_title': 'Бог стикеров',
'token_pack_unlimited_description': 'Для профессионалов и настоящих ценителей. Неограниченные возможности для творчества с максимальной выгодой.',
// Для CropPhoto.tsx
'crop_photo_title': 'Обрезка фото',
'crop_photo_done': 'Готово',
'crop_photo_hint': 'Отрегулируйте масштаб и положение фото',
// Для TokenPacksModal.tsx
'token_modal_title': 'Недостаточно токенов для генерации',
'token_modal_show_all': 'Показать все пакеты',
// Для Navigation.tsx
'nav_home': 'Главная',
'nav_gallery': 'Галерея',
'nav_sticker_packs': 'Стикерпаки',
'nav_profile': 'Профиль',
// Для useGenerationState.ts
'generation_attention': 'Внимание',
'generation_duplicate_error': 'Нельзя отправить одну и ту же комбинацию изображения и образа подряд. Пожалуйста, измените изображение или выберите другой образ.',
'generation_title': 'Генерация стикера',
'generation_sending': 'Отправка запроса...',
'generation_connection_error': 'Возникли проблемы с подключением. Пожалуйста, проверьте интернет-соединение.',
'generation_invalid_prompt_title': 'Недопустимый промпт',
'generation_invalid_prompt_message': 'Промпт содержит недопустимый контент. Пожалуйста, используйте более нейтральные формулировки.',
'generation_started': 'Создание стикеров началось!',
'generation_queue_position': 'Позиция в очереди: {0}',
'generation_wait_time': 'Время ожидания: {0}',
'generation_result_available': 'Результат будет доступен в галерее после завершения генерации.',
'generation_error_title': 'Ошибка',
'generation_error_message': 'Не удалось начать генерацию',
// Для CreateSticker.tsx
'create_sticker_screen_title': 'Создание стикера',
'create_sticker_screen_subtitle': 'Загрузите фотографию для создания стикера',
'create_sticker_screen_upload_text': 'Нажмите чтобы выбрать фото',
'create_sticker_screen_upload_hint': 'или перетащите файл сюда',
'create_sticker_screen_cost': 'Стоимость: {0} токенов',
'create_sticker_screen_balance_error': 'Произошла ошибка при проверке баланса. Попробуйте позже.',
// Для форматирования времени
'time_minutes_short': 'мин',
'time_seconds_short': 'сек',
// Для компонентов с жестко закодированными строками
'create_first': 'Сначала создайте изображения в разделе "Создать стикер".',
'add_balance_title': 'Нажмите чтобы пополнить баланс',
'tokens_alt': 'Токены',
'feedback_placeholder': 'Опишите вашу проблему или предложение...',
'sticker_description_placeholder': 'Опишите, какой стикер вы хотите получить...',
'sticker_pack_name_label': 'Название стикерпака',
'fullscreen_view_alt': 'Полноэкранный просмотр',
'preview_alt': 'Предпросмотр',
'avatar_alt': 'Аватар',
// Для сообщений об ошибках в сервисах
'invalid_prompt': 'Недопустимый промпт',
'failed_to_generate': 'Не удалось сгенерировать изображение',
'failed_to_translate': 'Не удалось перевести текст',
// Для AddStickerToPackScreen.tsx
'back_button': '← Назад',
'add_stickers_to_pack_title': 'Добавление стикеров в "{0}"',
'loading_data': 'Загрузка данных...',
'select_images_for_stickers': 'Выберите изображения для стикеров',
'image_alt': 'Изображение {0}',
'stickers_not_selected': 'Стикеры не выбраны',
'select_at_least_one_image': 'Пожалуйста, выберите хотя бы одно изображение для добавления в стикерпак.',
'error': 'Ошибка',
'pack_name_not_specified': 'Не указано имя стикерпака.',
'failed_to_add_stickers': 'Не удалось добавить стикеры в стикерпак. Пожалуйста, попробуйте еще раз.',
'adding': 'Добавление...',
'add_stickers': 'Добавить стикеры',
'failed_to_load_data': 'Не удалось загрузить данные',
// Для FeedbackModal.tsx
'feedback_title': 'Обратная связь',
'feedback_subtitle': 'Расскажите нам о проблеме или предложении',
'add_image': 'Добавить изображение',
'loading_image': 'Загрузка...',
'preview_image_alt': 'Превью {0}',
'select_image_file': 'Пожалуйста, выберите файл изображения (JPEG, PNG и т.д.)',
'file_selection_error': 'Произошла ошибка при выборе файла. Пожалуйста, попробуйте еще раз.',
'enter_text_or_add_image': 'Пожалуйста, введите текст или добавьте изображение',
'failed_to_send_feedback': 'Не удалось отправить обратную связь. Пожалуйста, попробуйте позже.',
'error_sending_feedback': 'Произошла ошибка при отправке. Пожалуйста, попробуйте позже.',
// Для модальных окон валидации генерации
'select_style_warning_title': 'Внимание',
'select_style_warning_message': 'Выберите образ для генерации',
'upload_image_warning_title': 'Внимание',
'upload_image_warning_message': 'Сначала загрузите изображение',
'enter_prompt_warning_title': 'Внимание',
'enter_prompt_warning_message': 'Введите текст промпта',
// Для модального окна выбора эмодзи
'emoji_picker_title': 'Выберите эмодзи',
'emoji_picker_cancel': 'Отмена',
'emoji_picker_apply': 'Применить',
// Для экрана создания стикерпака
'create_sticker_pack_back': '← Назад',
'create_sticker_pack_title': 'Создание стикерпака',
'create_sticker_pack_select_images': 'Выберите изображения для стикеров',
'create_sticker_pack_loading': 'Загрузка изображений...',
'create_sticker_pack_image_alt': 'Изображение {0}',
'create_sticker_pack_creating': 'Создание...',
'create_sticker_pack_create': 'Создать стикерпак',
'create_sticker_pack_name_required_title': 'Название не указано',
'create_sticker_pack_name_required_message': 'Пожалуйста, введите название для вашего стикерпака.',
'create_sticker_pack_no_stickers_title': 'Стикеры не выбраны',
'create_sticker_pack_no_stickers_message': 'Пожалуйста, выберите хотя бы одно изображение для вашего стикерпака.',
'create_sticker_pack_name_occupied_title': 'Имя стикерпака уже занято',
'create_sticker_pack_name_occupied_message': 'Стикерпак с таким именем уже существует в Telegram. Пожалуйста, измените название и попробуйте снова.',
'create_sticker_pack_error_title': 'Ошибка при создании стикерпака',
'create_sticker_pack_error_message': 'Не удалось создать стикерпак. Пожалуйста, попробуйте еще раз с другим названием или изображениями.'
},
en: {
// Английские переводы
'feedback': 'Feedback',
'instruction': 'Instructions',
'shortsLoader': 'ShortsLoader',
'uploadPhoto': 'Upload a photo with a face',
'auto': 'Auto',
'man': 'Male',
'woman': 'Female',
'chooseStyle': 'Choose style',
'emotions': 'Emotions',
'chibi': 'Chibi',
'realism': 'Realism',
'chooseImage': 'Choose image',
'collection': 'Collection',
'memes': 'Memes',
'loading': 'Loading...',
'generate': 'Generate',
'meme1': 'Meme 1',
'meme2': 'Meme 2',
'meme3': 'Meme 3',
'meme4': 'Meme 4',
'meme5': 'Meme 5',
'meme6': 'Meme 6',
'meme7': 'Meme 7',
'meme8': 'Meme 8',
'meme9': 'Meme 9',
'meme10': 'Meme 10',
// Английские переводы для OnboardingWelcome
'welcome_title': 'Welcome to Sticker Generator',
'welcome_description': 'Create unique stickers from your photos using artificial intelligence',
'next': 'Next',
'skip': 'Skip',
// Английские переводы для OnboardingHowTo
'how_to_title': 'How to Create a Sticker',
'upload_photo_title': 'Upload a Photo',
'upload_photo_description': 'Choose a photo and crop it into a square',
'choose_style_title': 'Choose a Style',
'choose_style_description': 'Select a suitable style for your sticker',
'create_sticker_title': 'Create a Sticker',
'create_sticker_description': 'Wait for generation and save the result',
// Английские переводы для OnboardingStickerPacks
'sticker_packs_title': 'Create Sticker Packs',
'start': 'Start',
'select_stickers_title': 'Select Stickers',
'select_stickers_description': 'Choose the best stickers from your gallery',
'organize_pack_title': 'Organize Pack',
'organize_pack_description': 'Arrange stickers in the desired order',
'publish_title': 'Publish',
'publish_description': 'Create a Telegram sticker pack with one click',
// Английские переводы для TermsAndConditions
'terms_title': 'Terms and Privacy',
'terms_description': 'By continuing to use this application, you agree to our Terms of Use and Privacy Policy.',
'terms_of_use': 'Terms of Use',
'privacy_policy': 'Privacy Policy',
'accept': 'Accept',
'decline': 'Decline',
// Английские переводы для NotificationModal
'continue': 'Continue',
'gallery': 'To Gallery',
'generating': 'Generating...',
'generated_sticker': 'Generated sticker',
// Английские переводы для useNotifications
'cancel': 'Cancel',
'close': 'Close',
'connection_error': 'Connection problems detected. Please check your internet connection.\n\nThe request is still being processed, but it may take longer than usual.',
'feedback_thanks': 'Thank you for your feedback',
'feedback_sent': 'Your message has been successfully sent',
'payment_success': 'Payment successful!',
'tokens_purchased': 'You have successfully purchased {0} tokens.',
// Английские переводы для UploadPhotoBlock
'change_photo': 'Change Photo',
'upload_photo_block_title': 'Upload Photo',
'upload_photo_block_subtitle': 'Drag and drop or click to select',
// Английские переводы для GridButtonsBlock
'show_more': 'Show More',
'collapse': 'Collapse',
// Английские переводы для stylePresets (Чиби)
'preset_sportscar': 'Sports Car',
'preset_skateboard': 'Skateboard',
'preset_coffee': 'Coffee',
'preset_flowers': 'Flowers',
'preset_balloon': 'Balloon',
'preset_book': 'Book',
'preset_icecream': 'Ice Cream',
'preset_umbrella': 'Umbrella',
'preset_cocktail': 'Cocktail',
'preset_gift': 'Gift',
'preset_dog': 'Dog',
'preset_newspaper': 'Newspaper',
'preset_bicycle': 'Bicycle',
'preset_surfer': 'Surfer',
'preset_detective': 'Detective',
'preset_biker': 'Biker',
'preset_fairy': 'Fairy',
'preset_scientist': 'Scientist',
'preset_cowboy': 'Cowboy',
'preset_knight': 'Knight',
'preset_ballerina': 'Ballerina',
'preset_firefighter': 'Firefighter',
'preset_chef': 'Chef',
// Английские переводы для stylePresets (Реализм)
'preset_business': 'Business',
'preset_casual': 'Casual',
'preset_sport': 'Sport',
'preset_evening': 'Evening',
'preset_beach': 'Beach',
'preset_winter': 'Winter',
'preset_retro': 'Retro',
'preset_cyberpunk': 'Cyberpunk',
'preset_military': 'Military',
'preset_steampunk': 'Steampunk',
// Английские переводы для stylePresets (Эмоции)
'preset_greeting': 'Greeting',
'preset_thumbsup': 'Thumbs Up',
'preset_heart': 'Heart',
'preset_laugh': 'Laugh',
'preset_shock': 'Shock',
'preset_threat': 'Threat',
'preset_hulk': 'Hulk',
'preset_fire': 'Fire',
'preset_tea': 'Tea',
'preset_dance': 'Dance',
'preset_trophy': 'Trophy',
'preset_teddybear': 'Teddy Bear',
'preset_rose': 'Rose',
'preset_flowers_bouquet': 'Bouquet',
'preset_karaoke': 'Karaoke',
'preset_zombie': 'Zombie',
'preset_homeless': 'Homeless',
'preset_beer': 'Beer',
'preset_boxing': 'Boxing',
'preset_kitten': 'Kitten',
'preset_puppy': 'Puppy',
'preset_superhero': 'Superhero',
'preset_wizard': 'Wizard',
'preset_dragon': 'Dragon',
'preset_mage': 'Mage',
'preset_pirate': 'Pirate',
'preset_samurai': 'Samurai',
'preset_wine': 'Wine',
'preset_slime': 'Slime',
'preset_rider': 'Rider',
'preset_drstrange': 'Dr. Strange',
'preset_captainamerica': 'Captain America',
// Английские переводы для GenerateButton
'start_generation': 'Start Generation',
'tokens_count': '{0} tokens',
// Английские переводы для Gallery
'gallery_title': 'Sticker Gallery',
'gallery_generating_section': 'In Progress',
'gallery_loading': 'Loading images...',
'gallery_empty': 'You don\'t have any generated stickers yet',
'gallery_refreshing': 'Refreshing...',
'gallery_queue_position': 'In queue: {0}',
'gallery_generating': 'Generating...',
'gallery_time_left': 'Time left: {0}',
'gallery_sticker_alt': 'Sticker {0}',
'gallery_create_pack': 'Create Sticker Pack',
// Английские переводы для модального окна удаления
'gallery_delete_title': 'Delete Sticker',
'gallery_delete_message': 'Are you sure you want to delete this sticker?',
'gallery_delete_cancel': 'Cancel',
'gallery_delete_confirm': 'Delete',
// Английские переводы для расчета времени ожидания
'gallery_generation_started': 'Generation started',
'gallery_seconds': '{0} sec',
'gallery_minutes_seconds': '{0} min {1} sec',
// Английские переводы для StickerPacks
'stickerpacks_title': 'My Sticker Packs',
'stickerpacks_subtitle': 'Create and publish sticker sets in Telegram',
'stickerpacks_pack_title_prefix': 'Sticker Pack',
'stickerpacks_pack_stats': '{0} / 49 stickers',
'stickerpacks_loading': 'Loading sticker packs...',
'stickerpacks_empty': 'You don\'t have any sticker packs yet',
'stickerpacks_error': 'Failed to load sticker packs',
'stickerpacks_sticker_id_error': 'Failed to determine sticker ID',
'stickerpacks_delete_sticker_error': 'Failed to delete sticker',
'stickerpacks_delete_pack_error': 'Failed to delete sticker pack',
'stickerpacks_create_button': 'Create Sticker Pack',
'stickerpacks_retry_button': 'Retry',
'stickerpacks_delete_button': 'Delete',
'stickerpacks_back_button': '← Back',
'stickerpacks_add_sticker_button': 'Add Sticker',
'stickerpacks_open_telegram_button': 'Open in Telegram',
'stickerpacks_sticker_alt': 'Sticker {0}',
// Английские переводы для модальных окон удаления стикерпаков
'stickerpacks_delete_pack_title': 'Delete Sticker Pack',
'stickerpacks_delete_pack_message': 'Are you sure you want to delete this sticker pack?',
'stickerpacks_delete_sticker_title': 'Delete Sticker',
'stickerpacks_delete_sticker_message': 'Are you sure you want to delete this sticker?',
'stickerpacks_cancel_button': 'Cancel',
'stickerpacks_confirm_delete_button': 'Delete',
// Английские переводы для Profile
'profile_title': 'Profile',
'profile_subtitle': 'Your statistics and settings',
'profile_stickers_created': 'Stickers created',
'profile_sticker_packs': 'Sticker packs',
'profile_tokens': 'Tokens',
'profile_payment_success': 'Payment successful!',
'profile_tokens_purchased': 'You have successfully purchased {0} tokens. Your current balance: {1} tokens.',
'profile_close': 'Close',
// Английские переводы для TokenPacksList
'token_packs_title': 'Token Packages',
// Английские переводы для TokenPackCard
'token_pack_popular': 'Popular choice',
'token_pack_best_value': 'Best value',
'token_pack_tokens': 'Tokens',
'token_pack_bonus': '+{0} BONUS',
'token_pack_stickers_count': '{0} stickers',
'token_pack_buy': 'BUY',
'token_pack_stars': 'Stars ⭐',
// Названия и описания пакетов токенов (английские)
'token_pack_basic_title': 'Sticker Beginner Pack',
'token_pack_basic_description': 'Perfect for beginners! Generate 15 unique stickers for your Telegram.',
'token_pack_optimal_title': 'Sticker Supply',
'token_pack_optimal_description': 'Create stickers for all occasions with an optimal token supply.',
'token_pack_advanced_title': 'Sticker Enthusiast',
'token_pack_advanced_description': 'Expand your possibilities! Create stickers without limitations.',
'token_pack_super_title': 'Sticker Magnate',
'token_pack_super_description': 'For the most demanding users. Create entire collections of stickers with maximum value.',
'token_pack_unlimited_title': 'Sticker God',
'token_pack_unlimited_description': 'For professionals and true connoisseurs. Unlimited creative possibilities with maximum value.',
// Для CropPhoto.tsx (английские)
'crop_photo_title': 'Crop Photo',
'crop_photo_done': 'Done',
'crop_photo_hint': 'Adjust the scale and position of the photo',
// Для TokenPacksModal.tsx (английские)
'token_modal_title': 'Not enough tokens for generation',
'token_modal_show_all': 'Show all packages',
// Для Navigation.tsx (английские)
'nav_home': 'Home',
'nav_gallery': 'Gallery',
'nav_sticker_packs': 'Sticker Packs',
'nav_profile': 'Profile',
// Для useGenerationState.ts (английские)
'generation_attention': 'Attention',
'generation_duplicate_error': 'You cannot send the same combination of image and style in a row. Please change the image or select a different style.',
'generation_title': 'Sticker Generation',
'generation_sending': 'Sending request...',
'generation_connection_error': 'Connection problems detected. Please check your internet connection.',
'generation_invalid_prompt_title': 'Invalid Prompt',
'generation_invalid_prompt_message': 'The prompt contains inappropriate content. Please use more neutral wording.',
'generation_started': 'Sticker creation has started!',
'generation_queue_position': 'Queue position: {0}',
'generation_wait_time': 'Estimated wait time: {0}',
'generation_result_available': 'The result will be available in the gallery after generation is complete.',
'generation_error_title': 'Error',
'generation_error_message': 'Failed to start generation',
// Для CreateSticker.tsx (английские)
'create_sticker_screen_title': 'Create Sticker',
'create_sticker_screen_subtitle': 'Upload a photo to create a sticker',
'create_sticker_screen_upload_text': 'Click to select a photo',
'create_sticker_screen_upload_hint': 'or drag and drop a file here',
'create_sticker_screen_cost': 'Cost: {0} tokens',
'create_sticker_screen_balance_error': 'An error occurred while checking your balance. Please try again later.',
// Для форматирования времени (английские)
'time_minutes_short': 'min',
'time_seconds_short': 'sec',
// Для компонентов с жестко закодированными строками (английские)
'create_first': 'First create images in the "Create Sticker" section.',
'add_balance_title': 'Click to add balance',
'tokens_alt': 'Tokens',
'feedback_placeholder': 'Describe your issue or suggestion...',
'sticker_description_placeholder': 'Describe what kind of sticker you want to get...',
'sticker_pack_name_label': 'Sticker pack name',
'fullscreen_view_alt': 'Fullscreen view',
'preview_alt': 'Preview',
'avatar_alt': 'Avatar',
// Для сообщений об ошибках в сервисах (английские)
'invalid_prompt': 'Invalid prompt',
'failed_to_generate': 'Failed to generate image',
'failed_to_translate': 'Failed to translate text',
// Для AddStickerToPackScreen.tsx (английские)
'back_button': '← Back',
'add_stickers_to_pack_title': 'Add stickers to "{0}"',
'loading_data': 'Loading data...',
'select_images_for_stickers': 'Select images for stickers',
'image_alt': 'Image {0}',
'stickers_not_selected': 'No stickers selected',
'select_at_least_one_image': 'Please select at least one image to add to the sticker pack.',
'error': 'Error',
'pack_name_not_specified': 'Sticker pack name not specified.',
'failed_to_add_stickers': 'Failed to add stickers to the sticker pack. Please try again.',
'adding': 'Adding...',
'add_stickers': 'Add stickers',
'failed_to_load_data': 'Failed to load data',
// Для FeedbackModal.tsx (английские)
'feedback_title': 'Feedback',
'feedback_subtitle': 'Tell us about your issue or suggestion',
'add_image': 'Add image',
'loading_image': 'Loading...',
'preview_image_alt': 'Preview {0}',
'select_image_file': 'Please select an image file (JPEG, PNG, etc.)',
'file_selection_error': 'An error occurred while selecting the file. Please try again.',
'enter_text_or_add_image': 'Please enter text or add an image',
'failed_to_send_feedback': 'Failed to send feedback. Please try again later.',
'error_sending_feedback': 'An error occurred while sending. Please try again later.',
// Для модальных окон валидации генерации (английские)
'select_style_warning_title': 'Attention',
'select_style_warning_message': 'Please select a style for generation',
'upload_image_warning_title': 'Attention',
'upload_image_warning_message': 'Please upload an image first',
'enter_prompt_warning_title': 'Attention',
'enter_prompt_warning_message': 'Please enter prompt text',
// Для модального окна выбора эмодзи (английские)
'emoji_picker_title': 'Choose emoji',
'emoji_picker_cancel': 'Cancel',
'emoji_picker_apply': 'Apply',
// Для экрана создания стикерпака (английские)
'create_sticker_pack_back': '← Back',
'create_sticker_pack_title': 'Create Sticker Pack',
'create_sticker_pack_select_images': 'Select images for stickers',
'create_sticker_pack_loading': 'Loading images...',
'create_sticker_pack_image_alt': 'Image {0}',
'create_sticker_pack_creating': 'Creating...',
'create_sticker_pack_create': 'Create Sticker Pack',
'create_sticker_pack_name_required_title': 'Name Required',
'create_sticker_pack_name_required_message': 'Please enter a name for your sticker pack.',
'create_sticker_pack_no_stickers_title': 'No Stickers Selected',
'create_sticker_pack_no_stickers_message': 'Please select at least one image for your sticker pack.',
'create_sticker_pack_name_occupied_title': 'Sticker Pack Name Already Taken',
'create_sticker_pack_name_occupied_message': 'A sticker pack with this name already exists in Telegram. Please change the name and try again.',
'create_sticker_pack_error_title': 'Error Creating Sticker Pack',
'create_sticker_pack_error_message': 'Failed to create sticker pack. Please try again with a different name or images.'
}
};
/**
* Функция для форматирования строки с параметрами
* @param str Строка с плейсхолдерами {0}, {1}, ...
* @param args Аргументы для замены плейсхолдеров
* @returns Отформатированная строка
*/
export const formatString = (str: string, ...args: any[]): string => {
return str.replace(/{(\d+)}/g, (match, index) => {
return typeof args[index] !== 'undefined' ? args[index] : match;
});
};
/**
* Функция для получения языка пользователя
* @returns Код языка ('ru' или 'en')
*/
export const getUserLanguage = (): 'ru' | 'en' => {
try {
const userInfo = getUserInfo();
// Если язык пользователя русский, возвращаем 'ru', иначе 'en'
return userInfo.language_code === 'ru' ? 'ru' : 'en';
} catch (error) {
console.error('Ошибка при получении языка пользователя:', error);
// По умолчанию возвращаем русский язык
return 'en';
}
};
/**
* Функция для получения перевода по ключу
* @param key Ключ перевода
* @param args Аргументы для форматирования строки (опционально)
* @returns Переведенный текст
*/
export const getTranslation = (key: string, ...args: any[]): string => {
const language = getUserLanguage();
const translation = translations[language][key] || translations['ru'][key] || key;
if (args.length > 0) {
return formatString(translation, ...args);
}
return translation;
};
/**
* Хук для использования переводов в компонентах (для удобства)
* @returns Объект с функцией перевода и текущим языком
*/
export const useTranslation = () => {
const language = getUserLanguage();
return {
t: (key: string, ...args: any[]) => getTranslation(key, ...args),
language
};
};

View File

@ -7,6 +7,7 @@ import { useBalance } from '../contexts/BalanceContext';
import { WorkflowType } from '../constants/workflows';
import customAnalyticsService from '../services/customAnalyticsService';
import { getCurrentUserId } from '../constants/user';
import { getTranslation } from '../constants/translations';
/**
* Хук для управления состоянием генерации
@ -194,8 +195,8 @@ export const useGenerationState = (
if (isSame) {
showNotification(
'Внимание',
'Нельзя отправить одну и ту же комбинацию изображения и образа подряд. Пожалуйста, измените изображение или выберите другой образ.',
getTranslation('generation_attention'),
getTranslation('generation_duplicate_error'),
{ showGalleryButton: false }
);
return;
@ -219,8 +220,8 @@ export const useGenerationState = (
// Показываем уведомление о начале генерации
showNotification(
'Генерация стикера',
'Отправка запроса...',
getTranslation('generation_title'),
getTranslation('generation_sending'),
{ isLoading: true, showButtons: false }
);
@ -244,8 +245,8 @@ export const useGenerationState = (
// Вызываем handleRequestTimeout из useNotifications через showNotification
showNotification(
'Генерация стикера',
'Возникли проблемы с подключением. Пожалуйста, проверьте интернет-соединение.',
getTranslation('generation_title'),
getTranslation('generation_connection_error'),
{ isLoading: true, showButtons: false }
);
}, 6000);
@ -310,8 +311,8 @@ export const useGenerationState = (
setIsGenerating(false);
showNotification(
'Недопустимый промпт',
'Промпт содержит недопустимый контент. Пожалуйста, используйте более нейтральные формулировки.',
getTranslation('generation_invalid_prompt_title'),
getTranslation('generation_invalid_prompt_message'),
{ isLoading: false, showGalleryButton: false, showButtons: true }
);
@ -343,23 +344,23 @@ export const useGenerationState = (
const minutes = Math.floor(estimatedTime / 60);
const seconds = estimatedTime % 60;
const timeString = minutes > 0
? `${minutes} мин ${seconds} сек`
: `${seconds} сек`;
? `${minutes} ${getTranslation('time_minutes_short')} ${seconds} ${getTranslation('time_seconds_short')}`
: `${seconds} ${getTranslation('time_seconds_short')}`;
// Форматируем сообщение
showNotification(
'Генерация стикера',
`Создание стикеров началось!\n` +
`Позиция в очереди: ${result.queue_position}\n` +
`Время ожидания: ${timeString}\n\n` +
`Результат будет доступен в галерее после завершения генерации.`,
getTranslation('generation_title'),
`${getTranslation('generation_started')}\n` +
`${getTranslation('generation_queue_position', result.queue_position)}\n` +
`${getTranslation('generation_wait_time', timeString)}\n\n` +
`${getTranslation('generation_result_available')}`,
{ isLoading: false, showButtons: true }
);
} else {
showNotification(
'Генерация стикера',
`Создание стикеров началось!\n\n` +
`Результат будет доступен в галерее после завершения генерации.`,
getTranslation('generation_title'),
`${getTranslation('generation_started')}\n\n` +
`${getTranslation('generation_result_available')}`,
{ isLoading: false, showButtons: true }
);
}
@ -388,8 +389,8 @@ export const useGenerationState = (
setIsGenerating(false);
showNotification(
'Ошибка',
'Не удалось начать генерацию',
getTranslation('generation_error_title'),
getTranslation('generation_error_message'),
{ isLoading: false, showGalleryButton: false, showButtons: true }
);

View File

@ -1,6 +1,7 @@
import { useState, useCallback } from 'react';
import { NotificationState } from '../types/generation';
import { useNavigate } from 'react-router-dom';
import { getTranslation } from '../constants/translations';
/**
* Хук для управления уведомлениями
@ -15,7 +16,7 @@ export const useNotifications = () => {
const [isLoading, setIsLoading] = useState(false);
const [showGalleryButton, setShowGalleryButton] = useState(true);
const [showButtons, setShowButtons] = useState(true);
const [continueButtonText, setContinueButtonText] = useState('Продолжить');
const [continueButtonText, setContinueButtonText] = useState(getTranslation('continue'));
// Состояния для модального окна с токенами
const [showTokensModal, setShowTokensModal] = useState(false);
@ -26,13 +27,10 @@ export const useNotifications = () => {
* Функция для обработки таймаута запроса
*/
const handleRequestTimeout = useCallback(() => {
setNotificationMessage(
'Возникли проблемы с подключением. Пожалуйста, проверьте интернет-соединение.\n\n' +
'Запрос все еще обрабатывается, но это может занять больше времени, чем обычно.'
);
setNotificationMessage(getTranslation('connection_error'));
// Добавляем кнопку отмены, чтобы пользователь мог прервать запрос
setShowButtons(true);
setContinueButtonText('Отменить');
setContinueButtonText(getTranslation('cancel'));
}, []);
/**
@ -44,7 +42,7 @@ export const useNotifications = () => {
setIsLoading(false);
setShowGalleryButton(false);
setShowButtons(true);
setContinueButtonText('Закрыть');
setContinueButtonText(getTranslation('close'));
setIsNotificationVisible(true);
}, []);
@ -62,7 +60,7 @@ export const useNotifications = () => {
setIsLoading(options?.isLoading ?? false);
setShowGalleryButton(options?.showGalleryButton ?? true);
setShowButtons(options?.showButtons ?? true);
setContinueButtonText(options?.continueButtonText ?? 'Продолжить');
setContinueButtonText(options?.continueButtonText ?? getTranslation('continue'));
setIsNotificationVisible(true);
}, []);
@ -95,13 +93,13 @@ export const useNotifications = () => {
*/
const showFeedbackSentNotification = useCallback(() => {
showNotification(
'Спасибо за обратную связь',
'Ваше сообщение успешно отправлено',
getTranslation('feedback_thanks'),
getTranslation('feedback_sent'),
{
isLoading: false,
showGalleryButton: false,
showButtons: true,
continueButtonText: 'Закрыть'
continueButtonText: getTranslation('close')
}
);
}, [showNotification]);
@ -111,13 +109,13 @@ export const useNotifications = () => {
*/
const showPaymentSuccessNotification = useCallback((tokens: number, bonusTokens: number) => {
showNotification(
'Оплата успешна!',
`Вы успешно приобрели ${tokens + bonusTokens} токенов.`,
getTranslation('payment_success'),
getTranslation('tokens_purchased', tokens + bonusTokens),
{
isLoading: false,
showGalleryButton: false,
showButtons: true,
continueButtonText: 'Закрыть'
continueButtonText: getTranslation('close')
}
);
}, [showNotification]);

View File

@ -7,6 +7,7 @@ import { GeneratedImage } from '../types/api';
import { getCurrentUserId } from '../constants/user';
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
import ValidationModal from '../components/shared/ValidationModal';
import { getTranslation } from '../constants/translations';
const AddStickerToPackScreen: React.FC = () => {
const navigate = useNavigate();
@ -47,7 +48,7 @@ const AddStickerToPackScreen: React.FC = () => {
setError(null);
} catch (err) {
console.error('Ошибка при загрузке данных:', err);
setError('Не удалось загрузить данные');
setError(getTranslation('failed_to_load_data'));
} finally {
setLoading(false);
}
@ -114,15 +115,15 @@ const AddStickerToPackScreen: React.FC = () => {
// Обработчик добавления стикеров
const handleAddStickers = async () => {
if (selectedImages.length === 0) {
setValidationTitle('Стикеры не выбраны');
setValidationMessage('Пожалуйста, выберите хотя бы одно изображение для добавления в стикерпак.');
setValidationTitle(getTranslation('stickers_not_selected'));
setValidationMessage(getTranslation('select_at_least_one_image'));
setIsValidationModalVisible(true);
return;
}
if (!packName) {
setValidationTitle('Ошибка');
setValidationMessage('Не указано имя стикерпака.');
setValidationTitle(getTranslation('error'));
setValidationMessage(getTranslation('pack_name_not_specified'));
setIsValidationModalVisible(true);
return;
}
@ -156,8 +157,8 @@ const AddStickerToPackScreen: React.FC = () => {
navigate('/packs');
} catch (err) {
console.error('Ошибка при добавлении стикеров:', err);
setValidationTitle('Ошибка');
setValidationMessage('Не удалось добавить стикеры в стикерпак. Пожалуйста, попробуйте еще раз.');
setValidationTitle(getTranslation('error'));
setValidationMessage(getTranslation('failed_to_add_stickers'));
setIsValidationModalVisible(true);
} finally {
setAdding(false);
@ -171,17 +172,17 @@ const AddStickerToPackScreen: React.FC = () => {
className={styles.backButton}
onClick={() => navigate('/packs')}
>
Назад
{getTranslation('back_button')}
</button>
<h1 className={styles.title}>
Добавление стикеров в "{packTitle || packName}"
{getTranslation('add_stickers_to_pack_title', packTitle || packName)}
</h1>
</div>
<div className={styles.content}>
{loading && (
<div className={styles.loading}>
<p>Загрузка данных...</p>
<p>{getTranslation('loading_data')}</p>
</div>
)}
@ -194,12 +195,11 @@ const AddStickerToPackScreen: React.FC = () => {
{!loading && (
<>
<div className={styles.section}>
<h2 className={styles.sectionTitle}>Выберите изображения для стикеров</h2>
<h2 className={styles.sectionTitle}>{getTranslation('select_images_for_stickers')}</h2>
{availableImages.length === 0 ? (
<p className={styles.noImages}>
У вас пока нет сгенерированных изображений.
Сначала создайте изображения в разделе "Создать стикер".
{getTranslation('create_first')}
</p>
) : (
<div className={styles.imagesGrid}>
@ -215,7 +215,7 @@ const AddStickerToPackScreen: React.FC = () => {
>
<img
src={image.url || ''}
alt={`Изображение ${index + 1}`}
alt={getTranslation('image_alt', index + 1)}
className={styles.image}
/>
@ -245,7 +245,7 @@ const AddStickerToPackScreen: React.FC = () => {
onClick={handleAddStickers}
disabled={adding}
>
{adding ? 'Добавление...' : 'Добавить стикеры'}
{adding ? getTranslation('adding') : getTranslation('add_stickers')}
</button>
</div>

View File

@ -7,6 +7,7 @@ import { paymentService } from '../services/paymentService';
import apiService from '../services/api';
import { getCurrentUserId } from '../constants/user';
import NotificationModal from '../components/shared/NotificationModal';
import { getTranslation } from '../constants/translations';
const TOKENS_PER_GENERATION = 10;
@ -33,7 +34,7 @@ const CreateSticker: React.FC = () => {
return true;
} catch (error) {
console.error('Error checking balance:', error);
alert('Произошла ошибка при проверке баланса. Попробуйте позже.');
alert(getTranslation('create_sticker_screen_balance_error'));
return false;
}
};
@ -50,10 +51,10 @@ const CreateSticker: React.FC = () => {
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>
Создание стикера
{getTranslation('create_sticker_screen_title')}
</h1>
<p className={styles.subtitle}>
Загрузите фотографию для создания стикера
{getTranslation('create_sticker_screen_subtitle')}
</p>
</div>
@ -64,13 +65,13 @@ const CreateSticker: React.FC = () => {
<div className={styles.uploadBox}>
<span className={styles.uploadIcon}>📷</span>
<span className={styles.uploadText}>
Нажмите чтобы выбрать фото
{getTranslation('create_sticker_screen_upload_text')}
</span>
<span className={styles.uploadHint}>
или перетащите файл сюда
{getTranslation('create_sticker_screen_upload_hint')}
</span>
<span className={styles.tokenCost}>
Стоимость: {TOKENS_PER_GENERATION} токенов
{getTranslation('create_sticker_screen_cost', TOKENS_PER_GENERATION)}
</span>
</div>
</div>
@ -91,8 +92,8 @@ const CreateSticker: React.FC = () => {
if (userData) {
// Обновляем данные на основе полученной информации
// Показываем модальное окно с информацией об успешной оплате
setNotificationTitle('Оплата успешна!');
setNotificationMessage(`Вы успешно приобрели ${pack.tokens + pack.bonusTokens} токенов.`);
setNotificationTitle(getTranslation('payment_success'));
setNotificationMessage(getTranslation('tokens_purchased', pack.tokens + pack.bonusTokens));
setShowNotificationModal(true);
} else {
// Если данные не получены, делаем запрос на получение данных пользователя
@ -101,8 +102,8 @@ const CreateSticker: React.FC = () => {
const balance = await apiService.getBalance(getCurrentUserId());
// Показываем модальное окно с информацией об успешной оплате
setNotificationTitle('Оплата успешна!');
setNotificationMessage(`Вы успешно приобрели ${pack.tokens + pack.bonusTokens} токенов. Ваш текущий баланс: ${balance} токенов.`);
setNotificationTitle(getTranslation('payment_success'));
setNotificationMessage(getTranslation('profile_tokens_purchased', pack.tokens + pack.bonusTokens, balance));
setShowNotificationModal(true);
} catch (error) {
console.error('Ошибка при обновлении данных пользователя:', error);
@ -120,7 +121,7 @@ const CreateSticker: React.FC = () => {
onGalleryClick={() => setShowNotificationModal(false)}
onContinueClick={() => setShowNotificationModal(false)}
showGalleryButton={false}
continueButtonText="Закрыть"
continueButtonText={getTranslation('close')}
/>
</div>
);

View File

@ -8,6 +8,7 @@ import { getCurrentUserId } from '../constants/user';
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
import ValidationModal from '../components/shared/ValidationModal';
import customAnalyticsService from '../services/customAnalyticsService';
import { getTranslation } from '../constants/translations';
/**
* Транслитерирует кириллический текст в латиницу.
@ -160,15 +161,15 @@ const CreateStickerPack: React.FC = () => {
// Обработчик создания стикерпака
const handleCreateStickerPack = async () => {
if (!title.trim()) {
setValidationTitle('Название не указано');
setValidationMessage('Пожалуйста, введите название для вашего стикерпака.');
setValidationTitle(getTranslation('create_sticker_pack_name_required_title'));
setValidationMessage(getTranslation('create_sticker_pack_name_required_message'));
setIsValidationModalVisible(true);
return;
}
if (selectedImages.length === 0) {
setValidationTitle('Стикеры не выбраны');
setValidationMessage('Пожалуйста, выберите хотя бы одно изображение для вашего стикерпака.');
setValidationTitle(getTranslation('create_sticker_pack_no_stickers_title'));
setValidationMessage(getTranslation('create_sticker_pack_no_stickers_message'));
setIsValidationModalVisible(true);
return;
}
@ -228,11 +229,11 @@ const CreateStickerPack: React.FC = () => {
// Проверяем, содержит ли сообщение об ошибке информацию о занятом имени
if (errorMessage.includes('sticker set name is already occupied')) {
setValidationTitle('Имя стикерпака уже занято');
setValidationMessage('Стикерпак с таким именем уже существует в Telegram. Пожалуйста, измените название и попробуйте снова.');
setValidationTitle(getTranslation('create_sticker_pack_name_occupied_title'));
setValidationMessage(getTranslation('create_sticker_pack_name_occupied_message'));
} else {
setValidationTitle('Ошибка при создании стикерпака');
setValidationMessage('Не удалось создать стикерпак. Пожалуйста, попробуйте еще раз с другим названием или изображениями.');
setValidationTitle(getTranslation('create_sticker_pack_error_title'));
setValidationMessage(getTranslation('create_sticker_pack_error_message'));
}
setIsValidationModalVisible(true);
@ -248,22 +249,22 @@ const CreateStickerPack: React.FC = () => {
className={styles.backButton}
onClick={() => navigate('/packs')}
>
Назад
{getTranslation('create_sticker_pack_back')}
</button>
<h1 className={styles.title}>Создание стикерпака</h1>
<h1 className={styles.title}>{getTranslation('create_sticker_pack_title')}</h1>
</div>
<div className={styles.content}>
<div className={styles.form}>
<div className={styles.formGroup}>
<label htmlFor="title" className={styles.label}>Название стикерпака</label>
<label htmlFor="title" className={styles.label}>{getTranslation('sticker_pack_name_label')}</label>
<input
id="title"
type="text"
className={styles.input}
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Введите название стикерпака"
placeholder={getTranslation('sticker_pack_name_label')}
ref={titleInputRef}
onKeyDown={handleTitleKeyDown}
enterKeyHint="done"
@ -271,14 +272,13 @@ const CreateStickerPack: React.FC = () => {
</div>
<div className={styles.formGroup}>
<label className={styles.label}>Выберите изображения для стикеров</label>
<label className={styles.label}>{getTranslation('create_sticker_pack_select_images')}</label>
{loading && <p className={styles.loading}>Загрузка изображений...</p>}
{loading && <p className={styles.loading}>{getTranslation('create_sticker_pack_loading')}</p>}
{!loading && availableImages.length === 0 && (
<p className={styles.noImages}>
У вас пока нет сгенерированных изображений.
Сначала создайте изображения в разделе "Создать стикер".
{getTranslation('create_first')}
</p>
)}
@ -296,7 +296,7 @@ const CreateStickerPack: React.FC = () => {
>
<img
src={image.url || ''}
alt={`Изображение ${index + 1}`}
alt={getTranslation('create_sticker_pack_image_alt', index + 1)}
className={styles.image}
/>
@ -327,7 +327,7 @@ const CreateStickerPack: React.FC = () => {
onClick={handleCreateStickerPack}
disabled={creating}
>
{creating ? 'Создание...' : 'Создать стикерпак'}
{creating ? getTranslation('create_sticker_pack_creating') : getTranslation('create_sticker_pack_create')}
</button>
</div>

View File

@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import styles from './CropPhoto.module.css';
import { getTranslation } from '../constants/translations';
const CropPhoto: React.FC = () => {
const location = useLocation();
@ -283,9 +284,9 @@ const CropPhoto: React.FC = () => {
<button className={styles.backButton} onClick={() => navigate('/')}>
</button>
<h1 className={styles.title}>Обрезка фото</h1>
<h1 className={styles.title}>{getTranslation('crop_photo_title')}</h1>
<button className={styles.confirmButton} onClick={handleConfirm}>
Готово
{getTranslation('crop_photo_done')}
</button>
</div>
@ -337,7 +338,7 @@ const CropPhoto: React.FC = () => {
className={styles.zoomSlider}
/>
<div className={styles.hint}>
Отрегулируйте масштаб и положение фото
{getTranslation('crop_photo_hint')}
</div>
</div>
</div>

View File

@ -8,6 +8,7 @@ import ImageWithFallback from '../components/shared/ImageWithFallback';
import NotificationModal from '../components/shared/NotificationModal';
import customAnalyticsService from '../services/customAnalyticsService';
import { getCurrentUserId } from '../constants/user';
import { getTranslation } from '../constants/translations';
const GalleryScreen: React.FC = () => {
const navigate = useNavigate();
@ -31,11 +32,11 @@ const GalleryScreen: React.FC = () => {
// Функция для расчета времени ожидания
const getEstimatedWaitTime = (queuePosition: number | null): string => {
if (queuePosition === null) return 'Генерация началась';
if (queuePosition === null) return getTranslation('gallery_generation_started');
const seconds = apiService.calculateEstimatedWaitTime(queuePosition);
if (seconds < 60) return `${seconds} сек`;
return `${Math.floor(seconds / 60)} мин ${seconds % 60} сек`;
if (seconds < 60) return getTranslation('gallery_seconds', seconds);
return getTranslation('gallery_minutes_seconds', Math.floor(seconds / 60), seconds % 60);
};
// Обработчики для режима удаления
@ -268,7 +269,7 @@ const GalleryScreen: React.FC = () => {
style={{ '--pull-distance': `${pullDistance}px` } as React.CSSProperties}
>
<div className={styles.refreshSpinner}></div>
{refreshing && <span>Обновление...</span>}
{refreshing && <span>{getTranslation('gallery_refreshing')}</span>}
</div>
<div
@ -281,13 +282,13 @@ const GalleryScreen: React.FC = () => {
>
<div className={styles.header}>
<h1 className={styles.title}>
Галерея стикеров
{getTranslation('gallery_title')}
</h1>
</div>
{loading && (
<div className={styles.placeholder}>
Загрузка изображений...
{getTranslation('gallery_loading')}
</div>
)}
@ -299,13 +300,13 @@ const GalleryScreen: React.FC = () => {
{!loading && !error && images.length === 0 && pendingTasks.length === 0 && (
<div className={styles.placeholder}>
У вас пока нет сгенерированных стикеров
{getTranslation('gallery_empty')}
</div>
)}
{!loading && !loadingPendingTasks && pendingTasks.length > 0 && (
<div className={styles.pendingTasksSection}>
<h2 className={styles.sectionTitle}>В процессе генерации</h2>
<h2 className={styles.sectionTitle}>{getTranslation('gallery_generating_section')}</h2>
<div className={styles.pendingTasksGrid}>
{pendingTasks.map((task) => (
<div key={task.task_id} className={styles.pendingTaskItem}>
@ -315,11 +316,11 @@ const GalleryScreen: React.FC = () => {
<div className={styles.pendingTaskInfo}>
<p className={styles.pendingTaskStatus}>
{task.status === 'PENDING'
? `В очереди: ${task.queue_position}`
: 'Генерация...'}
? getTranslation('gallery_queue_position', task.queue_position)
: getTranslation('gallery_generating')}
</p>
<p className={styles.pendingTaskTime}>
Осталось: {getEstimatedWaitTime(task.queue_position)}
{getTranslation('gallery_time_left', getEstimatedWaitTime(task.queue_position))}
</p>
</div>
</div>
@ -343,7 +344,7 @@ const GalleryScreen: React.FC = () => {
>
<ImageWithFallback
src={image.url || ''}
alt={`Стикер ${index + 1}`}
alt={getTranslation('gallery_sticker_alt', index + 1)}
className={styles.image}
onClick={() => !isDeleteMode && image.url && setSelectedImage(image.url)}
onContextMenu={(e: React.MouseEvent<HTMLDivElement>) => e.preventDefault()}
@ -376,12 +377,12 @@ const GalleryScreen: React.FC = () => {
{/* Модальное окно подтверждения удаления */}
<NotificationModal
isVisible={!!selectedForDelete}
title="Удаление стикера"
message="Вы уверены, что хотите удалить этот стикер?"
title={getTranslation('gallery_delete_title')}
message={getTranslation('gallery_delete_message')}
isLoading={isDeleting}
showGalleryButton={true}
galleryButtonText="Отмена"
continueButtonText="Удалить"
galleryButtonText={getTranslation('gallery_delete_cancel')}
continueButtonText={getTranslation('gallery_delete_confirm')}
onContinueClick={handleConfirmDelete}
onGalleryClick={() => setSelectedForDelete(null)}
isPrimaryGalleryButton={false}
@ -393,7 +394,7 @@ const GalleryScreen: React.FC = () => {
className={styles.createButtonFixed}
onClick={handleCreateStickerPack}
>
Создать стикерпак
{getTranslation('gallery_create_pack')}
</button>
)}
</div>

View File

@ -7,6 +7,7 @@ import TokenPacksList from '../components/tokens/TokenPacksList';
import { tokenPacks, TokenPack } from '../constants/tokenPacks';
import { paymentService } from '../services/paymentService';
import NotificationModal from '../components/shared/NotificationModal';
import { getTranslation } from '../constants/translations';
const Profile: React.FC = () => {
const [stickersCount, setStickersCount] = useState<number>(0);
@ -83,22 +84,22 @@ const Profile: React.FC = () => {
return (
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>Профиль</h1>
<p className={styles.subtitle}>Ваша статистика и настройки</p>
<h1 className={styles.title}>{getTranslation('profile_title')}</h1>
<p className={styles.subtitle}>{getTranslation('profile_subtitle')}</p>
</div>
<div className={styles.compactStats}>
<div className={styles.statItem}>
<span className={styles.statValue}>{loading ? '...' : stickersCount}</span>
<span className={styles.statLabel}>Стикеров создано</span>
<span className={styles.statLabel}>{getTranslation('profile_stickers_created')}</span>
</div>
<div className={styles.statItem}>
<span className={styles.statValue}>{loading ? '...' : packsCount}</span>
<span className={styles.statLabel}>Стикерпаков</span>
<span className={styles.statLabel}>{getTranslation('profile_sticker_packs')}</span>
</div>
<div className={styles.statItem}>
<span className={styles.statValue}>{loading ? '...' : userBalance}</span>
<span className={styles.statLabel}>Токенов</span>
<span className={styles.statLabel}>{getTranslation('profile_tokens')}</span>
</div>
</div>
@ -111,12 +112,14 @@ const Profile: React.FC = () => {
{/* Модальное окно успешной оплаты */}
<NotificationModal
isVisible={showPaymentSuccessModal}
title="Оплата успешна!"
message={lastPurchasedPack ? `Вы успешно приобрели ${lastPurchasedPack.tokens + lastPurchasedPack.bonusTokens} токенов. Ваш текущий баланс: ${userBalance} токенов.` : "Оплата прошла успешно!"}
title={getTranslation('profile_payment_success')}
message={lastPurchasedPack
? getTranslation('profile_tokens_purchased', lastPurchasedPack.tokens + lastPurchasedPack.bonusTokens, userBalance)
: getTranslation('profile_payment_success')}
onGalleryClick={handleClosePaymentSuccessModal}
onContinueClick={handleClosePaymentSuccessModal}
showGalleryButton={false}
continueButtonText="Закрыть"
continueButtonText={getTranslation('profile_close')}
/>
</div>
);

View File

@ -5,6 +5,7 @@ import { stickerService } from '../services/stickerService';
import { getCurrentUserId } from '../constants/user';
import NotificationModal from '../components/shared/NotificationModal';
import customAnalyticsService from '../services/customAnalyticsService';
import { getTranslation } from '../constants/translations';
// Функция для удаления дописанной части из названия стикерпака
const cleanPackTitle = (title: string): string => {
@ -61,7 +62,7 @@ const StickerPacks: React.FC = () => {
setError(null);
} catch (err) {
console.error('Ошибка при загрузке стикерпаков:', err);
setError('Не удалось загрузить список стикерпаков');
setError(getTranslation('stickerpacks_error'));
} finally {
setLoading(false);
}
@ -114,7 +115,7 @@ const StickerPacks: React.FC = () => {
setPackToDelete(null);
} catch (err) {
console.error('Ошибка при удалении стикерпака:', err);
alert('Не удалось удалить стикерпак');
alert(getTranslation('stickerpacks_delete_pack_error'));
setIsDeleting(false);
}
}
@ -128,7 +129,7 @@ const StickerPacks: React.FC = () => {
const handleDeleteSticker = (fileId: string) => {
if (!fileId) {
alert('Не удалось определить ID стикера');
alert(getTranslation('stickerpacks_sticker_id_error'));
return;
}
@ -158,7 +159,7 @@ const StickerPacks: React.FC = () => {
setStickerToDelete(null);
} catch (err) {
console.error('Ошибка при удалении стикера:', err);
alert('Не удалось удалить стикер');
alert(getTranslation('stickerpacks_delete_sticker_error'));
setIsDeletingSticker(false);
}
}
@ -168,27 +169,27 @@ const StickerPacks: React.FC = () => {
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>
Мои стикерпаки
{getTranslation('stickerpacks_title')}
</h1>
<p className={styles.subtitle}>
Создавайте и публикуйте наборы стикеров в Telegram
{getTranslation('stickerpacks_subtitle')}
</p>
</div>
{loading && (
<div className={styles.placeholder}>
<p>Загрузка стикерпаков...</p>
<p>{getTranslation('stickerpacks_loading')}</p>
</div>
)}
{error && (
<div className={styles.error}>
<p>{error}</p>
<p>{getTranslation('stickerpacks_error')}</p>
<button
className={styles.retryButton}
onClick={() => window.location.reload()}
>
Повторить
{getTranslation('stickerpacks_retry_button')}
</button>
</div>
)}
@ -197,13 +198,13 @@ const StickerPacks: React.FC = () => {
<div className={styles.placeholder}>
<span className={styles.placeholderIcon}>📦</span>
<p className={styles.placeholderText}>
У вас пока нет стикерпаков
{getTranslation('stickerpacks_empty')}
</p>
<button
className={styles.createButton}
onClick={handleCreateStickerPack}
>
Создать стикерпак
{getTranslation('stickerpacks_create_button')}
</button>
</div>
)}
@ -219,10 +220,10 @@ const StickerPacks: React.FC = () => {
>
<div className={styles.packHeader}>
<h3 className={styles.packTitle}>
Стикерпак: <span className={styles.packTitleName}>{cleanPackTitle(pack.title)}</span>
{getTranslation('stickerpacks_pack_title_prefix')}: <span className={styles.packTitleName}>{cleanPackTitle(pack.title)}</span>
</h3>
<p className={styles.packStats}>
{pack.stickers ? pack.stickers.length : 0} / 49 стикеров
{getTranslation('stickerpacks_pack_stats', pack.stickers ? pack.stickers.length : 0)}
</p>
</div>
<div className={styles.packStickers}>
@ -231,7 +232,7 @@ const StickerPacks: React.FC = () => {
{sticker.file_url ? (
<img
src={sticker.file_url}
alt={`Стикер ${idx + 1}`}
alt={getTranslation('stickerpacks_sticker_alt', idx + 1)}
className={styles.stickerImage}
/>
) : (
@ -249,7 +250,7 @@ const StickerPacks: React.FC = () => {
handleDeletePack(pack.name);
}}
>
Удалить
{getTranslation('stickerpacks_delete_button')}
</button>
</div>
))}
@ -259,7 +260,7 @@ const StickerPacks: React.FC = () => {
className={styles.createButtonFixed}
onClick={handleCreateStickerPack}
>
Создать стикерпак
{getTranslation('stickerpacks_create_button')}
</button>
</>
)}
@ -271,7 +272,7 @@ const StickerPacks: React.FC = () => {
className={styles.backButton}
onClick={handleCloseDetails}
>
Назад
{getTranslation('stickerpacks_back_button')}
</button>
<h2 className={styles.detailsTitle}>{cleanPackTitle(selectedPack.title)}</h2>
</div>
@ -282,14 +283,14 @@ const StickerPacks: React.FC = () => {
className={styles.addStickerButton}
onClick={() => navigate(`/add-sticker/${selectedPack.name}`)}
>
Добавить стикер
{getTranslation('stickerpacks_add_sticker_button')}
</button>
<button
className={styles.openInTelegramButton}
onClick={handleOpenInTelegram}
>
Открыть в Telegram
{getTranslation('stickerpacks_open_telegram_button')}
</button>
</div>
@ -299,7 +300,7 @@ const StickerPacks: React.FC = () => {
{sticker.file_url ? (
<img
src={sticker.file_url}
alt={`Стикер ${index + 1}`}
alt={getTranslation('stickerpacks_sticker_alt', index + 1)}
className={styles.stickerImage}
/>
) : (
@ -326,12 +327,12 @@ const StickerPacks: React.FC = () => {
{/* Модальное окно подтверждения удаления стикерпака */}
<NotificationModal
isVisible={!!packToDelete}
title="Удаление стикерпака"
message="Вы уверены, что хотите удалить этот стикерпак?"
title={getTranslation('stickerpacks_delete_pack_title')}
message={getTranslation('stickerpacks_delete_pack_message')}
isLoading={isDeleting}
showGalleryButton={true}
galleryButtonText="Отмена"
continueButtonText="Удалить"
galleryButtonText={getTranslation('stickerpacks_cancel_button')}
continueButtonText={getTranslation('stickerpacks_confirm_delete_button')}
onContinueClick={handleConfirmDelete}
onGalleryClick={() => setPackToDelete(null)}
isPrimaryGalleryButton={false}
@ -340,12 +341,12 @@ const StickerPacks: React.FC = () => {
{/* Модальное окно подтверждения удаления стикера */}
<NotificationModal
isVisible={!!stickerToDelete}
title="Удаление стикера"
message="Вы уверены, что хотите удалить этот стикер?"
title={getTranslation('stickerpacks_delete_sticker_title')}
message={getTranslation('stickerpacks_delete_sticker_message')}
isLoading={isDeletingSticker}
showGalleryButton={true}
galleryButtonText="Отмена"
continueButtonText="Удалить"
galleryButtonText={getTranslation('stickerpacks_cancel_button')}
continueButtonText={getTranslation('stickerpacks_confirm_delete_button')}
onContinueClick={handleConfirmDeleteSticker}
onGalleryClick={() => setStickerToDelete(null)}
isPrimaryGalleryButton={false}

View File

@ -4,6 +4,7 @@ import OnboardingLayout from '../../components/shared/OnboardingLayout';
import styles from './OnboardingHowTo.module.css';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import { getTranslation } from '../../constants/translations';
const OnboardingHowTo: React.FC = () => {
const navigate = useNavigate();
@ -42,11 +43,11 @@ const OnboardingHowTo: React.FC = () => {
return (
<OnboardingLayout
title="Как создать стикер"
title={getTranslation('how_to_title')}
currentStep={2}
totalSteps={3}
primaryButtonText="Далее"
secondaryButtonText="Пропустить"
primaryButtonText={getTranslation('next')}
secondaryButtonText={getTranslation('skip')}
onPrimaryClick={handleNext}
onSecondaryClick={handleSkip}
>
@ -54,24 +55,24 @@ const OnboardingHowTo: React.FC = () => {
<div className={styles.step}>
<div className={styles.stepNumber}>1</div>
<div className={styles.stepContent}>
<h3 className={styles.stepTitle}>Загрузите фото</h3>
<p className={styles.stepDescription}>Выберите фотографию и обрежьте её в квадрат</p>
<h3 className={styles.stepTitle}>{getTranslation('upload_photo_title')}</h3>
<p className={styles.stepDescription}>{getTranslation('upload_photo_description')}</p>
</div>
</div>
<div className={styles.step}>
<div className={styles.stepNumber}>2</div>
<div className={styles.stepContent}>
<h3 className={styles.stepTitle}>Выберите стиль</h3>
<p className={styles.stepDescription}>Подберите подходящий стиль для вашего стикера</p>
<h3 className={styles.stepTitle}>{getTranslation('choose_style_title')}</h3>
<p className={styles.stepDescription}>{getTranslation('choose_style_description')}</p>
</div>
</div>
<div className={styles.step}>
<div className={styles.stepNumber}>3</div>
<div className={styles.stepContent}>
<h3 className={styles.stepTitle}>Создайте стикер</h3>
<p className={styles.stepDescription}>Дождитесь генерации и сохраните результат</p>
<h3 className={styles.stepTitle}>{getTranslation('create_sticker_title')}</h3>
<p className={styles.stepDescription}>{getTranslation('create_sticker_description')}</p>
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@ import OnboardingLayout from '../../components/shared/OnboardingLayout';
import styles from './OnboardingStickerPacks.module.css';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import { getTranslation } from '../../constants/translations';
const OnboardingStickerPacks: React.FC = () => {
const navigate = useNavigate();
@ -42,11 +43,11 @@ const OnboardingStickerPacks: React.FC = () => {
return (
<OnboardingLayout
title="Создавайте стикерпаки"
title={getTranslation('sticker_packs_title')}
currentStep={3}
totalSteps={3}
primaryButtonText="Начать"
secondaryButtonText="Пропустить"
primaryButtonText={getTranslation('start')}
secondaryButtonText={getTranslation('skip')}
onPrimaryClick={handleStart}
onSecondaryClick={handleStart}
>
@ -54,24 +55,24 @@ const OnboardingStickerPacks: React.FC = () => {
<div className={styles.step}>
<div className={styles.stepNumber}>1</div>
<div className={styles.stepContent}>
<h3 className={styles.stepTitle}>Выберите стикеры</h3>
<p className={styles.stepDescription}>Отберите лучшие стикеры из вашей галереи</p>
<h3 className={styles.stepTitle}>{getTranslation('select_stickers_title')}</h3>
<p className={styles.stepDescription}>{getTranslation('select_stickers_description')}</p>
</div>
</div>
<div className={styles.step}>
<div className={styles.stepNumber}>2</div>
<div className={styles.stepContent}>
<h3 className={styles.stepTitle}>Организуйте набор</h3>
<p className={styles.stepDescription}>Расположите стикеры в нужном порядке</p>
<h3 className={styles.stepTitle}>{getTranslation('organize_pack_title')}</h3>
<p className={styles.stepDescription}>{getTranslation('organize_pack_description')}</p>
</div>
</div>
<div className={styles.step}>
<div className={styles.stepNumber}>3</div>
<div className={styles.stepContent}>
<h3 className={styles.stepTitle}>Опубликуйте</h3>
<p className={styles.stepDescription}>Создайте стикерпак в Telegram одним нажатием</p>
<h3 className={styles.stepTitle}>{getTranslation('publish_title')}</h3>
<p className={styles.stepDescription}>{getTranslation('publish_description')}</p>
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@ import OnboardingLayout from '../../components/shared/OnboardingLayout';
import { images } from '../../assets';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import { getTranslation } from '../../constants/translations';
const OnboardingWelcome: React.FC = () => {
const navigate = useNavigate();
@ -42,13 +43,13 @@ const OnboardingWelcome: React.FC = () => {
return (
<OnboardingLayout
title="Добро пожаловать в Sticker Generator"
title={getTranslation('welcome_title')}
image={images.onboard1}
description="Создавайте уникальные стикеры из ваших фотографий с помощью искусственного интеллекта"
description={getTranslation('welcome_description')}
currentStep={1}
totalSteps={3}
primaryButtonText="Далее"
secondaryButtonText="Пропустить"
primaryButtonText={getTranslation('next')}
secondaryButtonText={getTranslation('skip')}
onPrimaryClick={handleNext}
onSecondaryClick={handleSkip}
/>

View File

@ -4,6 +4,7 @@ import styles from './TermsAndConditions.module.css';
import { images } from '../../assets';
import customAnalyticsService from '../../services/customAnalyticsService';
import { getCurrentUserId } from '../../constants/user';
import { getTranslation } from '../../constants/translations';
const TermsAndConditions: React.FC = () => {
const navigate = useNavigate();
@ -63,10 +64,10 @@ const TermsAndConditions: React.FC = () => {
<img src={images.shieldIcon} alt="" className={styles.icon} />
</div>
<h1 className={styles.title}>Условия и Конфиденциальность</h1>
<h1 className={styles.title}>{getTranslation('terms_title')}</h1>
<p className={styles.description}>
Продолжая использовать это приложение, вы соглашаетесь с нашими Условиями использования и Политикой конфиденциальности.
{getTranslation('terms_description')}
</p>
<div className={styles.links}>
@ -77,7 +78,7 @@ const TermsAndConditions: React.FC = () => {
rel="noopener noreferrer"
onClick={() => handlePolicyLinkClick("https://telegra.ph/Polzovatelskoe-soglashenie-03-19-13")}
>
Условия использования
{getTranslation('terms_of_use')}
</a>
<a
href="https://telegra.ph/Politika-konfidencialnosti-03-19-10"
@ -86,7 +87,7 @@ const TermsAndConditions: React.FC = () => {
rel="noopener noreferrer"
onClick={() => handlePolicyLinkClick("https://telegra.ph/Politika-konfidencialnosti-03-19-10")}
>
Политика конфиденциальности
{getTranslation('privacy_policy')}
</a>
</div>
@ -95,13 +96,13 @@ const TermsAndConditions: React.FC = () => {
className={styles.primaryButton}
onClick={handleAccept}
>
Принять
{getTranslation('accept')}
</button>
<button
className={styles.secondaryButton}
onClick={handleDecline}
>
Отклонить
{getTranslation('decline')}
</button>
</div>
</div>

View File

@ -6,6 +6,7 @@ import translateService from './translateService';
import { getCurrentUserId, isTelegramWebAppAvailable } from '../constants/user';
import { trackStickerGeneration, trackRejectedPrompt } from './analyticsService';
import memeService from './memeService';
import { getTranslation } from '../constants/translations';
const API_BASE_URL = 'https://stickerserver.gymnasticstuff.uk';
@ -290,7 +291,7 @@ const apiService = {
// Не продолжаем генерацию, возвращаем ошибку с сообщением
return {
translationFailed: true,
usedPrompt: 'Недопустимый промпт', // Сообщение для пользователя
usedPrompt: getTranslation('invalid_prompt'), // Сообщение для пользователя
errorDetails: translationResult.text // Детали ошибки для отладки
};
}
@ -519,7 +520,7 @@ const apiService = {
// Возвращаем ошибку
return {
translationFailed: true,
usedPrompt: 'Недопустимый промпт',
usedPrompt: getTranslation('invalid_prompt'),
errorDetails: translationResult.text
};
}

View File

@ -1,6 +1,8 @@
// URL для LibreTranslate API (сохраняем, но не используем)
const TRANSLATE_API_URL = 'https://translate.maxdev.keenetic.pro/translate';
import { getTranslation } from '../constants/translations';
// URL и ключ для OpenRouter API
const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';
const OPENROUTER_API_KEY = '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';
@ -241,7 +243,7 @@ const translateService = {
// Если это основная модель, сразу возвращаем ошибку
// и не пытаемся использовать резервную модель
if (model === TRANSLATION_MODELS.PRIMARY) {
return { success: false, text: 'Недопустимый промпт' };
return { success: false, text: getTranslation('invalid_prompt') };
} else {
throw new Error('Model refused to translate');
}
@ -268,7 +270,7 @@ const translateService = {
// Если это была последняя модель, прекращаем попытки
if (i === models.length - 1) {
console.error('Все попытки перевода через LLM не удались');
return { success: false, text: 'Недопустимый промпт' };
return { success: false, text: getTranslation('invalid_prompt') };
}
// Иначе продолжаем со следующей моделью (только при технических ошибках)
}
@ -276,7 +278,7 @@ const translateService = {
// Этот код не должен выполниться, но оставляем для полноты
console.error('Все попытки перевода через LLM не удались');
return { success: false, text: 'Недопустимый промпт' };
return { success: false, text: getTranslation('invalid_prompt') };
}
};

View File

@ -1,5 +1,6 @@
import { WorkflowType } from '../constants/workflows';
import { GenerationOptions } from '../types/generation';
import { getTranslation } from '../constants/translations';
/**
* Определяет тип воркфлоу в зависимости от выбранного стиля и типа эмоций
@ -86,8 +87,8 @@ export const validateGenerationParams = (
if (!imageData) {
return {
isValid: false,
errorTitle: 'Внимание',
errorMessage: 'Сначала загрузите изображение'
errorTitle: getTranslation('upload_image_warning_title'),
errorMessage: getTranslation('upload_image_warning_message')
};
}
@ -95,8 +96,8 @@ export const validateGenerationParams = (
if (!presetId) {
return {
isValid: false,
errorTitle: 'Внимание',
errorMessage: 'Выберите образ для генерации'
errorTitle: getTranslation('select_style_warning_title'),
errorMessage: getTranslation('select_style_warning_message')
};
}
@ -104,8 +105,8 @@ export const validateGenerationParams = (
if (presetId === 'customPrompt' && (!customPrompt || !customPrompt.trim())) {
return {
isValid: false,
errorTitle: 'Внимание',
errorMessage: 'Введите текст промпта'
errorTitle: getTranslation('enter_prompt_warning_title'),
errorMessage: getTranslation('enter_prompt_warning_message')
};
}