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,
}
/**