diff --git a/app/src/main/java/org/dueattendant149/bookshelf/core/data/CoreData.kt b/app/src/main/java/org/dueattendant149/bookshelf/core/data/CoreData.kt index 3ce07ef5..21b68626 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/core/data/CoreData.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/core/data/CoreData.kt @@ -10,8 +10,9 @@ import kotlinx.collections.immutable.toPersistentList import org.dueattendant149.bookshelf.core.language.Language object CoreData { - val defaultLanguage = Language.fromLanguageTag(languageTag = "en") // English + val defaultLanguage = Language.fromLanguageTag(languageTag = "ru") // Russian val languages = listOf( + Language.fromLanguageTag(languageTag = "ru"), // Russian Language.fromLanguageTag(languageTag = "en"), // English Language.fromLanguageTag(languageTag = "uk"), // Ukrainian Language.fromLanguageTag(languageTag = "de"), // German diff --git a/app/src/main/java/org/dueattendant149/bookshelf/data/remote/bookshelfapi/BookshelfApiService.kt b/app/src/main/java/org/dueattendant149/bookshelf/data/remote/bookshelfapi/BookshelfApiService.kt index 44520196..3d94c0e2 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/data/remote/bookshelfapi/BookshelfApiService.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/data/remote/bookshelfapi/BookshelfApiService.kt @@ -35,28 +35,31 @@ interface BookshelfApiService { // Health @GET("health") - suspend fun health(): Response + suspend fun health(): Response // Libraries / books @GET("api/v1/books/libraries") suspend fun getLibraries(): Response> + @Streaming @GET("api/v1/books/{itemId}") suspend fun getBook( @Path("itemId") itemId: String, - ): Response + ): Response + @Streaming @POST("api/v1/books/{itemId}/progress") suspend fun updateReadingProgress( @Path("itemId") itemId: String, @Body request: ProgressUpdateRequest, - ): Response + ): Response + @Streaming @POST("api/v1/books/{itemId}/playback-progress") suspend fun updatePlaybackProgress( @Path("itemId") itemId: String, @Body request: PlaybackProgressUpdateRequest, - ): Response + ): Response @GET("api/v1/books/library/{libraryId}/search") suspend fun searchLibrary( diff --git a/app/src/main/java/org/dueattendant149/bookshelf/data/settings/ServerSettings.kt b/app/src/main/java/org/dueattendant149/bookshelf/data/settings/ServerSettings.kt index 05ed56a8..2ee0ca81 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/data/settings/ServerSettings.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/data/settings/ServerSettings.kt @@ -8,6 +8,7 @@ package org.dueattendant149.bookshelf.data.settings import androidx.datastore.preferences.core.stringPreferencesKey import org.dueattendant149.bookshelf.data.local.data_store.DataStore +import org.dueattendant149.bookshelf.domain.util.ensureUriScheme import org.dueattendant149.bookshelf.domain.util.isValidUri import org.dueattendant149.bookshelf.domain.util.normalizeUri import javax.inject.Inject @@ -47,11 +48,12 @@ class ServerSettings * or null when all credentials are present. */ suspend fun getMissingCredentialsError(): String? { - val bookshelfUrl = dataStore.getNullableData(bookshelfUrlKey) + val bookshelfUrl = dataStore.getNullableData(bookshelfUrlKey)?.trim() if (bookshelfUrl.isNullOrBlank()) { return "Bookshelf API URL is missing" } - if (!bookshelfUrl.isValidUri()) { + val normalizedBookshelfUrl = bookshelfUrl.ensureUriScheme() + if (!normalizedBookshelfUrl.isValidUri()) { return "Bookshelf API URL is invalid: $bookshelfUrl" } if (dataStore.getNullableData(absUrlKey).isNullOrBlank()) { @@ -67,8 +69,8 @@ class ServerSettings val url = dataStore.getNullableData(bookshelfUrlKey)?.trim() return when { url.isNullOrBlank() -> Result.failure(IllegalArgumentException("Bookshelf URL is empty")) - !url.isValidUri() -> Result.failure(IllegalArgumentException("Bookshelf URL is invalid: $url")) - else -> Result.success(url.normalizeUri()) + !url.ensureUriScheme().isValidUri() -> Result.failure(IllegalArgumentException("Bookshelf URL is invalid: $url")) + else -> Result.success(url.ensureUriScheme().normalizeUri()) } } @@ -80,6 +82,8 @@ class ServerSettings private fun String.normalizeIfValid(): String? { val trimmed = trim() - return if (trimmed.isValidUri()) trimmed.normalizeUri() else null + if (trimmed.isBlank()) return null + val withScheme = trimmed.ensureUriScheme() + return if (withScheme.isValidUri()) withScheme.normalizeUri() else null } } diff --git a/app/src/main/java/org/dueattendant149/bookshelf/domain/util/UriUtils.kt b/app/src/main/java/org/dueattendant149/bookshelf/domain/util/UriUtils.kt index b81b0c9c..a7876fc2 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/domain/util/UriUtils.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/domain/util/UriUtils.kt @@ -20,7 +20,7 @@ fun String.isValidUri(): Boolean { /** * Returns true if the string starts with http:// or https:// (case-insensitive). */ -fun String.hasUriScheme(): Boolean = matches(URL_SCHEME_REGEX) +fun String.hasUriScheme(): Boolean = URL_SCHEME_REGEX.containsMatchIn(this) /** * Adds https:// if the string has no http/https scheme. diff --git a/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt index ccee3aba..365cf4b8 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/presentation/settings/BookshelfSettingsModel.kt @@ -18,6 +18,8 @@ import org.dueattendant149.bookshelf.domain.model.remote.RemoteLibrary import org.dueattendant149.bookshelf.domain.use_case.remote.CheckHealthUseCase import org.dueattendant149.bookshelf.domain.use_case.remote.FetchLibrariesUseCase import org.dueattendant149.bookshelf.domain.util.deriveAbsUrl +import org.dueattendant149.bookshelf.domain.util.ensureUriScheme +import org.dueattendant149.bookshelf.domain.util.normalizeUri import javax.inject.Inject data class BookshelfSettingsState( @@ -108,8 +110,20 @@ class BookshelfSettingsModel fun save() { viewModelScope.launch { val current = _state.value - serverSettings.setBookshelfUrl(current.bookshelfUrl) - serverSettings.setAbsUrl(current.absUrl) + val normalizedBookshelfUrl = current.bookshelfUrl.trim().let { + if (it.isNotBlank()) it.ensureUriScheme().normalizeUri() else it + } + val normalizedAbsUrl = current.absUrl.trim().let { + if (it.isNotBlank()) it.ensureUriScheme().normalizeUri() else it + } + _state.update { + it.copy( + bookshelfUrl = normalizedBookshelfUrl, + absUrl = normalizedAbsUrl, + ) + } + serverSettings.setBookshelfUrl(normalizedBookshelfUrl) + serverSettings.setAbsUrl(normalizedAbsUrl) serverSettings.setAbsToken(current.absToken) } } diff --git a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt index d3dc34fb..c07c7e73 100644 --- a/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt +++ b/app/src/main/java/org/dueattendant149/bookshelf/ui/settings/bookshelf/BookshelfSettingsContent.kt @@ -73,7 +73,7 @@ fun BookshelfSettingsContent( value = state.bookshelfUrl, onValueChange = model::updateBookshelfUrl, label = { Text(stringResource(R.string.bookshelf_url_option)) }, - placeholder = { Text("http://bookshelf-api:8073") }, + placeholder = { Text("https://tts.dueattendant149.org") }, supportingText = { Text(stringResource(R.string.bookshelf_url_option_desc)) }, modifier = Modifier.fillMaxWidth() ) @@ -83,7 +83,7 @@ fun BookshelfSettingsContent( value = state.absUrl, onValueChange = model::updateAbsUrl, label = { Text(stringResource(R.string.abs_url_option)) }, - placeholder = { Text("http://192.168.1.119:13378") }, + placeholder = { Text("https://books.dueattendant149.org") }, supportingText = { Text(stringResource(R.string.abs_url_option_desc)) }, modifier = Modifier.fillMaxWidth() ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20890bad..2f1abed5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,577 +7,577 @@ Book\'s Story - Default + По умолчанию - Audio playback - Shows the currently playing audiobook - No track - -10s - +30s - Previous - Next - Play - Pause + Воспроизведение аудио + Показывает текущую аудиокнигу + Нет трека + -10с + +30с + Предыдущий + Следующий + Играть + Пауза - Library - History - Browse - Settings - About app + Библиотека + История + Обзор + Настройки + О приложении - Add books? - Delete books? - Delete book? - Delete reading history? - Remove category? + Добавить книги? + Удалить книги? + Удалить книгу? + Удалить историю чтения? + Удалить категорию? - You can choose which books you want to add. - You can edit Title and Cover image of the book later. - The loading process can take up to several minutes. + Вы можете выбрать книги, которые хотите добавить. + Название и обложку можно изменить позже. + Загрузка может занять несколько минут. - It will delete all selected books(%1$s) from the database. - It will not delete selected books from your device. + Это удалит все выбранные книги (%1$s) из базы данных. + Файлы на устройстве не будут удалены. - It will delete this book from the database. - It will not delete this book from your device. + Это удалит книгу из базы данных. + Файл на устройстве не будет удалён. - It will delete the whole history from the database. - After deleting the whole history, the order of the books will be reset. + Это удалит всю историю из базы данных. + После удаления порядок книг будет сброшен. - This will remove this category from the Library but will not delete any books. + Категория будет удалена из библиотеки, но книги не будут удалены. - No description. - Something went wrong. - No translator app found. - No dictionary app found. - No browser app found. - No app to share found. - No app to choose folder found. + Нет описания. + Что-то пошло не так. + Приложение-переводчик не найдено. + Словарь не найден. + Браузер не найден. + Нет приложения для отправки. + Нет приложения для выбора папки. - File is empty, missing, or inaccessible. - Verify the file, scan settings or change the path, then try again. + Файл пуст, отсутствует или недоступен. + Проверьте файл, настройки сканирования или измените путь. - Could not reset cover image. - Closed source - File does not exist or inaccessible - This field should not be empty. + Не удалось сбросить обложку. + Закрытый исходный код + Файл не существует или недоступен + Поле не должно быть пустым. - Search… + Поиск… - Expand the library\'s book collection with your downloads + Расширьте коллекцию книг, загрузив новые файлы - Read your favorite books and sort them between categories + Читайте любимые книги и сортируйте их по категориям - Let your last book you read open the pages of a new great story of your reading journey + Откройте последнюю книгу и продолжите чтение - No categories. + Нет категорий. - %1$s selected - Preset %1$s - Selected «%1$s» + %1$s выбрано + Пресет %1$s + Выбран «%1$s» - Cancel - Add a book - Move - Delete - Never - Undo - Copy - Report - Translate - Dictionary - Next - Done - Web search - Share - OK - Read - Change path - Add folder - Edit - Create category - Set up scanning + Отмена + Добавить книгу + Переместить + Удалить + Никогда + Отменить + Копировать + Сообщить + Перевести + Словарь + Далее + Готово + Поиск в сети + Поделиться + ОК + Читать + Изменить путь + Добавить папку + Изменить + Создать категорию + Настроить сканирование - Unknown - Reset cover image - Reset cover image of the book to the default - Change cover image - Choose new cover from your photos - Delete cover image - Delete current cover image of the book - Author + Неизвестен + Сбросить обложку + Сбросить обложку книги по умолчанию + Изменить обложку + Выбрать новую обложку из фото + Удалить обложку + Удалить текущую обложку книги + Автор - Default - General - Display - Colors - Filter - Sort - Reader + По умолчанию + Общие + Отображение + Цвета + Фильтр + Сортировка + Читалка - General - App language - Appearance - Theme, colors - Reader - Font, text - Browse - Scan, display - Library - Categories, display, tabs - Bookshelf - Server, ABS, TTS + Общие + Язык приложения + Внешний вид + Тема, цвета + Читалка + Шрифт, текст + Обзор + Сканирование, отображение + Библиотека + Категории, отображение, вкладки + Книжная полка + Сервер, ABS, TTS TTS - Engine - Voice - Book ID - Speed - Create job - Refresh jobs - Jobs - TTS jobs + Движок + Голос + ID книги + Скорость + Создать задачу + Обновить задачи + Задачи + Задачи TTS TTS - Bookshelf server - Bookshelf API URL - Base URL of the bookshelf-api service - Audiobookshelf URL - Direct URL of the Audiobookshelf server - ABS token - API token from Audiobookshelf settings - Save - Check connection - Derive ABS URL - Fetch libraries - Connection successful - Connected to %1$d libraries - Libraries - %1$s • %2$d items - Selected - Books in %1$s - No remote libraries found - Refresh - Retry + Сервер книжной полки + URL Bookshelf API + Базовый URL сервиса bookshelf-api + URL Audiobookshelf + Прямой URL сервера Audiobookshelf + Токен ABS + API-токен из настроек Audiobookshelf + Сохранить + Проверить соединение + Получить ABS URL + Загрузить библиотеки + Соединение установлено + Подключено к %1$d библиотекам + Библиотеки + %1$s • %2$d элементов + Выбрано + Книги в %1$s + Удалённые библиотеки не найдены + Обновить + Повторить - Font - Text - Translator - Margins - Misc - Reading speed - Reading mode - System - Chapters - Images - Progress - Theme preferences - Colors - Scan - Display - Tabs - Categories - Filter - Sort + Шрифт + Текст + Переводчик + Поля + Прочее + Скорость чтения + Режим чтения + Система + Главы + Изображения + Прогресс + Настройки темы + Цвета + Сканирование + Отображение + Вкладки + Категории + Фильтр + Сортировка - These folders are used to scan and retrieve files in the Browse. - Removing a folder here will not delete actual files or books from the Library. - To revoke access, click the corresponding button. + Эти папки используются для сканирования файлов в Обзоре. + Удаление папки не удалит сами файлы или книги из Библиотеки. + Чтобы отозвать доступ, нажмите соответствующую кнопку. - Initially, books are added to the default category, - but can belong to several custom categories created here. - Removing a category here will not delete books from the Library. - Hold and move the category icon to rearrange the categories. + Изначально книги добавляются в категорию по умолчанию, + но могут принадлежать нескольким категориям, созданным здесь. + Удаление категории не удалит книги из Библиотеки. + Удерживайте и перетаскивайте иконку категории для изменения порядка. - Sorting can be changed in the Library. + Сортировку можно изменить в Библиотеке. - App language - Dark theme - Pure dark - Absolute dark - Change background color to be completely black - Theme contrast - App theme - Double back press to exit - Press back twice to exit the app - Show navigation labels - Show navigation bar labels under icons + Язык приложения + Тёмная тема + Полностью тёмная + Абсолютно тёмная + Сделать цвет фона полностью чёрным + Контраст темы + Тема приложения + Двойное нажатие для выхода + Нажмите «Назад» дважды для выхода + Подписи навигации + Показывать подписи под иконками навигации - Font family - Font thickness - Font style - Font size - Line height - Paragraph spacing - Paragraph indentation - Color preset - Background color - Font color - Side margins - Vertical margins - Double click translation - Double click on paragraph to translate it - Fast color preset change - Swipe top bar left or right to change color preset - Text alignment - Chapter title alignment - Letter spacing - Cutout margin - Apply margin to the cutout area - Fullscreen - Hide system bars and margins - Keep screen on - Hide bars on fast scroll - Perception expander + Гарнитура шрифта + Толщина шрифта + Начертание шрифта + Размер шрифта + Межстрочный интервал + Интервал между абзацами + Отступ абзаца + Цветовой пресет + Цвет фона + Цвет шрифта + Боковые поля + Вертикальные поля + Перевод по двойному клику + Двойной клик по абзацу для перевода + Быстрая смена пресета + Свайп верхней панели для смены пресета + Выравнивание текста + Выравнивание заголовка главы + Межбуквенный интервал + Отступ выреза + Отступ для области выреза экрана + Полноэкранный режим + Скрыть системные панели и поля + Не выключать экран + Скрыть панели при быстрой прокрутке + Тренажёр скорости - Increase your reading speed by focusing on center + Увеличьте скорость чтения, фокусируясь на центре - Line side margin - Line thickness - Horizontal limiter + Боковой отступ линии + Толщина линии + Горизонтальный ограничитель - Show horizontal ruler and dim unfocused part of the screen + Показывать линейку и затемнять нефокусируемую часть экрана - Height - Vertical offset - Horizontal ruler - Ruler thickness - Dimming - Screen orientation - Custom brightness - Brightness - Horizontal gesture - Scroll fraction - Sensitivity - Visibility animation + Высота + Вертикальное смещение + Горизонтальная линейка + Толщина линейки + Затемнение + Ориентация экрана + Своя яркость + Яркость + Горизонтальный жест + Доля прокрутки + Чувствительность + Анимация видимости - Enable visibility animation when using horizontal gesture + Анимация видимости при горизонтальном жесте - Pull animation + Анимация подтягивания - Enable pull animation when using horizontal gesture + Анимация подтягивания при горизонтальном жесте - Disable scrolling - Completely disable reader scrolling - Bottom bar margin - Highlight words - Highlight the beginning of each word - Highlight thickness - Images - Display images - Captions - Display captions under images - Corners roundness - Alignment - Size - Color effects - Progress bar - Display progress bar at the bottom - Margins - Alignment - Font size - Progress count + Отключить прокрутку + Полностью отключить прокрутку читалки + Отступ нижней панели + Подсветка слов + Подсвечивать начало каждого слова + Толщина подсветки + Изображения + Показывать изображения + Подписи + Показывать подписи под изображениями + Скругление углов + Выравнивание + Размер + Цветовые эффекты + Индикатор прогресса + Показывать индикатор прогресса внизу + Отступы + Выравнивание + Размер шрифта + Отсчёт прогресса - Show «Read» button - Show progress - Title position - Show book count - Show category tabs - Always show default tab - Per-category sort + Показывать кнопку «Читать» + Показывать прогресс + Позиция заголовка + Показывать количество книг + Показывать вкладки категорий + Всегда показывать вкладку по умолчанию + Сортировка по категориям - Red - Green - Blue - Name a preset… + Красный + Зелёный + Синий + Название пресета… - Disabled - Enabled - System + Выключено + Включено + Как в системе - Disabled - Enabled - Saver + Выключено + Включено + В режиме экономии - Standard - Medium - High + Стандартный + Средний + Высокий - Italic - Normal + Курсив + Обычный - Hidden - Below - Inside + Скрыт + Снизу + Внутри - Title + Название - Display mode - List - Grid + Режим отображения + Список + Сетка - Alphabetically - File format - Last modified - File size - Last read - Progress - Author + По алфавиту + Формат файла + Последнее изменение + Размер файла + Последнее чтение + Прогресс + Автор - Grid size - Auto - per row + Размер сетки + Авто + в ряд - Start - Justify - Center - End + По началу + По ширине + По центру + По концу - Free - Portrait - Landscape - Locked portrait - Locked landscape + Свободная + Портретная + Альбомная + Заблокированная портретная + Заблокированная альбомная - Off - On - On (Inverse) + Выключено + Включено + Включено (инверсия) - level + уровень - Off - Grayscale - Font color - Background color + Выключено + Оттенки серого + Цвет шрифта + Цвет фона - Thin - Extra light - Light - Normal - Medium + Тонкий + Очень светлый + Светлый + Обычный + Средний - Percentage - Quantity + Проценты + Количество - Mercury - Neptune - Earth - Io - Enceladus - Mars - Callisto - Jupiter - Ceres - Saturn - Ganymede - Eris - Pluto - Venus - Makemake - Uranus - Iapetus + Меркурий + Нептун + Земля + Ио + Энцелад + Марс + Каллисто + Юпитер + Церера + Сатурн + Ганимед + Эрида + Плутон + Венера + Макемаке + Уран + Япет - Press back again to exit - All selected books were successfully added. - All selected books were successfully moved. - All selected books were successfully deleted. - Cover Image was successfully changed. - Cover Image was successfully deleted. - This book was successfully deleted. - This book was successfully moved. - Copied - History was successfully deleted. - Cover image was successfully reset. - Title was successfully changed. - Author was successfully changed. - Description was successfully changed. - File path was successfully changed. + Нажмите «Назад» ещё раз для выхода + Все выбранные книги добавлены. + Все выбранные книги перемещены. + Все выбранные книги удалены. + Обложка изменена. + Обложка удалена. + Книга удалена. + Книга перемещена. + Скопировано + История удалена. + Обложка сброшена. + Название изменено. + Автор изменён. + Описание изменено. + Путь к файлу изменён. - History element was successfully deleted. + Элемент истории удалён. - Start - Continue from %1$s - File details - Path - Last opened - Size - Unknown + Начать + Продолжить с %1$s + Детали файла + Путь + Последнее открытие + Размер + Неизвестно - No chapters - Chapters + Нет глав + Главы - App version - Report a bug - Open source licenses - Credits - Help translate - Support development - Contributors - Slava Ukraini! + Версия приложения + Сообщить об ошибке + Открытые лицензии + Благодарности + Помочь с переводом + Поддержать разработку + Участники + Слава Україні! Reddit GitHub GitLab Codeberg - Tryzub + Тризуб - Design - Icons - New updates managing - Fonts - Translation - Contribution - Ideas + Дизайн + Иконки + Управление обновлениями + Шрифты + Перевод + Вклад + Идеи - Welcome to %1$s! + Добро пожаловать в %1$s! - To a world where words charm, - teach and lead on incredible adventures! - Let\'s set some things up! + В мир, где слова чаруют, + учат и ведут в невероятные приключения! + Давайте всё настроим! - Language preferences - Theme preferences - Scan preferences - Everything is done! - Setup completed. + Языковые настройки + Настройки темы + Настройки сканирования + Всё готово! + Настройка завершена. - Today - Yesterday + Сегодня + Вчера - Oops! The app crashed. - Please report this crash message in the most convenient way for you. + Ой! Приложение завершилось с ошибкой. + Пожалуйста, сообщите об ошибке удобным способом. - Continue reading - Go back - Cover image - Reader settings - File - Sort order - Checkbox - Cover image not found - Error - Search - Clear selected items - Add files - Select all files - Select all books - Exit search - Delete whole history - Move selected books - Delete selected books - Revert - Selected - App icon - Delete history element - Your library - Reading history - Add books - Bookshelf - Remote bookshelf - Offline only - Pending - Downloading - Cached - Failed - Download - Delete - Play - Failed to sync reading progress - Failed to sync playback progress - Arrow - Reset start guide - Open in web - Delete color preset - Create new color preset - Shuffle colors - Drag - Filter - Checkpoint back - Checkpoint forward - Chapters - More - Pin - Show less - Show more - Edit - Expand - Collapse - Remove - Note + Продолжить чтение + Назад + Обложка + Настройки читалки + Файл + Порядок сортировки + Чекбокс + Обложка не найдена + Ошибка + Поиск + Очистить выбранные элементы + Добавить файлы + Выбрать все файлы + Выбрать все книги + Выйти из поиска + Удалить всю историю + Переместить выбранные книги + Удалить выбранные книги + Отменить + Выбрано + Иконка приложения + Удалить элемент истории + Ваша библиотека + История чтения + Добавить книги + Книжная полка + Удалённая книжная полка + Только оффлайн + Ожидание + Загрузка + В кэше + Ошибка + Скачать + Удалить + Играть + Не удалось синхронизировать прогресс чтения + Не удалось синхронизировать прогресс воспроизведения + Стрелка + Сбросить начальную настройку + Открыть в браузере + Удалить цветовой пресет + Создать цветовой пресет + Перемешать цвета + Перетащить + Фильтр + Контрольная точка назад + Контрольная точка вперёд + Главы + Ещё + Закрепить + Свернуть + Развернуть + Изменить + Раскрыть + Свернуть + Удалить + Заметка - Status: %1$s - Progress: %1$d / %2$d chapters - Saved to: %1$s - Selected - Speed: %1$.2fx - Create TTS job - Book: %1$s + Статус: %1$s + Прогресс: %1$d / %2$d глав + Сохранено в: %1$s + Выбрано + Скорость: %1$.2fx + Создать TTS-задачу + Книга: %1$s %1$s • %2$s - Failed to create TTS job - TTS download failed + Не удалось создать TTS-задачу + Ошибка загрузки TTS \ No newline at end of file