diff --git a/README.md b/README.md index 041f7ab..cb24db4 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ In a world dominated by Artificial Intelligence, having the ability to think - 🎙️ **Generate professional podcasts** - Advanced multi-speaker podcast generation - 🔍 **Search intelligently** - Full-text and vector search across all your content - 💬 **Chat with context** - AI conversations powered by your research -- 🌐 **Multi-language UI** - English, Portuguese, Chinese (Simplified & Traditional), and Japanese support +- 🌐 **Multi-language UI** - English, Portuguese, Chinese (Simplified & Traditional), Japanese, and Russian support Learn more about our project at [https://www.open-notebook.ai](https://www.open-notebook.ai) diff --git a/frontend/src/components/common/LanguageToggle.tsx b/frontend/src/components/common/LanguageToggle.tsx index 502d719..bf168c5 100644 --- a/frontend/src/components/common/LanguageToggle.tsx +++ b/frontend/src/components/common/LanguageToggle.tsx @@ -64,6 +64,12 @@ export function LanguageToggle({ iconOnly = false }: LanguageToggleProps) { > {t.common.japanese} + setLanguage('ru-RU')} + className={currentLang === 'ru-RU' || currentLang.startsWith('ru') ? 'bg-accent' : ''} + > + {t.common.russian} + ) diff --git a/frontend/src/lib/locales/CLAUDE.md b/frontend/src/lib/locales/CLAUDE.md index 26cd838..d1249a6 100644 --- a/frontend/src/lib/locales/CLAUDE.md +++ b/frontend/src/lib/locales/CLAUDE.md @@ -18,7 +18,8 @@ lib/ ├── pt-BR/index.ts # Brazilian Portuguese translations ├── zh-CN/index.ts # Simplified Chinese translations ├── zh-TW/index.ts # Traditional Chinese translations - └── ja-JP/index.ts # Japanese translations + ├── ja-JP/index.ts # Japanese translations + └── ru-RU/index.ts # Russian translations ``` ## Key Components diff --git a/frontend/src/lib/locales/en-US/index.ts b/frontend/src/lib/locales/en-US/index.ts index 9402b91..674d5f6 100644 --- a/frontend/src/lib/locales/en-US/index.ts +++ b/frontend/src/lib/locales/en-US/index.ts @@ -23,6 +23,7 @@ export const enUS = { english: "English", chinese: "简体中文", japanese: "日本語", + russian: "Русский", source: "Source", notebook: "Notebook", podcast: "Podcast", diff --git a/frontend/src/lib/locales/index.test.ts b/frontend/src/lib/locales/index.test.ts index d9248c9..731710f 100644 --- a/frontend/src/lib/locales/index.test.ts +++ b/frontend/src/lib/locales/index.test.ts @@ -3,6 +3,7 @@ import { enUS } from './en-US' import { zhCN } from './zh-CN' import { zhTW } from './zh-TW' import { jaJP } from './ja-JP' +import { ruRU } from './ru-RU' describe('Internationalization Locales Integrity', () => { const getKeys = (obj: Record, prefix = ''): string[] => { @@ -19,6 +20,7 @@ describe('Internationalization Locales Integrity', () => { const zhCNKeys = getKeys(zhCN) const zhTWKeys = getKeys(zhTW) const jaJPKeys = getKeys(jaJP) + const ruRUKeys = getKeys(ruRU) it('zh-CN should have the same keys as en-US', () => { const missingInZhCN = enKeys.filter(key => !zhCNKeys.includes(key)) @@ -43,4 +45,12 @@ describe('Internationalization Locales Integrity', () => { expect(missingInJaJP, `Missing keys in ja-JP: ${missingInJaJP.join(', ')}`).toEqual([]) expect(extraInJaJP, `Extra keys in ja-JP: ${extraInJaJP.join(', ')}`).toEqual([]) }) + + it('ru-RU should have the same keys as en-US', () => { + const missingInRuRU = enKeys.filter(key => !ruRUKeys.includes(key)) + const extraInRuRU = ruRUKeys.filter(key => !enKeys.includes(key)) + + expect(missingInRuRU, `Missing keys in ru-RU: ${missingInRuRU.join(', ')}`).toEqual([]) + expect(extraInRuRU, `Extra keys in ru-RU: ${extraInRuRU.join(', ')}`).toEqual([]) + }) }) diff --git a/frontend/src/lib/locales/index.ts b/frontend/src/lib/locales/index.ts index f6d2682..e1da611 100644 --- a/frontend/src/lib/locales/index.ts +++ b/frontend/src/lib/locales/index.ts @@ -4,6 +4,7 @@ import { zhTW } from './zh-TW'; import { ptBR } from './pt-BR'; import { jaJP } from './ja-JP'; import { itIT } from './it-IT'; +import { ruRU } from './ru-RU'; export const resources = { 'zh-CN': { translation: zhCN }, @@ -12,11 +13,12 @@ export const resources = { 'pt-BR': { translation: ptBR }, 'ja-JP': { translation: jaJP }, 'it-IT': { translation: itIT }, + 'ru-RU': { translation: ruRU }, } as const; export type TranslationKeys = typeof enUS; -export type LanguageCode = 'zh-CN' | 'en-US' | 'zh-TW' | 'pt-BR' | 'ja-JP' | 'it-IT'; +export type LanguageCode = 'zh-CN' | 'en-US' | 'zh-TW' | 'pt-BR' | 'ja-JP' | 'it-IT' | 'ru-RU'; export type Language = { code: LanguageCode; @@ -30,6 +32,7 @@ export const languages: Language[] = [ { code: 'pt-BR', label: 'Português' }, { code: 'ja-JP', label: '日本語' }, { code: 'it-IT', label: 'Italiano' }, + { code: 'ru-RU', label: 'Русский' }, ]; -export { zhCN, enUS, zhTW, ptBR, jaJP, itIT }; +export { zhCN, enUS, zhTW, ptBR, jaJP, itIT, ruRU }; diff --git a/frontend/src/lib/locales/ja-JP/index.ts b/frontend/src/lib/locales/ja-JP/index.ts index 7d9e6c3..7f0671f 100644 --- a/frontend/src/lib/locales/ja-JP/index.ts +++ b/frontend/src/lib/locales/ja-JP/index.ts @@ -23,6 +23,7 @@ export const jaJP = { english: "English", chinese: "简体中文", japanese: "日本語", + russian: "Русский", source: "ソース", notebook: "ノートブック", podcast: "ポッドキャスト", diff --git a/frontend/src/lib/locales/pt-BR/index.ts b/frontend/src/lib/locales/pt-BR/index.ts index 6bd401b..eab0776 100644 --- a/frontend/src/lib/locales/pt-BR/index.ts +++ b/frontend/src/lib/locales/pt-BR/index.ts @@ -23,6 +23,7 @@ export const ptBR = { english: "English", chinese: "简体中文", japanese: "日本語", + russian: "Русский", source: "Fonte", notebook: "Caderno", podcast: "Podcast", diff --git a/frontend/src/lib/locales/ru-RU/index.ts b/frontend/src/lib/locales/ru-RU/index.ts new file mode 100644 index 0000000..2dd39ff --- /dev/null +++ b/frontend/src/lib/locales/ru-RU/index.ts @@ -0,0 +1,958 @@ +export const ruRU = { + common: { + search: "Поиск...", + create: "Создать", + new: "Новый", + cancel: "Отмена", + delete: "Удалить", + edit: "Редактировать", + theme: "Тема", + signOut: "Выйти", + noMatches: "Совпадений не найдено", + tryDifferentSearch: "Попробуйте другой поисковый запрос.", + light: "Светлая", + dark: "Тёмная", + system: "Системная", + loading: "Загрузка...", + note: "Заметка", + insight: "Инсайт", + newSource: "Новый источник", + newNotebook: "Новый блокнот", + newPodcast: "Новый подкаст", + language: "Язык", + english: "English", + chinese: "简体中文", + japanese: "日本語", + russian: "Русский", + source: "Источник", + notebook: "Блокнот", + podcast: "Подкаст", + quickActions: "Быстрые действия", + quickActionsDesc: "Навигация, поиск, запрос, тема", + appName: "Open Notebook", + add: "Добавить", + remove: "Удалить", + confirm: "Подтвердить", + warning: "Предупреждение", + error: "Ошибка", + success: "Успешно", + sessions: "Сессии", + model: "Модель", + send: "Отправить", + back: "Назад", + next: "Далее", + done: "Готово", + processing: "Обработка...", + creating: "Создание...", + tokenCount: "Токены", + charCount: "Символы", + linked: "Связано", + added: "Добавлено {date}", + adding: "Добавление...", + addSelected: "Добавить выбранное", + customModel: "Своя модель", + messages: "Сообщения", + failed: "не удалось", + current: "Текущий", + save: "Сохранить", + writeNote: "Написать заметку", + batchMode: "Пакетный режим", + optional: "Необязательно", + type: "Тип", + title: "Название", + created: "Создано {time}", + updated: "Обновлено {time}", + actions: "Действия", + noResults: "Нет результатов", + references: "Ссылки", + refreshPage: "Попробуйте обновить страницу", + refresh: "Обновить", + aiGenerated: "Сгенерировано ИИ", + human: "Человек", + unknown: "Неизвестно", + notes: "Заметки", + chat: "Чат", + details: "Подробности", + deleteForever: "Удалить навсегда", + connectionError: "Ошибка подключения", + unableToConnect: "Не удаётся подключиться к API-серверу", + retryConnection: "Повторить подключение", + diagnosticInfo: "Диагностическая информация", + version: "Версия", + built: "Сборка", + apiUrl: "URL API", + frontendUrl: "URL фронтенда", + checkConsoleLogs: "Проверьте консоль браузера для подробных логов (ищите сообщения 🔧 [Config])", + yes: "Да", + no: "Нет", + simple: "Простой", + saving: "Сохранение...", + description: "Описание", + saveToNote: "Сохранить в заметку", + copyToClipboard: "Копировать в буфер обмена", + close: "Закрыть", + insights: "Инсайты", + progress: "Прогресс", + deleting: "Удаление...", + created_label: "Создано", + updated_label: "Обновлено", + download: "Скачать", + saveChanges: "Сохранить изменения", + name: "Название", + default: "По умолчанию", + nameRequired: "Название обязательно", + modelConfiguration: "Настройка модели", + resetToDefault: "Сбросить по умолчанию", + notFound: "Не найдено", + reasoning: "Рассуждение", + searchTerms: "Поисковые запросы", + strategy: "Стратегия", + individualAnswers: "Отдельные ответы ({count})", + finalAnswer: "Итоговый ответ", + notebookLabel: "Блокнот: {name}", + itemNotFound: "Этот {type} не найден", + accessibility: { + navigation: "Навигация", + transformationViews: "Представления трансформаций", + searchKB: "Спросить или найти в базе знаний", + enterQuestion: "Введите вопрос для базы знаний", + enterSearch: "Введите поисковый запрос", + searchKBBtn: "Поиск по базе знаний", + podcastViews: "Представления подкастов", + chatSessions: "Сессии чата", + ytVideo: "Видео YouTube", + askResponse: "Ответ на запрос", + searchNotebooks: "Поиск блокнотов", + }, + url: "URL", + errorDetails: "Детали ошибки", + editTransformation: "Редактировать трансформацию", + comingSoon: "Скоро будет", + retry: "Повторить", + traditionalChinese: "繁體中文", + portuguese: "Português", + completed: "завершено", + saveSuccess: "Успешно сохранено", + contextModes: { + off: "Не включено в чат", + insights: "Только инсайты", + full: "Полное содержимое", + clickToCycle: "Нажмите для переключения", + }, + clickToEdit: "Нажмите для редактирования", + }, + apiErrors: { + notebookNotFound: "Блокнот не найден", + sourceNotFound: "Источник не найден", + transformationNotFound: "Трансформация не найдена", + fileUploadFailed: "Не удалось загрузить файл", + urlRequired: "URL обязателен для типа «ссылка»", + contentRequired: "Содержимое обязательно для типа «текст»", + invalidSourceType: "Недопустимый тип источника", + processingFailed: "Обработка не удалась", + failedToQueue: "Не удалось поставить в очередь обработки", + invalidSortBy: "Поле сортировки должно быть 'created' или 'updated'", + invalidSortOrder: "Порядок сортировки должен быть 'asc' или 'desc'", + accessDenied: "Доступ к файлу запрещён", + fileNotFoundOnServer: "Файл не найден на сервере", + searchFailed: "Поиск не удался", + askFailed: "Запрос не удался", + pleaseEnterQuestion: "Пожалуйста, введите вопрос", + pleaseConfigureModels: "Пожалуйста, настройте все необходимые модели", + failedToCreateSession: "Не удалось создать сессию", + failedToUpdateSession: "Не удалось обновить сессию", + failedToDeleteSession: "Не удалось удалить сессию", + failedToSendMessage: "Не удалось отправить сообщение", + unauthorized: "Неавторизованный доступ, проверьте пароль", + invalidPassword: "Неверный пароль", + missingAuth: "Отсутствует авторизация", + embeddingModelRequired: "Для этой функции требуется модель эмбеддингов. Настройте её в разделе «Модели».", + strategyModelNotFound: "Модель стратегии не найдена", + answerModelNotFound: "Модель ответов не найдена", + finalAnswerModelNotFound: "Модель итогового ответа не найдена", + noAnswerGenerated: "Не удалось сгенерировать ответ", + genericError: "Произошла непредвиденная ошибка", + }, + connectionErrors: { + apiTitle: "Не удаётся подключиться к API-серверу", + apiDesc: "API-сервер Open Notebook недоступен", + dbTitle: "Ошибка подключения к базе данных", + dbDesc: "API-сервер работает, но база данных недоступна", + troubleshooting: "Обычно это означает:", + apiUnreachable1: "API-сервер не запущен", + apiUnreachable2: "API-сервер работает по другому адресу", + apiUnreachable3: "Проблемы с сетевым подключением", + dbFailed1: "SurrealDB не запущен", + dbFailed2: "Неверные настройки подключения к базе данных", + dbFailed3: "Сетевые проблемы между API и базой данных", + quickFixes: "Быстрые решения:", + setApiUrl: "Установите переменную окружения API_URL:", + checkSurreal: "Проверьте, запущен ли SurrealDB:", + seeDocumentation: "Подробные инструкции по настройке см. в:", + docLink: "Документация Open Notebook", + showTechnical: "Показать техническую информацию", + attemptedUrl: "Использованный URL", + message: "Сообщение", + technicalDetails: "Технические детали", + stackTrace: "Стек вызовов", + retryLabel: "Повторить подключение", + retryHint: "Нажмите R или кнопку для повторной попытки", + dockerLabel: "Для Docker", + localDevLabel: "Для локальной разработки", + }, + auth: { + loginTitle: "Open Notebook", + loginDesc: "Введите пароль для доступа к приложению", + passwordPlaceholder: "Пароль", + signingIn: "Вход...", + signIn: "Войти", + unhandledError: "Необработанная ошибка при входе", + connectErrorHint: "Не удаётся подключиться к серверу. Проверьте, запущен ли API.", + }, + navigation: { + collect: "Собрать", + process: "Обработать", + create: "Создать", + manage: "Управление", + sources: "Источники", + notebooks: "Блокноты", + askAndSearch: "Запрос и поиск", + podcasts: "Подкасты", + models: "Модели", + transformations: "Трансформации", + transformation: "Трансформация", + settings: "Настройки", + advanced: "Дополнительно", + nav: "Навигация", + language: "Переключить язык", + theme: "Тема", + search: "Поиск", + ask: "Запрос", + }, + notebooks: { + title: "Блокноты", + newNotebook: "Новый блокнот", + searchPlaceholder: "Поиск блокнотов...", + archived: "Архивные", + archive: "Архивировать", + unarchive: "Разархивировать", + deleteNotebook: "Удалить блокнот", + deleteNotebookDesc: "Вы уверены, что хотите удалить «{name}»? Это действие нельзя отменить.", + deleteNotebookLoading: "Загрузка предварительного просмотра удаления...", + deleteNotebookNotes: "Будет удалено заметок: {count}.", + deleteNotebookNoNotes: "Нет заметок для удаления.", + deleteNotebookExclusiveSources: "Источников только в этом блокноте: {count}.", + deleteNotebookSharedSources: "Источников, связанных с другими блокнотами (будут отвязаны): {count}.", + deleteNotebookNoSources: "В этом блокноте нет источников.", + deleteExclusiveSourcesLabel: "Удалить эксклюзивные источники", + keepExclusiveSourcesLabel: "Отвязать и сохранить", + activeNotebooks: "Активные блокноты", + archivedNotebooks: "Архивные блокноты", + emptyDescription: "Начните с создания первого блокнота для организации исследований.", + noActiveNotebooks: "Нет активных блокнотов", + noArchivedNotebooks: "Нет архивных блокнотов", + notFound: "Блокнот не найден", + notFoundDesc: "Запрошенный блокнот не существует.", + noDescription: "Без описания...", + updated: "Обновлено", + namePlaceholder: "Название блокнота", + addDescription: "Добавить описание...", + noNotesYet: "Пока нет заметок", + deleteNote: "Удалить заметку", + deleteNoteConfirm: "Вы уверены, что хотите удалить эту заметку? Это действие нельзя отменить.", + noteCreatedSuccess: "Заметка успешно создана", + failedToCreateNote: "Не удалось создать заметку", + noteUpdatedSuccess: "Заметка успешно обновлена", + failedToUpdateNote: "Не удалось обновить заметку", + noteDeletedSuccess: "Заметка успешно удалена", + failedToDeleteNote: "Не удалось удалить заметку", + createNew: "Создать новый блокнот", + createNewDesc: "Введите название и необязательное описание для начала.", + descPlaceholder: "Добавьте дополнительную информацию о блокноте...", + createSuccess: "Блокнот успешно создан", + updateSuccess: "Блокнот успешно обновлён", + deleteSuccess: "Блокнот успешно удалён", + }, + sources: { + title: "Источники", + add: "Добавить источник", + addNew: "Добавить новый источник", + addExisting: "Добавить существующий источник", + empty: "Пока нет источников", + emptyDesc: "Добавьте первый источник, чтобы начать создание базы знаний.", + delete: "Удалить источник", + deleteMsg: "Вы уверены, что хотите удалить этот источник? Это действие нельзя отменить.", + statusPreparing: "Подготовка", + statusQueued: "В очереди", + statusProcessing: "Обработка", + statusCompleted: "Завершено", + statusFailed: "Ошибка", + statusPreparingDesc: "Подготовка к обработке", + statusQueuedDesc: "Ожидание обработки", + statusProcessingDesc: "Выполняется обработка", + statusCompletedDesc: "Успешно обработано", + statusFailedDesc: "Обработка не удалась", + failedToLoad: "Не удалось загрузить источники", + allSourcesDesc: "Просмотр всех источников. Вы можете добавлять новые или управлять существующими.", + allSources: "Все источники", + insights: "Инсайты", + yes: "Да", + no: "Нет", + loadingMore: "Загрузка...", + noSourcesYet: "Пока нет источников", + allSourcesDescShort: "Просмотр всех ваших источников.", + cannotSaveNoteNoNotebook: "Невозможно сохранить заметку: ID блокнота недоступен", + createFirstSource: "Добавьте первый источник, чтобы начать создание базы знаний.", + deleteSourceConfirm: "Вы уверены, что хотите удалить этот источник?", + deleteConfirm: "Вы уверены, что хотите удалить это?", + deleteConfirmWithTitle: "Вы уверены, что хотите удалить «{title}»?", + deleteSuccess: "Источник успешно удалён. Примечание: Чтобы удалить файл из хранилища, необходимо включить опцию «Удалить файл» в настройках.", + failedToDelete: "Не удалось удалить источник", + sourceQueued: "Источник в очереди", + sourceQueuedDesc: "Источник отправлен на фоновую обработку. Отслеживайте прогресс в списке источников.", + sourceAddedSuccess: "Источник успешно добавлен", + failedToAddSource: "Не удалось добавить источник", + sourceUpdatedSuccess: "Источник успешно обновлён", + failedToUpdateSource: "Не удалось обновить источник", + sourceDeletedSuccess: "Источник успешно удалён", + failedToDeleteSource: "Не удалось удалить источник", + fileUploadedSuccess: "Файл успешно загружен", + failedToUploadFile: "Не удалось загрузить файл", + sourceRequeued: "Повторная обработка в очереди", + sourceRequeuedDesc: "Источник поставлен в очередь на повторную обработку.", + failedToRetry: "Повтор не удался", + failedToRetryDesc: "Не удалось повторить обработку источника. Попробуйте ещё раз.", + sourcesAddedToNotebook: "Добавлено источников в блокнот: {count}", + failedToAddSourcesToNotebook: "Не удалось добавить источники в блокнот", + partialAddSuccess: "Добавлено: {success}, не удалось: {failed}", + sourceRemovedFromNotebook: "Источник успешно удалён из блокнота", + failedToRemoveSourceFromNotebook: "Не удалось удалить источник из блокнота", + removeConfirm: "Вы уверены, что хотите удалить это из блокнота?", + checking: "Проверка...", + untitledSource: "Без названия", + maxItems: "макс. {count}", + insightsCount: "Инсайтов: {count}", + details: "Детали", + detailsTitle: "Детали источника", + content: "Содержимое", + metadata: "Метаданные", + type: { + link: "Ссылка", + file: "Файл", + text: "Текст", + }, + id: "ID источника", + topics: "Темы", + embedded: "С эмбеддингом", + notEmbedded: "Без эмбеддинга", + embedContent: "Создать эмбеддинг", + embedding: "Создание эмбеддинга...", + alreadyEmbedded: "Эмбеддинг уже создан", + downloadFile: "Скачать файл", + fileUnavailable: "Файл недоступен", + preparing: "Подготовка...", + generateNewInsight: "Сгенерировать новый инсайт", + selectTransformation: "Выберите трансформацию...", + noInsightsYet: "Пока нет инсайтов", + createFirstInsight: "Создайте первый инсайт с помощью трансформации выше", + viewInsight: "Просмотреть инсайт", + deleteInsight: "Удалить инсайт", + deleteInsightConfirm: "Вы уверены, что хотите удалить этот инсайт? Это действие нельзя отменить.", + insightGenerationStarted: "Генерация инсайта запущена. Скоро он появится.", + deleteNoteConfirm: "Вы уверены, что хотите удалить эту заметку? Это действие нельзя отменить.", + editNote: "Редактировать заметку", + createNote: "Создать заметку", + addTitle: "Добавьте название...", + untitledNote: "Без названия", + writeNotePlaceholder: "Напишите содержимое заметки здесь...", + saveNote: "Сохранить заметку", + createNoteBtn: "Создать заметку", + noNotesYet: "Пока нет заметок", + createFirstNote: "Создайте первую заметку для записи идей и наблюдений.", + deleteNote: "Удалить заметку", + urlLabel: "URL(ы) *", + fileLabel: "Файл(ы) *", + textContentLabel: "Текстовое содержимое *", + enterUrlsPlaceholder: "Введите URL-адреса, по одному на строку\nhttps://example.com/article1\nhttps://example.com/article2", + batchUrlHint: "Вставьте несколько URL (по одному на строку) для пакетного импорта", + invalidUrlsDetected: "Обнаружены недопустимые URL:", + lineLabel: "Строка {line}", + fixInvalidUrls: "Исправьте или удалите недопустимые URL для продолжения", + selectMultipleFilesHint: "Выберите несколько файлов для пакетного импорта. Поддерживаются: Документы (PDF, DOC, DOCX, PPT, XLS, EPUB, TXT, MD), Медиа (MP4, MP3, WAV, M4A), Изображения (JPG, PNG), Архивы (ZIP)", + selectedFiles: "Выбранные файлы:", + textPlaceholder: "Вставьте или введите содержимое здесь...", + htmlDetected: "Обнаружено HTML-содержимое. После обработки оно будет преобразовано в Markdown.", + titlePlaceholder: "Дайте источнику понятное название", + batchTitlesAuto: "Названия будут автоматически сгенерированы для каждого источника.", + batchCommonSettings: "Одинаковые блокноты и трансформации будут применены ко всем элементам.", + urlsCount: "URL: {count}", + filesCount: "Файлов: {count}", + addSource: "Добавить источник", + notEmbeddedAlert: "Содержимое без эмбеддинга", + notEmbeddedDesc: "Для этого содержимого не создан эмбеддинг для векторного поиска. Эмбеддинг обеспечивает расширенные возможности поиска и лучшее обнаружение контента.", + openOnYoutube: "Открыть на YouTube", + urlCopied: "URL скопирован в буфер обмена", + viewSource: "Просмотреть источник", + noInsightSelected: "Инсайт не выбран", + sourceInsight: "Инсайт источника", + manageNotebooks: "Управление блокнотами", + manageNotebooksDesc: "Управление блокнотами, содержащими этот источник", + noNotebooksAvailable: "Нет доступных блокнотов", + loadFailed: "Не удалось загрузить детали источника", + removeFromNotebook: "Удалить из блокнота", + retryProcessing: "Повторить обработку", + deleteSource: "Удалить источник", + retry: "Повторить", + progress: "Прогресс", + addExistingTitle: "Добавить существующие источники", + addExistingDesc: "Выберите существующие источники из всех блокнотов для добавления в текущий.", + searchPlaceholder: "Поиск источников по названию или URL...", + noNotebooksFound: "Блокноты не найдены.", + showingFirst100: "Показаны первые 100 источников. Используйте поиск для конкретных результатов.", + selectedCount: "Выбрано источников: {count}", + added: "Добавлено {date}", + addUrl: "Добавить URL", + uploadFile: "Загрузить файл", + enterText: "Ввести текст", + processDescription: "Содержимое будет обработано и проанализировано ИИ.", + processingFiles: "Обработка файлов...", + titleRequired: "Для текстового содержимого требуется название", + titleGenerated: "Если оставить пустым, название будет сгенерировано из содержимого", + batchCount: "{count} {type} будет обработано", + enableEmbedding: "Включить эмбеддинг для поиска", + embeddingDesc: "Позволяет находить этот источник в векторном поиске и запросах ИИ", + embeddingAlways: "Эмбеддинг включён автоматически", + embeddingAlwaysDesc: "В ваших настройках включено автоматическое создание эмбеддинга для векторного поиска.", + embeddingNever: "Эмбеддинг отключён", + embeddingNeverDesc: "В ваших настройках отключено создание эмбеддинга. Векторный поиск будет недоступен для этого источника.", + changeInSettings: "Вы можете изменить это в Настройках", + notFound: "Источник не найден", + noContent: "Содержимое недоступно", + insightsDesc: "Инсайты, сгенерированные анализом модели", + uploadedFile: "Загруженный файл", + fileUnavailableDesc: "Этот файл временно недоступен из-за проблем с хранилищем.", + batchSuccess: "Успешно создано источников: {count}", + batchFailed: "Не удалось создать все источники: {count}", + batchPartial: "Успешно: {success}, не удалось: {failed}", + submittingSource: "Отправка источника на обработку...", + contentRequired: "Пожалуйста, предоставьте необходимое содержимое для выбранного типа источника", + titleRequiredForText: "Для текстовых источников требуется название", + processingBatchSources: "Обработка источников: {count}. Это может занять некоторое время.", + processingSource: "Источник обрабатывается. Это может занять некоторое время.", + maxFilesAllowed: "Максимальное количество файлов в пакете: {count}", + }, + chat: { + sessions: "Сессии", + sessionTitlePlaceholder: "Введите название...", + noSessions: "Пока нет сессий чата", + startChatting: "Начните общение о ваших источниках.", + deleteSession: "Удалить сессию", + deleteSessionDesc: "Вы уверены, что хотите удалить эту сессию чата? Это действие нельзя отменить.", + sendPlaceholder: "Задайте вопрос о ваших источниках...", + newChat: "Новый чат", + sessionsTitle: "Сессии чата", + clearhistory: "Очистить историю", + renameSession: "Переименовать сессию", + noSourcesLinked: "Нет связанных источников", + thinking: "ИИ размышляет...", + chatWith: "Чат с {name}", + startConversation: "Начните разговор об этом {type}", + askQuestions: "Задавайте вопросы, чтобы лучше понять содержимое", + pressToSend: "Нажмите {key} для отправки", + model: "Модель", + createToStart: "Создайте сессию для начала.", + chatWithNotebook: "Чат с блокнотом", + unableToLoadChat: "Не удалось загрузить чат", + noDescription: "Без описания", + startByCreating: "Начните с создания первого блокнота для организации исследований.", + messagesCount: "Сообщений: {count}", + sessionCreated: "Сессия чата создана", + sessionUpdated: "Сессия обновлена", + sessionDeleted: "Сессия удалена", + }, + searchPage: { + askAndSearch: "Запрос и поиск", + chooseAMode: "Выберите режим", + askBeta: "Запрос (бета)", + search: "Поиск", + askYourKb: "Спросите базу знаний (бета)", + askYourKbDesc: "LLM ответит на ваш запрос на основе документов в базе знаний.", + question: "Вопрос", + enterQuestionPlaceholder: "Введите ваш вопрос...", + pressToSubmit: "Нажмите Cmd/Ctrl+Enter для отправки", + noEmbeddingModel: "Вы не можете использовать эту функцию, потому что не выбрана модель эмбеддинга. Настройте её на странице «Модели».", + usingCustomModels: "Используются пользовательские модели", + usingDefaultModels: "Используются модели по умолчанию", + advanced: "Расширенные", + strategy: "Стратегия", + answer: "Ответ", + final: "Итоговый", + ask: "Спросить", + processing: "Обработка...", + saveToNotebooks: "Сохранить в блокноты", + searchDesc: "Поиск в базе знаний по ключевым словам или концепциям", + enterSearchPlaceholder: "Введите поисковый запрос...", + pressToSearch: "Нажмите Enter для поиска", + searchType: "Тип поиска", + vectorSearchWarning: "Векторный поиск требует модель эмбеддинга. Доступен только текстовый поиск.", + textSearch: "Текстовый поиск", + vectorSearch: "Векторный поиск", + searchIn: "Искать в", + searchSources: "Искать в источниках", + searchNotes: "Искать в заметках", + resultsFound: "Найдено результатов: {count}", + matches: "Совпадения ({count})", + noResultsFor: "Нет результатов для «{query}»", + notSet: "Не задано", + saveToNotebook: "Сохранить в блокнот", + saveSuccess: "Успешно сохранено в блокнот", + saveError: "Не удалось сохранить в блокнот", + selectNotebook: "Выберите блокнот", + createNewNotebook: "Создать новый блокнот", + cancel: "Отмена", + searchAndAsk: "Поиск и запрос", + searchResultsFor: "Результаты поиска для «{query}»", + askAbout: "Спросить о «{query}»", + orSearchKb: "Или выполнить поиск по базе знаний", + saving: "Сохранение...", + advancedModelTitle: "Расширенный выбор моделей", + advancedModelDesc: "Выберите конкретные модели для каждого этапа процесса запроса", + strategyModel: "Модель стратегии", + answerModel: "Модель ответа", + finalAnswerModel: "Модель итогового ответа", + selectStrategyPlaceholder: "Выберите модель стратегии", + selectAnswerPlaceholder: "Выберите модель ответа", + selectFinalPlaceholder: "Выберите модель итогового ответа", + saveChanges: "Сохранить изменения", + processingQuestion: "Обработка вашего вопроса...", + }, + podcasts: { + generateEpisode: "Сгенерировать эпизод подкаста", + generateEpisodeDesc: "Выберите контент для включения и настройте параметры эпизода перед генерацией.", + content: "Контент", + contentDesc: "Выберите блокноты, источники и заметки для этого эпизода.", + itemsSelected: "Выбрано элементов: {count}", + tokens: "Токенов: {count}", + chars: "Символов: {count}", + loadingNotebooks: "Загрузка блокнотов...", + noNotebooksFoundInPodcasts: "Блокноты не найдены. Создайте блокнот и добавьте контент перед генерацией подкаста.", + noContentSelected: "Контент не выбран", + summary: "Краткое содержание", + fullContent: "Полное содержимое", + untitledSource: "Без названия", + untitledNote: "Без названия", + episodeSettings: "Настройки эпизода", + episodeProfile: "Профиль эпизода", + episodeProfilePlaceholder: "Выберите профиль эпизода", + episodeName: "Название эпизода", + episodeNamePlaceholder: "напр., ИИ и будущее работы", + additionalInstructions: "Дополнительные инструкции", + instructionsPlaceholder: "Любые дополнительные указания для брифинга эпизода...", + generating: "Генерация...", + generate: "Сгенерировать", + hostPlaceholder: "Ведущий {number}", + profileRequired: "Требуется профиль эпизода", + profileRequiredDesc: "Выберите профиль эпизода перед генерацией подкаста.", + nameRequired: "Требуется название эпизода", + nameRequiredDesc: "Укажите название эпизода.", + addContext: "Добавить контекст", + addContextDesc: "Выберите хотя бы один источник или заметку для включения в эпизод.", + generationFailed: "Генерация подкаста не удалась", + speakerProfile: "Профиль говорящего", + usesSpeakerProfile: "Использует профиль говорящего", + sources: "Источники", + notes: "Заметки", + noSources: "В этом блокноте нет доступных источников.", + noNotes: "В этом блокноте нет доступных заметок.", + selectMode: "Выберите режим", + buildContextFailed: "Не удалось построить контекст. Проверьте выбранные элементы.", + podcastTaskStarted: "Задача подкаста запущена", + loadingProfiles: "Загрузка профилей эпизодов...", + noProfilesFound: "Профили эпизодов не найдены. Создайте профиль перед генерацией подкаста.", + listTitle: "Подкасты", + listDesc: "Отслеживайте сгенерированные эпизоды и управляйте шаблонами.", + chooseAView: "Выберите представление", + episodesTab: "Эпизоды", + templatesTab: "Шаблоны", + overviewTitle: "Обзор эпизодов", + overviewDesc: "Отслеживайте задачи генерации подкастов и просматривайте готовые материалы.", + generateBtn: "Сгенерировать подкаст", + total: "Всего", + processingLabel: "В обработке", + completedLabel: "Завершено", + failedLabel: "Ошибка", + pendingLabel: "Ожидание", + loadErrorTitle: "Не удалось загрузить эпизоды", + loadErrorDesc: "Не удалось получить последние эпизоды подкастов. Попробуйте позже.", + loadingEpisodes: "Загрузка эпизодов…", + noEpisodesYet: "Пока нет эпизодов подкастов. Сгенерируйте первый из интерфейса чата блокнота или источника.", + statusRunningTitle: "В процессе", + statusRunningDesc: "Эпизоды, для которых активно генерируются материалы.", + statusPendingTitle: "В очереди / Ожидание", + statusPendingDesc: "Отправленные эпизоды, ожидающие начала обработки.", + statusCompletedTitle: "Завершённые эпизоды", + statusCompletedDesc: "Готовы к просмотру, загрузке или публикации.", + statusFailedTitle: "Неудачные эпизоды", + statusFailedDesc: "Эпизоды с ошибками во время генерации.", + templatesWorkspaceTitle: "Рабочее пространство шаблонов", + templatesWorkspaceDesc: "Создавайте переиспользуемые конфигурации эпизодов и говорящих для быстрого производства подкастов.", + howTemplatesPowerTitle: "Как шаблоны ускоряют генерацию подкастов", + howTemplatesPowerDesc: "Шаблоны разделяют процесс на два переиспользуемых компонента. Комбинируйте их при генерации нового эпизода.", + episodeProfilesSetFormat: "Профили эпизодов задают формат", + episodeProfilesList1: "Определяют количество сегментов и структуру повествования", + episodeProfilesList2: "Выбирают языковые модели для брифинга, планирования и написания сценария", + episodeProfilesList3: "Хранят стандартные брифинги для единообразного стиля эпизодов", + speakerProfilesBringVoices: "Профили говорящих оживляют голоса", + speakerProfilesList1: "Выбирают провайдера и модель озвучивания", + speakerProfilesList2: "Фиксируют личность, биографию и заметки о произношении для каждого говорящего", + speakerProfilesList3: "Переиспользуют одни и те же голоса ведущих или гостей в разных форматах эпизодов", + recommendedWorkflow: "Рекомендуемый рабочий процесс", + workflowStep1: "Создайте профили говорящих для каждого нужного голоса", + workflowStep2: "Создайте профили эпизодов со ссылками на говорящих по имени", + workflowStep3: "Генерируйте подкасты, выбирая подходящий профиль эпизода", + workflowHint: "Профили эпизодов ссылаются на профили говорящих по имени, поэтому начинайте с говорящих, чтобы избежать пропущенных назначений голосов.", + failedToLoadTemplates: "Не удалось загрузить данные шаблонов", + failedToLoadTemplatesDesc: "Убедитесь, что API работает, и попробуйте снова. Некоторые разделы могут быть неполными.", + loadingTemplates: "Загрузка шаблонов…", + speakerProfilesTitle: "Профили говорящих", + speakerProfilesDesc: "Настройте голоса и личности для генерируемых эпизодов.", + createSpeaker: "Создать говорящего", + noSpeakerProfiles: "Пока нет профилей говорящих. Создайте один, чтобы шаблоны эпизодов стали доступны.", + noDescription: "Описание не указано.", + usedByCount_one: "Используется в 1 эпизоде", + usedByCount_other: "Используется в {count} эпизодах", + usedByCount: "Используется в {count} эпизодах", + unused: "Не используется", + voiceId: "ID голоса", + backstory: "Биография", + personality: "Личность", + edit: "Редактировать", + duplicate: "Дублировать", + deleteSpeakerProfileTitle: "Удалить профиль говорящего?", + deleteSpeakerProfileDesc: "Удаление «{name}» нельзя отменить.", + deleteSpeakerDisabledHint: "Сначала удалите этого говорящего из профилей эпизодов.", + deleting: "Удаление…", + episodeProfilesTitle: "Профили эпизодов", + episodeProfilesDesc: "Определите переиспользуемые настройки генерации для ваших шоу.", + createProfile: "Создать профиль", + createSpeakerFirst: "Сначала создайте профиль говорящего перед добавлением профиля эпизода.", + noEpisodeProfiles: "Пока нет профилей эпизодов. Создайте один для запуска генерации подкастов.", + speakerCreated: "Говорящий создан", + speakerCreatedDesc: "Говорящий «{name}» успешно добавлен.", + failedToCreateSpeaker: "Не удалось создать профиль говорящего", + speakerUpdated: "Говорящий обновлён", + speakerUpdatedDesc: "Говорящий «{name}» успешно обновлён.", + failedToUpdateSpeaker: "Не удалось обновить профиль говорящего", + speakerDeleted: "Говорящий удалён", + speakerDeletedDesc: "Говорящий «{name}» успешно удалён.", + failedToDeleteSpeaker: "Не удалось удалить профиль говорящего", + speakerDuplicated: "Говорящий дублирован", + speakerDuplicatedDesc: "Говорящий «{name}» успешно дублирован.", + failedToDuplicateSpeaker: "Не удалось дублировать профиль говорящего", + generationStarted: "Генерация запущена", + generationStartedDesc: "Генерация подкаста поставлена в очередь.", + failedToStartGeneration: "Не удалось запустить генерацию", + tryAgainMoment: "Попробуйте ещё раз через некоторое время.", + deleteProfileTitle: "Удалить профиль?", + deleteProfileDesc: "Это удалит «{name}». Существующие эпизоды сохранят свои данные, но новые больше не смогут использовать эту конфигурацию.", + profileCreated: "Профиль создан", + profileCreatedDesc: "Профиль эпизода «{name}» успешно создан.", + failedToCreateProfile: "Не удалось создать профиль", + profileUpdated: "Профиль обновлён", + profileUpdatedDesc: "Профиль эпизода «{name}» успешно обновлён.", + failedToUpdateProfile: "Не удалось обновить профиль", + profileDeleted: "Профиль удалён", + profileDeletedDesc: "Профиль эпизода «{name}» успешно удалён.", + failedToDeleteProfile: "Не удалось удалить профиль", + failedToDeleteProfileDesc: "Не удалось удалить профиль эпизода.", + profileDuplicated: "Профиль дублирован", + profileDuplicatedDesc: "Профиль эпизода «{name}» успешно дублирован.", + failedToDuplicateProfile: "Не удалось дублировать профиль", + episodeDeleted: "Эпизод удалён", + episodeDeletedDesc: "Эпизод успешно удалён.", + failedToDeleteEpisode: "Не удалось удалить эпизод", + failedToDeleteSpeakerDesc: "Не удалось удалить профиль говорящего.", + outlineModel: "Модель плана", + transcriptModel: "Модель транскрипта", + segments: "Сегменты", + defaultBriefingTitle: "Брифинг по умолчанию", + created: "Создано {time}", + details: "Детали", + summaryTab: "Краткое", + outlineTab: "План", + transcriptTab: "Транскрипт", + briefing: "Брифинг", + noOutline: "План недоступен.", + noTranscript: "Транскрипт недоступен.", + deleteEpisodeTitle: "Удалить эпизод?", + deleteEpisodeDesc: "Это навсегда удалит «{name}» и его аудиофайл.", + audioUnavailable: "Аудио недоступно", + segment: "Сегмент", + speaker: "Говорящий", + profile: "Профиль", + link: "Ссылка", + file: "Файл", + embedded: "С эмбеддингом", + notEmbedded: "Без эмбеддинга", + noSpeakerProfilesAvailable: "Нет доступных профилей говорящих", + noLanguageModelsAvailable: "Нет доступных языковых моделей", + editEpisodeProfile: "Редактировать профиль эпизода", + createEpisodeProfile: "Создать профиль эпизода", + episodeProfileFormDesc: "Определите, как должны генерироваться эпизоды и какую конфигурацию говорящих использовать по умолчанию.", + noSpeakerProfilesDesc: "Создайте профиль говорящего перед настройкой профиля эпизода.", + noLanguageModelsDesc: "Добавьте языковые модели в разделе «Модели» для настройки генерации плана и транскрипта.", + profileName: "Название профиля", + profileNamePlaceholder: "напр., Техническая дискуссия", + descriptionPlaceholder: "Краткое описание, когда использовать этот профиль", + speakerConfig: "Конфигурация говорящих", + selectSpeakerProfile: "Выберите профиль говорящего", + outlineGeneration: "Генерация плана", + transcriptGeneration: "Генерация транскрипта", + defaultBriefingPlaceholder: "Опишите структуру, тон и цели для этого формата эпизода", + editSpeakerProfile: "Редактировать профиль говорящего", + createSpeakerProfile: "Создать профиль говорящего", + speakerProfileFormDesc: "Настройте параметры озвучивания и определите до четырёх говорящих.", + noTtsModelsAvailable: "Нет доступных моделей озвучивания", + noTtsModelsDesc: "Добавьте TTS-модели в разделе «Модели» перед созданием профиля говорящего.", + speakers: "Говорящие", + speakersDesc: "Настройте от одного до четырёх голосов для этого профиля.", + addSpeaker: "Добавить говорящего", + speakerNumber: "Говорящий {number}", + backstoryPlaceholder: "Краткая биография или контекст для говорящего", + personalityPlaceholder: "Опишите стиль и тон", + outlineProviderRequired: "Требуется провайдер плана", + outlineModelRequired: "Требуется модель плана", + transcriptProviderRequired: "Требуется провайдер транскрипта", + transcriptModelRequired: "Требуется модель транскрипта", + defaultBriefingRequired: "Требуется брифинг по умолчанию", + segmentsInteger: "Должно быть целым числом", + segmentsMin: "Минимум 3 сегмента", + segmentsMax: "Максимум 20 сегментов", + voiceIdRequired: "Требуется ID голоса", + backstoryRequired: "Требуется биография", + personalityRequired: "Требуется описание личности", + speakerCountMin: "Требуется минимум один говорящий", + speakerCountMax: "Можно настроить до 4 говорящих", + delete: "Удалить", + unknown: "Неизвестно", + deleteSuccess: "Подкаст успешно удалён", + failedToDelete: "Не удалось удалить подкаст", + }, + settings: { + contentProcessing: "Обработка контента", + contentProcessingDesc: "Настройте обработку документов и URL", + docEngine: "Движок обработки документов", + docEnginePlaceholder: "Выберите движок обработки документов", + urlEngine: "Движок обработки URL", + urlEnginePlaceholder: "Выберите движок обработки URL", + autoRecommended: "Авто (рекомендуется)", + simple: "Простой", + docling: "Docling", + helpMeChoose: "Помогите выбрать", + docHelp: "· Docling немного медленнее, но точнее, особенно для документов с таблицами и изображениями. · Simple извлекает содержимое без форматирования. · Авто (рекомендуется) попробует Docling и переключится на Simple при необходимости.", + firecrawl: "Firecrawl", + jina: "Jina", + urlHelp: "· Firecrawl — платный сервис (есть бесплатный уровень), очень мощный. · Jina тоже хороший вариант с бесплатным уровнем. · Simple использует базовое HTTP-извлечение и пропустит контент на JavaScript-сайтах. · Авто (рекомендуется) попробует Firecrawl, затем Jina, затем Simple.", + embeddingAndSearch: "Эмбеддинг и поиск", + embeddingAndSearchDesc: "Настройте параметры поиска и эмбеддинга", + defaultEmbeddingOption: "Опция эмбеддинга по умолчанию", + embeddingOptionPlaceholder: "Выберите опцию эмбеддинга", + ask: "Спрашивать", + always: "Всегда", + never: "Никогда", + embeddingHelp: "Эмбеддинг контента упрощает поиск для вас и ИИ-агентов. Если вы используете локальную модель эмбеддинга (например, Ollama), можно не беспокоиться о стоимости и создавать эмбеддинг для всего.", + fileManagement: "Управление файлами", + fileManagementDesc: "Настройте обработку и хранение файлов", + autoDeleteFiles: "Автоудаление файлов", + autoDeletePlaceholder: "Выберите опцию автоудаления", + filesHelp: "После загрузки и обработки файлы больше не нужны. Большинству пользователей следует разрешить Open Notebook автоматически удалять загруженные файлы из папки загрузок.", + loadFailed: "Не удалось загрузить настройки", + }, + advanced: { + title: "Дополнительные инструменты", + desc: "Расширенные инструменты и утилиты для опытных пользователей", + systemInfo: "Информация о системе", + systemInfoDesc: "Просмотр состояния системных компонентов", + rebuildEmbeddings: "Пересоздать эмбеддинги", + rebuildEmbeddingsDesc: "Пересоздать индекс векторного поиска для всех источников", + rebuildWarning: "Это действие может занять много времени в зависимости от количества источников. Существующие векторные индексы будут очищены и эмбеддинги будут созданы заново.", + startRebuild: "Начать пересоздание", + rebuilding: "Пересоздание...", + rebuildSuccess: "Пересоздание эмбеддингов успешно запущено", + currentVersion: "Текущая версия", + latestVersion: "Последняя версия", + status: "Статус", + updateAvailable: "Доступна версия {version}", + updateAvailableDesc: "Доступна новая версия Open Notebook.", + upToDate: "Актуальная версия", + unknown: "Неизвестно", + viewOnGithub: "Посмотреть на GitHub", + updateCheckFailed: "Не удалось проверить обновления. GitHub может быть недоступен.", + rebuild: { + mode: "Режим пересоздания", + existing: "Существующие", + all: "Все", + existingDesc: "Пересоздать эмбеддинги только для элементов с существующими эмбеддингами (быстрее, для смены модели)", + allDesc: "Пересоздать существующие + создать эмбеддинги для элементов без них (медленнее, полный охват)", + include: "Включить в пересоздание", + selectOneError: "Выберите хотя бы один тип элементов для пересоздания", + starting: "Запуск пересоздания...", + startBtn: "🚀 Начать пересоздание", + queued: "В очереди", + running: "Отправка задач...", + completed: "Задачи отправлены!", + failed: "Ошибка", + leavePageHint: "Вы можете покинуть эту страницу — процесс выполняется в фоне", + startNew: "Начать новое пересоздание", + itemsProcessed: "Отправлено задач: {processed}/{total} ({percent}%)", + failedItems: "Не удалось отправить задач: {count}", + time: "Время", + whenToRebuild: "Когда нужно пересоздавать эмбеддинги?", + whenToRebuildAns: "При смене модели, обновлении версии, исправлении повреждений или после массового импорта.", + howLong: "Сколько времени занимает пересоздание?", + howLongAns: "Время зависит от количества элементов, скорости модели и лимитов API. Локальные модели обычно очень быстрые.", + isSafe: "Безопасно ли пересоздавать во время работы с приложением?", + isSafeAns: "Да, пересоздание безопасно! Оно не удаляет контент, только заменяет эмбеддинги, и корректно обрабатывает ошибки.", + }, + }, + transformations: { + title: "Трансформации", + desc: "Трансформации — это промпты, которые LLM использует для обработки источника и извлечения инсайтов, резюме и т.д.", + workspace: "Выберите рабочее пространство", + playground: "Песочница", + defaultPrompt: "Промпт трансформации по умолчанию", + defaultPromptDesc: "Этот текст будет добавлен ко всем промптам трансформаций", + defaultPromptPlaceholder: "Введите инструкции трансформации по умолчанию...", + saveDefault: "Сохранить по умолчанию", + listTitle: "Пользовательские трансформации", + createNew: "Создать новую", + testInPlayground: "Тестировать в песочнице", + inputLabel: "Входной текст", + inputPlaceholder: "Введите текст для трансформации...", + outputLabel: "Результат", + runTest: "Запустить трансформацию", + running: "Выполнение...", + selectToStart: "Выберите трансформацию для начала", + name: "Название", + namePlaceholder: "Уникальный идентификатор, напр. key_topics", + titlePlaceholder: "Отображаемое название, по умолчанию совпадает с именем", + promptPlaceholder: "Напишите промпт для этой трансформации...", + descriptionPlaceholder: "Опишите, что делает эта трансформация.", + suggestDefault: "Предлагать по умолчанию для новых источников", + promptHint: "Промпты должны учитывать содержимое источника. Вы можете попросить модель резюмировать, извлечь инсайты или создать структурированный вывод, например таблицы.", + createSuccess: "Трансформация успешно создана", + updateSuccess: "Трансформация успешно обновлена", + deleteSuccess: "Трансформация успешно удалена", + noTransformations: "Пока нет трансформаций", + createOne: "Создайте трансформацию для начала", + deleteDesc: "Удаление трансформации нельзя отменить.", + selectModel: "Выберите модель", + deleteConfirm: "Вы уверены, что хотите удалить эту трансформацию?", + model: "Модель", + systemPrompt: "Системный промпт", + type: "Тип", + extraction: "Извлечение", + summary: "Резюме", + custom: "Пользовательский", + saveChanges: "Сохранить изменения", + overrideModelDesc: "Переопределить модель по умолчанию для этой сессии чата. Оставьте пустым для использования системной модели.", + sessionUseReplacement: "Эта сессия будет использовать {name} вместо модели по умолчанию.", + systemDefault: "Системная по умолчанию", + }, + models: { + title: "Управление моделями", + desc: "Настройте ИИ-модели для различных задач в Open Notebook", + failedToLoad: "Не удалось загрузить данные моделей", + language: "Языковые модели", + embedding: "Модели эмбеддинга", + tts: "Озвучивание (TTS)", + stt: "Распознавание речи (STT)", + providers: "Провайдеры", + defaultModels: "Модели по умолчанию", + status: "Статус", + notConfigured: "Не настроено", + active: "Активно", + inactive: "Неактивно", + configure: "Настроить", + saveChanges: "Сохранить изменения", + addModel: "Добавить модель", + modelName: "Название модели", + provider: "Провайдер", + apiKey: "API-ключ", + baseUrl: "Базовый URL", + capabilities: "Возможности", + enabled: "Включено", + disabled: "Отключено", + deleteConfirm: "Вы уверены, что хотите удалить эту модель?", + deleteSuccess: "Модель успешно удалена", + saveSuccess: "Модель успешно сохранена", + providerStatus: "Статус провайдера", + connectionOk: "Подключение OK", + connectionFailed: "Ошибка подключения", + changeEmbeddingWarning: "Изменение модели эмбеддинга по умолчанию повлияет на новые источники. Существующие источники могут потребовать переиндексации.", + changeEmbeddingTitle: "Изменить модель эмбеддинга по умолчанию?", + aiProviders: "ИИ-провайдеры", + providerConfigDesc: "Настройте провайдеров через переменные окружения для включения их моделей.", + configuredCount: "Настроено {count} из {total}", + noModels: "Нет моделей", + learnMore: "Узнать, как настроить провайдеров →", + seeLess: "Свернуть", + seeAll: "Показать все {count} провайдеров", + language_models: "Языковые модели", + embedding_models: "Модели эмбеддинга", + text_to_speech: "Озвучивание (TTS)", + speech_to_text: "Распознавание речи (STT)", + languageDesc: "Чат, трансформации и генерация текста", + embeddingDesc: "Семантический поиск и векторные эмбеддинги", + ttsDesc: "Генерация аудио из текста", + sttDesc: "Транскрибация аудио в текст", + all: "Все", + noModelsConfigured: "Модели не настроены", + noProviderModelsConfigured: "Модели {provider} не настроены", + showMore: "Показать ещё {count}", + deleteModel: "Удалить модель", + deleteModelDesc: "Вы уверены, что хотите удалить «{name}»? Это действие нельзя отменить.", + defaultAssignments: "Назначение моделей по умолчанию", + defaultAssignmentsDesc: "Настройте, какие модели использовать для различных задач в Open Notebook", + missingRequiredModels: "Отсутствуют необходимые модели: {models}. Open Notebook может работать некорректно без них.", + selectModelPlaceholder: "Выберите модель", + requiredModelPlaceholder: "⚠️ Обязательно — выберите модель", + whichModelToChoose: "Какую модель выбрать? →", + chatModelLabel: "Модель чата", + chatModelDesc: "Используется для чат-разговоров", + transformationModelLabel: "Модель трансформаций", + transformationModelDesc: "Используется для резюме, инсайтов и трансформаций", + toolsModelLabel: "Модель инструментов", + toolsModelDesc: "Используется для вызова функций — рекомендуется OpenAI или Anthropic", + largeContextModelLabel: "Модель для большого контекста", + largeContextModelDesc: "Используется для обработки больших документов — рекомендуется Gemini", + embeddingModelLabel: "Модель эмбеддинга", + embeddingModelDesc: "Используется для семантического поиска и векторных эмбеддингов", + ttsModelLabel: "Модель озвучивания", + ttsModelDesc: "Используется для генерации подкастов", + sttModelLabel: "Модель распознавания речи", + sttModelDesc: "Используется для транскрибации аудио", + addSpecificModel: "Добавить модель {type}", + addSpecificModelDesc: "Настройте новую модель {type} от доступных провайдеров.", + noProvidersForType: "Нет доступных провайдеров для моделей {type}", + selectProviderPlaceholder: "Выберите провайдера", + providerRequired: "Требуется провайдер", + modelNameRequired: "Требуется название модели", + modelRequired: "Требуется модель", + adding: "Добавление...", + azureHint: "Для Azure используйте название деплоймента как название модели", + enterModelName: "Введите название модели", + embeddingChangeTitle: "Изменение модели эмбеддинга", + embeddingChangeConfirm: "Вы собираетесь изменить модель эмбеддинга с {from} на {to}.", + rebuildRequired: "Важно: Требуется пересоздание", + rebuildReason: "Изменение модели эмбеддинга требует пересоздания всех существующих эмбеддингов для сохранения согласованности. Без пересоздания поиск может возвращать некорректные или неполные результаты.", + whatHappensNext: "Что произойдёт далее:", + step1: "Модель эмбеддинга по умолчанию будет обновлена", + step2: "Существующие эмбеддинги останутся неизменными до пересоздания", + step3: "Новый контент будет использовать новую модель эмбеддинга", + step4: "Вам следует пересоздать эмбеддинги как можно скорее", + proceedToRebuildPrompt: "Хотите перейти на страницу «Дополнительно», чтобы начать пересоздание сейчас?", + changeModelOnly: "Только изменить модель", + changeAndRebuild: "Изменить и перейти к пересозданию", + } +} diff --git a/frontend/src/lib/locales/zh-CN/index.ts b/frontend/src/lib/locales/zh-CN/index.ts index 23ef420..408dbd2 100644 --- a/frontend/src/lib/locales/zh-CN/index.ts +++ b/frontend/src/lib/locales/zh-CN/index.ts @@ -28,6 +28,7 @@ export const zhCN = { english: "English", chinese: "简体中文", japanese: "日本語", + russian: "Русский", source: "来源", notebook: "笔记本", podcast: "播客", diff --git a/frontend/src/lib/locales/zh-TW/index.ts b/frontend/src/lib/locales/zh-TW/index.ts index e2b16b7..07fc601 100644 --- a/frontend/src/lib/locales/zh-TW/index.ts +++ b/frontend/src/lib/locales/zh-TW/index.ts @@ -28,6 +28,7 @@ export const zhTW = { english: "English", chinese: "簡體中文", japanese: "日本語", + russian: "Русский", source: "來源", notebook: "筆記本", podcast: "播客", diff --git a/frontend/src/lib/utils/date-locale.ts b/frontend/src/lib/utils/date-locale.ts index 8724ec0..4fd976d 100644 --- a/frontend/src/lib/utils/date-locale.ts +++ b/frontend/src/lib/utils/date-locale.ts @@ -1,4 +1,4 @@ -import { zhCN, enUS, zhTW, ptBR, ja, Locale } from 'date-fns/locale' +import { zhCN, enUS, zhTW, ptBR, ja, ru, Locale } from 'date-fns/locale' /** * Mapping of language codes to date-fns locales. @@ -10,6 +10,7 @@ const LOCALE_MAP: Record = { 'en-US': enUS, 'pt-BR': ptBR, 'ja-JP': ja, + 'ru-RU': ru, } /**