Merge pull request #3930 from Yokayo/staging

Update ru-ru translation
This commit is contained in:
Cohee
2025-05-04 14:10:12 +03:00
committed by GitHub
17 changed files with 257 additions and 94 deletions

View File

@ -87,7 +87,7 @@
}
#rm_group_members:empty::before {
content: 'Group is empty';
content: attr(group_empty_text);
font-weight: bolder;
width: 100%;
@ -115,7 +115,7 @@
}
#rm_group_add_members:empty::before {
content: 'No characters available';
content: attr(no_characters_text);
font-weight: bolder;
width: 100%;

View File

@ -2052,8 +2052,8 @@
<div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
<label for="openai_reasoning_effort">
<span data-i18n="Reasoning Effort">Reasoning Effort</span>
<i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i>
<i data-source="claude,makersuite" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i>
<i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level." data-i18n="[title]OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i>
<i data-source="claude,makersuite" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent." data-i18n="[title]Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i>
</label>
<select id="openai_reasoning_effort">
<option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>
@ -2498,8 +2498,8 @@
<option value="search" data-i18n="Search" hidden>A-Z</option>
<option value="asc">A-Z</option>
<option value="desc">Z-A</option>
<option value="date_asc">Date Asc</option>
<option value="date_desc">Date Desc</option>
<option data-i18n="Date Asc" value="date_asc">Date Asc</option>
<option data-i18n="Date Desc" value="date_desc">Date Desc</option>
</select>
<select id="featherless_category_selection" class="text_pole">
<option value="" disabled selected data-i18n="category">category</option>
@ -2509,7 +2509,7 @@
<option value="All" data-i18n="All">All</option>
</select>
<select id="featherless_class_selection" class="text_pole">
<option value="" selected data-i18n="class">All Classes</option>
<option value="" selected data-i18n="All Classes">All Classes</option>
</select>
<div id="featherless_model_pagination_container" class="flex1"></div>
<i id="featherless_model_grid_toggle" class="fa-solid fa-table-cells-large menu_button" data-i18n="[title]Toggle grid view" title="Toggle grid view"></i>
@ -5531,7 +5531,7 @@
<div class="inline-drawer-content">
<div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
<div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div>
<div id="rm_group_members" class="rm_group_members overflowYAuto flex-container"></div>
<div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div>
</div>
</div>
</div>
@ -5549,7 +5549,7 @@
<div class="tags rm_tag_filter"></div>
</div>
<div id="rm_group_add_members_pagination" class="group_pagination"></div>
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
<div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div>
</div>
</div>
</div>
@ -5969,7 +5969,7 @@
<div class="tag_view_color_picker" data-value="color"></div>
<div class="tag_view_color_picker" data-value="color2"></div>
<div class="tag_view_name" contenteditable="true"></div>
<div class="tag_view_counter"><span class="tag_view_counter_value"></span>&nbsp;entries</div>
<div class="tag_view_counter"><span class="tag_view_counter_value"></span>&nbsp;<span data-i18n="tag_entries">entries</span></div>
<div title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div>
</div>
</div>
@ -6704,7 +6704,7 @@
</div>
<div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter">
<div class="wide100p character_name_block">
<span class="ch_name">Go back</span>
<span class="ch_name" data-i18n="Go back">Go back</span>
</div>
</div>
</div>

View File

@ -52,7 +52,7 @@
"Presence Penalty": "Штраф за присутствие",
"Top A": "Top А",
"Tail Free Sampling": "Tail Free Sampling",
"Rep. Pen. Slope": "Rep. Pen. Slope",
"Rep. Pen. Slope": "Рост штрафа за повтор к концу промпта",
"Top K": "Top K",
"Top P": "Top P",
"Do Sample": "Включить сэмплинг",
@ -162,9 +162,9 @@
"Story String": "Строка истории",
"Example Separator": "Разделитель примеров сообщений",
"Chat Start": "Начало чата",
"Activation Regex": "Regex для активации",
"Activation Regex": "Рег. выражение для активации",
"Instruct Mode": "Режим Instruct",
"Wrap Sequences with Newline": "Отделять строки символом новой строки",
"Wrap Sequences with Newline": "Каждая строка из шаблона на новой строке",
"Include Names": "Добавлять имена",
"Force for Groups and Personas": "Также для групп и персон",
"System Prompt": "Системный промпт",
@ -299,7 +299,7 @@
"AI Horde": "AI Horde",
"NovelAI": "NovelAI",
"OpenAI API key": "Ключ для API OpenAI",
"Trim spaces": "Обрезать пробелы",
"Trim spaces": "Обрезать пробелы в начале и конце",
"Trim Incomplete Sentences": "Удалять неоконченные предложения",
"Include Newline": "Добавлять новую строку",
"Non-markdown strings": "Строки без разметки",
@ -510,7 +510,7 @@
"New preset": "Новый пресет",
"Delete preset": "Удалить пресет",
"API Connections": "Соединения с API",
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь с плохими ответами ставя в очередь только подтвержденных работников. Может замедлить время ответа.",
"Can help with bad responses by queueing only the approved workers. May slowdown the response time.": "Может помочь при плохих ответах, делая запросы только к доверенным рабочим машинам. Может замедлить время ответа.",
"Clear your API key": "Стереть ключ от API",
"Refresh models": "Обновить модели",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai",
@ -551,7 +551,7 @@
"Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным, используйте как ориентир",
"Click to select a new avatar for this character": "Нажмите чтобы выбрать новый аватар для этого персонажа",
"Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Пример:\n [{{user}} is a 28-year-old Romanian cat girl.]",
"Toggle grid view": "Переключить вид сетки",
"Toggle grid view": "Сменить вид сетки",
"Add to Favorites": "Добавить в Избранное",
"Advanced Definition": "Расширенное описание",
"Character Lore": "Лор персонажа",
@ -624,7 +624,7 @@
"UI Theme": "Тема UI",
"This message is invisible for the AI": "Это сообщение невидимо для ИИ",
"Sampler Priority": "Приоритет сэмплеров",
"Ooba only. Determines the order of samplers.": "Только oobabooga. Определяет порядок сэмплеров.",
"Ooba only. Determines the order of samplers.": "Только для oobabooga. Определяет порядок сэмплеров.",
"Load default order": "Загрузить стандартный порядок",
"Max Tokens Second": "Макс. кол-во токенов в секунду",
"CFG": "CFG",
@ -695,7 +695,7 @@
"Medium": "Средний",
"Aggressive": "Агрессивный",
"Very aggressive": "Очень агрессивный",
"Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.&#13;В единицах 1e-4; разумное значение - 3.&#13;Установите в 0, чтобы отключить.&#13;См. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
"Eta_Cutoff_desc": "Eta cutoff - основной параметр специальной техники сэмплинга под названием Eta Sampling.\nВ единицах 1e-4; разумное значение - 3.\nУстановите в 0, чтобы отключить.\nСм. статью Truncation Sampling as Language Model Desmoothing от Хьюитт и др. (2022) для получения подробной информации.",
"Learn how to contribute your idle GPU cycles to the Horde": "Узнайте, как использовать время простоя вашего GPU для помощи Horde",
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Используйте соответствующий токенизатор для моделей Google через их API. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.",
"Load koboldcpp order": "Загрузить порядок из koboldcpp",
@ -964,7 +964,7 @@
"char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)",
"char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)",
"char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)",
"char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в",
"char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в",
"char_import_7": ")",
"Grammar String": "Грамматика",
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.",
@ -1016,7 +1016,7 @@
"prompt_manager_relative": "Относительная",
"prompt_manager_depth": "Глубина",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глубина вставки. 0 = после последнего сообщения, 1 = перед последним сообщением, и т.д.",
"The prompt to be sent.": "Отправляемый ИИ промпт.",
"The prompt to be sent.": "Текст промпта.",
"prompt_manager_forbid_overrides": "Запретить перезапись",
"This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.",
"image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель (такой функционал поддерживают GPT-4V, Claude 3 или Llava 13B). Чтобы добавить в чат изображение, используйте на нужном сообщении действие",
@ -1232,7 +1232,7 @@
"Top P & Min P": "Top P & Min P",
"llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.": "llama.cpp only. Determines the order of samplers. If Mirostat mode is not 0, sampler order is ignored.",
"Helps the model to associate messages with characters.": "Помогает модели связывать сообщения с персонажами.",
"character_names_default": "Except for groups and past personas. Otherwise, make sure you provide names in the prompt.",
"character_names_default": "Добавлять префиксы для групповых чатов и предыдущих персон. В остальных случаях указывайте имена в промпте иными способами.",
"Completion": "Completion Object",
"character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.",
"Use AI21 Tokenizer": "Использовать токенайзер AI21",
@ -1277,7 +1277,7 @@
"Will be inserted as a last prompt line when using system/neutral generation.": "Will be inserted as a last prompt line when using system/neutral generation.",
"If a stop sequence is generated, everything past it will be removed from the output (inclusive).": "Если ИИ генерирует стоп-строку, то всё после неё будет вырезано из ответа (включая и саму стоп-строку).",
"Will be inserted at the start of the chat history if it doesn't start with a User message.": "Вставляется в начале истории чата, если она начинается не с сообщения пользователя.",
"Global World Info/Lorebook activation settings": "Настройки активации глобального лорбука / Информации о мире",
"Global World Info/Lorebook activation settings": "Глобальные настройки активации лорбука / Информации о мире",
"Click to expand": "Щёлкните, чтобы развернуть",
"Insertion Strategy": "Как инжектить",
"Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "Only the entries with the most number of key matches will be selected for Inclusion Group filtering",
@ -1646,8 +1646,8 @@
"mui_reset": "Сброс",
"Quick 'Impersonate' button": "Быстрое перевоплощение",
"Show a button in the input area to ask the AI to impersonate your character for a single message": "Показать в поле ввода кнопку, по нажатии на которую ИИ сгенерирует одно сообщение от лица вашего персонажа.",
"Separators as Stop Strings": "Разделители как стоп-строки",
"Names as Stop Strings": "Имена как стоп-строки",
"Separators as Stop Strings": "Разделители в качестве стоп-строк",
"Names as Stop Strings": "Имена в качестве стоп-строк",
"Add Character and User names to a list of stopping strings.": "Добавлять имена персонажа и пользователя в список стоп-строк.",
"context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.",
"First User Prefix": "Первый префикс пользователя",
@ -1914,8 +1914,8 @@
"Cannot restore GUI preset": "Пресет для Gui восстановить нельзя",
"Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию",
"Default template cannot be restored": "Невозможно восстановить шаблон по умолчанию",
"Resetting a <b>default preset</b> will restore the default settings": "Сброс <b>стандартного пресета</b> восстановит настройки по умолчанию.",
"Resetting a <b>default template</b> will restore the default settings.": "Сброс <b>стандартного шаблона</b> восстановит настройки по умолчанию.",
"Resetting a <b>default preset</b> will restore the default settings.": "Сброс <b>комплектного пресета</b> восстановит настройки по умолчанию.",
"Resetting a <b>default template</b> will restore the default settings.": "Сброс <b>комплектного шаблона</b> восстановит настройки по умолчанию.",
"Are you sure?": "Вы уверены?",
"Default preset restored": "Стандартный пресет восстановлен",
"Default template restored": "Стандартный шаблон восстановлен",
@ -2046,11 +2046,11 @@
"prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью",
"prompt_post_processing_semi": "Semi-strict (чередовать роли)",
"prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)",
"Select Horde models": "Выбрать модель из Horde",
"Select Horde models": "Выберите модель из Horde",
"Model ID (optional)": "Идентификатор модели (необязательно)",
"Derive context size from backend": "Использовать бэкенд для определения размера контекста",
"Rename current preset": "Переименовать пресет",
"No Worlds active. Click here to select.": "Нет активных миров. Нажмите, чтобы выбрать.",
"No Worlds active. Click here to select.": "Активных миров нет, ЛКМ для выбора.",
"Title/Memo": "Название",
"Strategy": "Статус",
"Position": "Позиция",
@ -2169,7 +2169,7 @@
"instruct_derived": "Считывать из метаданных модели (по возможности)",
"Confirm token parsing with": "Чтобы убедиться в правильности выделения токенов, используйте",
"Reasoning Effort": "Рассуждения",
"Constrains effort on reasoning for reasoning models.": "Регулирует объём внутренних рассуждений модели (reasoning), для моделей которые поддерживают эту возможность.\nНа данный момент поддерживаются три значения: Подробные, Обычные, Поверхностные.\nПри менее подробном рассуждении ответ получается быстрее, а также экономятся токены, уходящие на рассуждения.",
"Constrains effort on reasoning for reasoning models.": "Регулирует объём внутренних рассуждений модели (reasoning), для моделей, которые поддерживают эту возможность.\nПри менее подробном рассуждении ответ получается быстрее, а также экономятся токены, уходящие на рассуждения.",
"openai_reasoning_effort_low": "Поверхностные",
"openai_reasoning_effort_medium": "Обычные",
"openai_reasoning_effort_high": "Подробные",
@ -2274,8 +2274,8 @@
"Persona Name Not Set": "У персоны отсутствует имя",
"You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
"Default Persona Removed": "Персона по умолчанию снята",
"Persona is locked to the current character": "Персона закреплена за этим персонажем",
"Persona is locked to the current chat": "Персона закреплена за этим чатом",
"Persona is locked to the current character": "Персона закреплена за текущим персонажем",
"Persona is locked to the current chat": "Персона закреплена за текущим чатом",
"characters": "перс.",
"character": "персонаж",
"in this group": "в группе",
@ -2336,5 +2336,88 @@
"Reasoning already exists.": "Рассуждения уже присутствуют.",
"Edit Message": "Редактирование",
"Status check bypassed": "Проверка статуса отключена",
"Valid": "Работает"
"Valid": "Работает",
"Use Group Scoring": "Использовать Group Scoring",
"Only the entries with the most number of key matches will be selected for Inclusion Group filtering": "До групповых фильтров будут допущены только записи с наибольшим кол-вом совпадений",
"Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)",
"( None )": "(Отсутствует)",
"Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами",
"Move Entry to Another Lorebook": "Переместить запись в другой лорбук",
"There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.",
"Select Target Lorebook": "Выберите куда переместить",
"Move '${0}' to:": "Переместить '${0}' в:",
"Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.",
"Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной",
"Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}",
"Select your current Reasoning Template": "Выберите текущий Шаблон рассуждений",
"Delete template": "Удалить шаблон",
"Reasoning Template": "Шаблон рассуждений",
"openai_reasoning_effort_auto": "Авто",
"openai_reasoning_effort_minimum": "Минимальные",
"openai_reasoning_effort_maximum": "Максимальные",
"OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.": "OpenAI принимает следующее: low (Поверхностные), medium (Обычные), high (Подробные). Minimum (Минимальные) - то же самое, что low. Maximum (Максимальные) - то же самое, что high. При выборе Auto (Авто) значение не отсылается вообще.",
"Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent.": "Резервирует часть ответа для рассуждений (Поверхностные: 10% ответа, Обычные: 25%, Подробные: 50%). Остальные значения зависят от конкретной модели.",
"xAI Model": "Модель xAI",
"xAI API Key": "Ключ от API xAI",
"HuggingFace Token": "Токен HuggingFace",
"Endpoint URL": "Адрес эндпоинта",
"Example: https://****.endpoints.huggingface.cloud": "Пример: https://****.endpoints.huggingface.cloud",
"Featherless Model Selection": "Выбор модели из Featherless",
"category": "категория",
"Top": "Топовые",
"All Classes": "Все классы",
"Date Asc": "Дата, возрастание",
"Date Desc": "Дата, убывание",
"Background Image": "Фоновое изображение",
"Delete the background?": "Удалить фон?",
"Tags_as_Folders_desc": "Чтобы тег отображался как папка, его нужно отметить таковым в меню управления тегами. Нажмите сюда, чтобы открыть его.",
"tag_entries": "раз исп.",
"Multiple personas are connected to this character.\nSelect a persona to use for this chat.": "К этому персонажу привязано несколько персон.\nВыберите персону, которую хотите использовать в этом чате.",
"Select Persona": "Выберите персону",
"Completion Object": "Как часть Completion Object",
"Move ${0} to:": "Переместить '${0}' в:",
"Chat Scenario Override": "Перезапись сценария чата",
"Unique to this chat.": "Действует только в рамках текущего чата.",
"All group members will use the following scenario text instead of what is specified in their character cards.": "Все участники группы будут использовать этот сценарий вместо того, который указан в карточке.",
"Checkpoints inherit the scenario override from their parent, and can be changed individually after that.": "Чекпоинты наследуют сценарий родителя, после отделения его можно менять.",
"Delete Tag": "Удалить тег",
"Do you want to delete the tag": "Вы точно хотите удалить тег",
"If you want to merge all references to this tag into another tag, select it below:": "Если хотите заменить ссылки на этот тег на какой-то другой, то выберите из списка:",
"Open Folder (Show all characters even if not selected)": "Открытая папка (показать всех персонажей, включая невыбранных)",
"Closed Folder (Hide all characters unless selected)": "Закрытая папка (скрыть всех персонажей, кроме выбранных)",
"No Folder": "Не папка",
"Show only favorites": "Показать только избранных персонажей",
"Show only groups": "Показать только группы",
"Show only folders": "Показать только папки",
"Manage tags": "Панель управления тегами",
"Show Tag List": "Показать список тегов",
"Clear all filters": "Сбросить все фильтры",
"There are no items to display.": "Отображать абсолютно нечего.",
"Characters and groups hidden by filters or closed folders": "Персонажи и группы скрыты настройками фильтров либо закрытыми папками",
"Otterly empty": "Всё что можно, всё выдрано",
"Here be dragons": "Список настолько очистился, что в него вернулись драконы",
"Kiwibunga": "Настолько пусто, что киви прилетела посидеть",
"Pump-a-Rum": "Пу-пу-пу",
"Croak it": "Только кваканье лягушек и стрёкот сверчков",
"${0} character hidden.": "Персонажей скрыто: ${0}.",
"${0} characters hidden.": "Персонажей скрыто: ${0}.",
"/ page": "/ стр.",
"Context Length": "Размер контекста",
"Added On": "Добавлена",
"Class": "Класс",
"Bulk_edit_characters": "Массовое редактирование персонажей\n\nЛКМ, чтобы выделить либо отменить выделение персонажа\nShift+ЛКМ, чтобы массово выделить либо отменить выделение персонажей\nПКМ, чтобы выбрать действие",
"Bulk select all characters": "Выбрать всех персонажей",
"Duplicate": "Клонировать",
"Next page": "След. страница",
"Previous page": "Пред. страница",
"Group: ${0}": "Группа: ${0}",
"You deleted a character/chat and arrived back here for safety reasons! Pick another character!": "Вы удалили персонажа или чат, и мы из соображений безопасности перенесли вас на эту страницу! Выберите другого персонажа!",
"Group is empty.": "Группа пуста.",
"No characters available": "Персонажей нет",
"Choose what to export": "Выберите, что экспортировать",
"Text Completion Preset": "Пресет для режима Text Completion",
"Update enabled": "Обновить включенные",
"Could not connect to API": "Не удалось подключиться к API",
"Connected to API": "Соединение с API установлено",
"Go back": "Назад"
}

View File

@ -143,6 +143,7 @@ import {
getHordeModels,
adjustHordeGenerationParams,
MIN_LENGTH,
initHorde,
} from './scripts/horde.js';
import {
@ -175,6 +176,9 @@ import {
saveBase64AsFile,
uuidv4,
equalsIgnoreCaseAndAccents,
localizePagination,
renderPaginationDropdown,
paginationDropdownChangeHandler,
} from './scripts/utils.js';
import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js';
@ -994,6 +998,7 @@ async function firstLoadInit() {
initAuthorsNote();
await initPersonas();
initWorldInfo();
initHorde();
initRossMods();
initStats();
initCfg();
@ -1423,30 +1428,26 @@ function getBackBlock() {
return template;
}
function getEmptyBlock() {
async function getEmptyBlock() {
const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog'];
const texts = ['Here be dragons', 'Otterly empty', 'Kiwibunga', 'Pump-a-Rum', 'Croak it'];
const texts = [t`Here be dragons`, t`Otterly empty`, t`Kiwibunga`, t`Pump-a-Rum`, t`Croak it`];
const roll = new Date().getMinutes() % icons.length;
const emptyBlock = `
<div class="text_block empty_block">
<i class="fa-solid ${icons[roll]} fa-4x"></i>
<h1>${texts[roll]}</h1>
<p>There are no items to display.</p>
</div>`;
const params = {
text: texts[roll],
icon: icons[roll],
};
const emptyBlock = await renderTemplateAsync('emptyBlock', params);
return $(emptyBlock);
}
/**
* @param {number} hidden Number of hidden characters
*/
function getHiddenBlock(hidden) {
const hiddenBlock = `
<div class="text_block hidden_block">
<small>
<p>${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.</p>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div>
</small>
</div>`;
async function getHiddenBlock(hidden) {
const params = {
text: (hidden > 1 ? t`${hidden} characters hidden.` : t`${hidden} character hidden.`),
};
const hiddenBlock = await renderTemplateAsync('hiddenBlock', params);
return $(hiddenBlock);
}
@ -1526,10 +1527,11 @@ export async function printCharacters(fullRefresh = false) {
const entities = getEntitiesList({ doFilter: true });
const pageSize = Number(accountStorage.getItem(storageKey)) || per_page_default;
const sizeChangerOptions = [10, 25, 50, 100, 250, 500, 1000];
$('#rm_print_characters_pagination').pagination({
dataSource: entities,
pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default,
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
pageSize,
pageRange: 1,
pageNumber: saveCharactersPage || 1,
position: 'top',
@ -1538,14 +1540,16 @@ export async function printCharacters(fullRefresh = false) {
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
showNavigator: true,
callback: function (/** @type {Entity[]} */ data) {
callback: async function (/** @type {Entity[]} */ data) {
$(listId).empty();
if (power_user.bogus_folders && isBogusFolderOpen()) {
$(listId).append(getBackBlock());
}
if (!data.length) {
$(listId).append(getEmptyBlock());
const emptyBlock = await getEmptyBlock();
$(listId).append(emptyBlock);
}
let displayCount = 0;
for (const i of data) {
@ -1566,13 +1570,16 @@ export async function printCharacters(fullRefresh = false) {
const hidden = (characters.length + groups.length) - displayCount;
if (hidden > 0 && entitiesFilter.hasAnyFilter()) {
$(listId).append(getHiddenBlock(hidden));
const hiddenBlock = await getHiddenBlock(hidden);
$(listId).append(hiddenBlock);
}
localizePagination($('#rm_print_characters_pagination'));
eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
},
afterSizeSelectorChange: function (e) {
afterSizeSelectorChange: function (e, size) {
accountStorage.setItem(storageKey, e.target.value);
paginationDropdownChangeHandler(e, size);
},
afterPaging: function (e) {
saveCharactersPage = e;
@ -8430,15 +8437,15 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w
function getOkButtonText() {
if (['text', 'char_not_selected'].includes(popup_type)) {
$dialoguePopupCancel.css('display', 'none');
return okButton ?? 'Ok';
return okButton ?? t`Ok`;
} else if (['delete_extension'].includes(popup_type)) {
return okButton ?? 'Ok';
return okButton ?? t`Ok`;
} else if (['new_chat', 'confirm'].includes(popup_type)) {
return okButton ?? 'Yes';
return okButton ?? t`Yes`;
} else if (['input'].includes(popup_type)) {
return okButton ?? t`Save`;
}
return okButton ?? 'Delete';
return okButton ?? t`Delete`;
}
dialogueCloseStop = true;

View File

@ -6,6 +6,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { flashHighlight, stringFormat } from './utils.js';
import { t } from './i18n.js';
import { Popup } from './popup.js';
const BG_METADATA_KEY = 'custom_background';
const LIST_METADATA_KEY = 'chat_backgrounds';
@ -291,7 +292,7 @@ async function onDeleteBackgroundClick(e) {
const bgToDelete = $(this).closest('.bg_example');
const url = bgToDelete.data('url');
const isCustom = bgToDelete.attr('custom') === 'true';
const confirm = await callPopup('<h3>Delete the background?</h3>', 'confirm');
const confirm = await Popup.show.confirm(t`Delete the background?`, null);
const bg = bgToDelete.attr('bgfile');
if (confirm) {

View File

@ -13,6 +13,9 @@ import {
getBase64Async,
resetScrollHeight,
initScrollHeight,
localizePagination,
renderPaginationDropdown,
paginationDropdownChangeHandler,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js';
import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js';
@ -1374,6 +1377,8 @@ function getGroupCharacters({ doFilter, onlyMembers } = {}) {
function printGroupCandidates() {
const storageKey = 'GroupCandidates_PerPage';
const pageSize = Number(accountStorage.getItem(storageKey)) || 5;
const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000];
$('#rm_group_add_members_pagination').pagination({
dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
pageRange: 1,
@ -1382,18 +1387,20 @@ function printGroupCandidates() {
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
showNavigator: true,
showSizeChanger: true,
pageSize: Number(accountStorage.getItem(storageKey)) || 5,
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
afterSizeSelectorChange: function (e) {
pageSize,
afterSizeSelectorChange: function (e, size) {
accountStorage.setItem(storageKey, e.target.value);
paginationDropdownChangeHandler(e, size);
},
callback: function (data) {
$('#rm_group_add_members').empty();
for (const i of data) {
$('#rm_group_add_members').append(getGroupCharacterBlock(i.item));
}
localizePagination($('#rm_group_add_members_pagination'));
},
});
}
@ -1401,6 +1408,9 @@ function printGroupCandidates() {
function printGroupMembers() {
const storageKey = 'GroupMembers_PerPage';
$('.rm_group_members_pagination').each(function () {
let that = this;
const pageSize = Number(accountStorage.getItem(storageKey)) || 5;
const sizeChangerOptions = [5, 10, 25, 50, 100, 200, 500, 1000];
$(this).pagination({
dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
pageRange: 1,
@ -1411,16 +1421,18 @@ function printGroupMembers() {
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
showSizeChanger: true,
pageSize: Number(accountStorage.getItem(storageKey)) || 5,
sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000],
afterSizeSelectorChange: function (e) {
formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
pageSize,
afterSizeSelectorChange: function (e, size) {
accountStorage.setItem(storageKey, e.target.value);
paginationDropdownChangeHandler(e, size);
},
callback: function (data) {
$('.rm_group_members').empty();
for (const i of data) {
$('.rm_group_members').append(getGroupCharacterBlock(i.item));
}
localizePagination($(that));
},
});
});
@ -1804,7 +1816,7 @@ async function createGroup() {
const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(', ');
if (!name) {
name = `Group: ${memberNames}`;
name = t`Group: ${memberNames}`;
}
const avatar_url = $('#group_avatar_preview img').attr('src');

View File

@ -394,7 +394,7 @@ function getHordeModelTemplate(option) {
`));
}
jQuery(function () {
export function initHorde () {
$('#horde_model').on('mousedown change', async function (e) {
console.log('Horde model change', e);
horde_settings.models = $('#horde_model').val();
@ -441,7 +441,7 @@ jQuery(function () {
if (!isMobile()) {
$('#horde_model').select2({
width: '100%',
placeholder: 'Select Horde models',
placeholder: t`Select Horde models`,
allowClear: true,
closeOnSelect: false,
templateSelection: function (data) {
@ -451,5 +451,5 @@ jQuery(function () {
templateResult: getHordeModelTemplate,
});
}
});
}

View File

@ -3851,7 +3851,7 @@ function createLogitBiasListItem(entry) {
}
async function createNewLogitBiasPreset() {
const name = await callPopup('Preset name:', 'input');
const name = await Popup.show.input(t`Preset name:`, null);
if (!name) {
return;

View File

@ -22,7 +22,7 @@ import {
} from '../script.js';
import { persona_description_positions, power_user } from './power-user.js';
import { getTokenCountAsync } from './tokenizers.js';
import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock } from './utils.js';
import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler } from './utils.js';
import { debounce_timeout } from './constants.js';
import { FILTER_TYPES, FilterHelper } from './filters.js';
import { groups, selected_group } from './group-chats.js';
@ -250,16 +250,18 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
const storageKey = 'Personas_PerPage';
const listId = '#user_avatar_block';
const perPage = Number(accountStorage.getItem(storageKey)) || 5;
const sizeChangerOptions = [5, 10, 25, 50, 100, 250, 500, 1000];
$('#persona_pagination_container').pagination({
dataSource: entities,
pageSize: perPage,
sizeChangerOptions: [5, 10, 25, 50, 100, 250, 500, 1000],
sizeChangerOptions,
pageRange: 1,
pageNumber: savePersonasPage || 1,
position: 'top',
showPageNumbers: false,
showSizeChanger: true,
formatSizeChanger: renderPaginationDropdown(perPage, sizeChangerOptions),
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
@ -270,9 +272,11 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
$(listId).append(getUserAvatarBlock(item));
}
updatePersonaUIStates();
localizePagination($('#persona_pagination_container'));
},
afterSizeSelectorChange: function (e) {
afterSizeSelectorChange: function (e, size) {
accountStorage.setItem(storageKey, e.target.value);
paginationDropdownChangeHandler(e, size);
},
afterPaging: function (e) {
savePersonasPage = e;

View File

@ -27,7 +27,7 @@ import { debounce_timeout } from './constants.js';
import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { renderTemplateAsync } from './templates.js';
import { t } from './i18n.js';
import { t, translate } from './i18n.js';
export {
TAG_FOLDER_TYPES,
@ -318,7 +318,7 @@ function getTagBlock(tag, entities, hidden = 0, isUseless = false) {
template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`);
template.find('.ch_name').text(tag.name).attr('title', `[Folder] ${tag.name}`);
template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : '');
template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
template.find('.bogus_folder_counter').text(`${count} ` + (count != 1 ? t`characters` : t`character`));
template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
if (isUseless) template.addClass('useless');
@ -1057,7 +1057,7 @@ function appendTagToList(listElement, tag, { removable = false, isFilter = false
tagElement.attr('title', tag.title);
}
if (tag.icon) {
tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title || ''}`.trim()).addClass(tag.icon);
tagElement.find('.tag_name').text('').attr('title', `${translate(tag.name)} ${tag.title || ''}`.trim()).addClass(tag.icon);
tagElement.addClass('actionable');
}
@ -1644,6 +1644,7 @@ function updateDrawTagFolder(element, tag) {
// Draw/update css attributes for this class
folderElement.attr('title', tagFolder.tooltip);
folderElement.attr('data-i18n', '[title]' + tagFolder.tooltip);
const indicator = folderElement.find('.tag_folder_indicator');
indicator.text(tagFolder.icon);
indicator.css('color', tagFolder.color);
@ -1655,14 +1656,7 @@ async function onTagDeleteClick() {
const tag = tags.find(x => x.id === id);
const otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name })));
const popupContent = $(`
<h3>Delete Tag</h3>
<div>Do you want to delete the tag <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div>
<div class="m-t-2 marginBot5">If you want to merge all references to this tag into another tag, select it below:</div>
<select id="merge_tag_select">
<option value="">--- None ---</option>
${otherTags.map(x => `<option value="${x.id}">${x.name}</option>`).join('')}
</select>`);
const popupContent = $(await renderTemplateAsync('deleteTag', { otherTags }));
appendTagToList(popupContent.find('#tag_to_delete'), tag);

View File

@ -0,0 +1,9 @@
<h3 data-i18n="Delete Tag">Delete Tag</h3>
<div><span data-i18n="Do you want to delete the tag">Do you want to delete the tag</span> <div id="tag_to_delete" class="tags_inline inline-flex margin-r2"></div>?</div>
<div class="m-t-2 marginBot5" data-i18n="If you want to merge all references to this tag into another tag, select it below:">If you want to merge all references to this tag into another tag, select it below:</div>
<select id="merge_tag_select">
<option value="">--- None ---</option>
{{#each otherTags}}
<option value="{{this.id}}">{{this.name}}</option>
{{/each}}
</select>

View File

@ -0,0 +1,7 @@
<div class="text_block empty_block">
<i class="fa-solid {{icon}} fa-4x"></i>
<h1>{{text}}</h1>
<p data-i18n="There are no items to display.">
There are no items to display.
</p>
</div>

View File

@ -0,0 +1,6 @@
<div class="text_block hidden_block">
<small>
<p>{{text}}</p>
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div>
</small>
</div>

View File

@ -7,6 +7,7 @@ import { renderTemplateAsync } from './templates.js';
import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { t } from './i18n.js';
import { accountStorage } from './util/AccountStorage.js';
import { localizePagination, PAGINATION_TEMPLATE } from './utils.js';
let mancerModels = [];
let togetherModels = [];
@ -361,9 +362,7 @@ export async function loadFeatherlessModels(data) {
showSizeChanger: false,
prevText: '<',
nextText: '>',
formatNavigator: function (currentPage, totalPage) {
return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage;
},
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
callback: function (modelsOnPage, pagination) {
modelCardBlock.innerHTML = '';
@ -385,15 +384,15 @@ export async function loadFeatherlessModels(data) {
const modelClassDiv = document.createElement('div');
modelClassDiv.classList.add('model-class');
modelClassDiv.textContent = `Class: ${model.model_class || 'N/A'}`;
modelClassDiv.textContent = t`Class` + `: ${model.model_class || 'N/A'}`;
const contextLengthDiv = document.createElement('div');
contextLengthDiv.classList.add('model-context-length');
contextLengthDiv.textContent = `Context Length: ${model.context_length}`;
contextLengthDiv.textContent = t`Context Length` + `: ${model.context_length}`;
const dateAddedDiv = document.createElement('div');
dateAddedDiv.classList.add('model-date-added');
dateAddedDiv.textContent = `Added On: ${new Date(model.created * 1000).toLocaleDateString()}`;
dateAddedDiv.textContent = t`Added On` + `: ${new Date(model.created * 1000).toLocaleDateString()}`;
detailsContainer.appendChild(modelClassDiv);
detailsContainer.appendChild(contextLengthDiv);
@ -417,6 +416,7 @@ export async function loadFeatherlessModels(data) {
// Update the current page value whenever the page changes
featherlessCurrentPage = pagination.pageNumber;
localizePagination(paginationContainer);
},
afterSizeSelectorChange: function (e) {
const newPerPage = e.target.value;

View File

@ -20,7 +20,46 @@ import { getCurrentLocale, t } from './i18n.js';
* Pagination status string template.
* @type {string}
*/
export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> of <%= totalNumber %>';
export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>';
export const localizePagination = function(container) {
container.find('[title="Next page"]').attr('title', t`Next page`);
container.find('[title="Previous page"]').attr('title', t`Previous page`);
};
/**
* Renders a dropdown for selecting page size in pagination.
* @param {number} pageSize Page size
* @param {number[]} sizeChangerOptions Array of page size options
* @returns {string} The rendered dropdown element as a string
*/
export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) {
const sizeSelect = document.createElement('select');
sizeSelect.classList.add('J-paginationjs-size-select');
if (sizeChangerOptions.indexOf(pageSize) === -1) {
sizeChangerOptions.unshift(pageSize);
sizeChangerOptions.sort((a, b) => a - b);
}
for (let i = 0; i < sizeChangerOptions.length; i++) {
const option = document.createElement('option');
option.value = `${sizeChangerOptions[i]}`;
option.textContent = `${sizeChangerOptions[i]} ${t`/ page`}`;
if (sizeChangerOptions[i] === pageSize) {
option.setAttribute('selected', 'selected');
}
sizeSelect.appendChild(option);
}
return sizeSelect.outerHTML;
};
export const paginationDropdownChangeHandler = function(event, size) {
let dropdown = $(event?.originalEvent?.currentTarget || event.delegateTarget).find('select');
dropdown.find('[selected]').removeAttr('selected');
dropdown.find(`[value=${size}]`).attr('selected', '');
};
/**
* Navigation options for pagination.

View File

@ -2658,7 +2658,7 @@ export async function getWorldEntry(name, data, entry) {
if (!isMobile()) {
$(characterFilter).select2({
width: '100%',
placeholder: 'Tie this entry to specific characters or characters with specific tags',
placeholder: t`Tie this entry to specific characters or characters with specific tags`,
allowClear: true,
closeOnSelect: false,
});
@ -3258,7 +3258,7 @@ export async function getWorldEntry(name, data, entry) {
// Create wrapper div
const wrapper = document.createElement('div');
wrapper.textContent = t`Move "${sourceName}" to:`;
wrapper.textContent = t`Move '${sourceName}' to:`;
// Create container and append elements
const container = document.createElement('div');

View File

@ -5715,6 +5715,7 @@ body:not(.movingUI) .drawer-content.maximized {
width: unset;
margin: 0;
font-size: calc(var(--mainFontSize) * 0.85);
padding-right: 20px;
}
.paginationjs-pages ul li a {