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 { #rm_group_members:empty::before {
content: 'Group is empty'; content: attr(group_empty_text);
font-weight: bolder; font-weight: bolder;
width: 100%; width: 100%;
@ -115,7 +115,7 @@
} }
#rm_group_add_members:empty::before { #rm_group_add_members:empty::before {
content: 'No characters available'; content: attr(no_characters_text);
font-weight: bolder; font-weight: bolder;
width: 100%; 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."> <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"> <label for="openai_reasoning_effort">
<span data-i18n="Reasoning Effort">Reasoning Effort</span> <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="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."></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> </label>
<select id="openai_reasoning_effort"> <select id="openai_reasoning_effort">
<option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option> <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="search" data-i18n="Search" hidden>A-Z</option>
<option value="asc">A-Z</option> <option value="asc">A-Z</option>
<option value="desc">Z-A</option> <option value="desc">Z-A</option>
<option value="date_asc">Date Asc</option> <option data-i18n="Date Asc" value="date_asc">Date Asc</option>
<option value="date_desc">Date Desc</option> <option data-i18n="Date Desc" value="date_desc">Date Desc</option>
</select> </select>
<select id="featherless_category_selection" class="text_pole"> <select id="featherless_category_selection" class="text_pole">
<option value="" disabled selected data-i18n="category">category</option> <option value="" disabled selected data-i18n="category">category</option>
@ -2509,7 +2509,7 @@
<option value="All" data-i18n="All">All</option> <option value="All" data-i18n="All">All</option>
</select> </select>
<select id="featherless_class_selection" class="text_pole"> <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> </select>
<div id="featherless_model_pagination_container" class="flex1"></div> <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> <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 class="inline-drawer-content">
<div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1"> <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_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> </div>
</div> </div>
@ -5549,7 +5549,7 @@
<div class="tags rm_tag_filter"></div> <div class="tags rm_tag_filter"></div>
</div> </div>
<div id="rm_group_add_members_pagination" class="group_pagination"></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> </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="color"></div>
<div class="tag_view_color_picker" data-value="color2"></div> <div class="tag_view_color_picker" data-value="color2"></div>
<div class="tag_view_name" contenteditable="true"></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 title="Delete tag" class="tag_delete fa-solid fa-trash-can right_menu_button" data-i18n="[title]Delete tag"></div>
</div> </div>
</div> </div>
@ -6704,7 +6704,7 @@
</div> </div>
<div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter"> <div class="flex-container wide100pLess70px character_select_container height100p alignitemscenter">
<div class="wide100p character_name_block"> <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> </div>
</div> </div>

View File

@ -52,7 +52,7 @@
"Presence Penalty": "Штраф за присутствие", "Presence Penalty": "Штраф за присутствие",
"Top A": "Top А", "Top A": "Top А",
"Tail Free Sampling": "Tail Free Sampling", "Tail Free Sampling": "Tail Free Sampling",
"Rep. Pen. Slope": "Rep. Pen. Slope", "Rep. Pen. Slope": "Рост штрафа за повтор к концу промпта",
"Top K": "Top K", "Top K": "Top K",
"Top P": "Top P", "Top P": "Top P",
"Do Sample": "Включить сэмплинг", "Do Sample": "Включить сэмплинг",
@ -162,9 +162,9 @@
"Story String": "Строка истории", "Story String": "Строка истории",
"Example Separator": "Разделитель примеров сообщений", "Example Separator": "Разделитель примеров сообщений",
"Chat Start": "Начало чата", "Chat Start": "Начало чата",
"Activation Regex": "Regex для активации", "Activation Regex": "Рег. выражение для активации",
"Instruct Mode": "Режим Instruct", "Instruct Mode": "Режим Instruct",
"Wrap Sequences with Newline": "Отделять строки символом новой строки", "Wrap Sequences with Newline": "Каждая строка из шаблона на новой строке",
"Include Names": "Добавлять имена", "Include Names": "Добавлять имена",
"Force for Groups and Personas": "Также для групп и персон", "Force for Groups and Personas": "Также для групп и персон",
"System Prompt": "Системный промпт", "System Prompt": "Системный промпт",
@ -299,7 +299,7 @@
"AI Horde": "AI Horde", "AI Horde": "AI Horde",
"NovelAI": "NovelAI", "NovelAI": "NovelAI",
"OpenAI API key": "Ключ для API OpenAI", "OpenAI API key": "Ключ для API OpenAI",
"Trim spaces": "Обрезать пробелы", "Trim spaces": "Обрезать пробелы в начале и конце",
"Trim Incomplete Sentences": "Удалять неоконченные предложения", "Trim Incomplete Sentences": "Удалять неоконченные предложения",
"Include Newline": "Добавлять новую строку", "Include Newline": "Добавлять новую строку",
"Non-markdown strings": "Строки без разметки", "Non-markdown strings": "Строки без разметки",
@ -510,7 +510,7 @@
"New preset": "Новый пресет", "New preset": "Новый пресет",
"Delete preset": "Удалить пресет", "Delete preset": "Удалить пресет",
"API Connections": "Соединения с API", "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", "Clear your API key": "Стереть ключ от API",
"Refresh models": "Обновить модели", "Refresh models": "Обновить модели",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Получите свой OpenRouter API токен используя OAuth. У вас будет открыта вкладка openrouter.ai", "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.": "Счетчик токенов может быть неточным, используйте как ориентир", "Token counts may be inaccurate and provided just for reference.": "Счетчик токенов может быть неточным, используйте как ориентир",
"Click to select a new avatar for this character": "Нажмите чтобы выбрать новый аватар для этого персонажа", "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.]", "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": "Добавить в Избранное", "Add to Favorites": "Добавить в Избранное",
"Advanced Definition": "Расширенное описание", "Advanced Definition": "Расширенное описание",
"Character Lore": "Лор персонажа", "Character Lore": "Лор персонажа",
@ -624,7 +624,7 @@
"UI Theme": "Тема UI", "UI Theme": "Тема UI",
"This message is invisible for the AI": "Это сообщение невидимо для ИИ", "This message is invisible for the AI": "Это сообщение невидимо для ИИ",
"Sampler Priority": "Приоритет сэмплеров", "Sampler Priority": "Приоритет сэмплеров",
"Ooba only. Determines the order of samplers.": "Только oobabooga. Определяет порядок сэмплеров.", "Ooba only. Determines the order of samplers.": "Только для oobabooga. Определяет порядок сэмплеров.",
"Load default order": "Загрузить стандартный порядок", "Load default order": "Загрузить стандартный порядок",
"Max Tokens Second": "Макс. кол-во токенов в секунду", "Max Tokens Second": "Макс. кол-во токенов в секунду",
"CFG": "CFG", "CFG": "CFG",
@ -695,7 +695,7 @@
"Medium": "Средний", "Medium": "Средний",
"Aggressive": "Агрессивный", "Aggressive": "Агрессивный",
"Very 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", "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. Медленная обработка подсказок, но предлагает намного более точный подсчет токенов.", "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", "Load koboldcpp order": "Загрузить порядок из koboldcpp",
@ -964,7 +964,7 @@
"char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)", "char_import_3": "Персонаж с JanitorAI (прямая ссылка или UUID)",
"char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)", "char_import_4": "Персонаж с Pygmalion.chat (прямая ссылка или UUID)",
"char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)", "char_import_5": "Персонаж с AICharacterCards.com (прямая ссылка или ID)",
"char_import_6": "Прямая ссылка на PNG-файл (чтобы узнать список разрешённых хостов, загляните в", "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в",
"char_import_7": ")", "char_import_7": ")",
"Grammar String": "Грамматика", "Grammar String": "Грамматика",
"GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", "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_relative": "Относительная",
"prompt_manager_depth": "Глубина", "prompt_manager_depth": "Глубина",
"Injection depth. 0 = after the last message, 1 = before the last message, etc.": "Глубина вставки. 0 = после последнего сообщения, 1 = перед последним сообщением, и т.д.", "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": "Запретить перезапись", "prompt_manager_forbid_overrides": "Запретить перезапись",
"This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.", "This prompt cannot be overridden by character cards, even if overrides are preferred.": "Карточка персонажа не сможет перезаписать этот промпт, даже если настройки отдают приоритет именно ей.",
"image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель (такой функционал поддерживают GPT-4V, Claude 3 или Llava 13B). Чтобы добавить в чат изображение, используйте на нужном сообщении действие", "image_inlining_hint_1": "Отправлять картинки как часть промпта, если позволяет модель (такой функционал поддерживают GPT-4V, Claude 3 или Llava 13B). Чтобы добавить в чат изображение, используйте на нужном сообщении действие",
@ -1232,7 +1232,7 @@
"Top P & Min P": "Top P & Min P", "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.", "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.": "Помогает модели связывать сообщения с персонажами.", "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", "Completion": "Completion Object",
"character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.", "character_names_completion": "Только латинские буквы, цифры и знак подчёркивания. Работает не для всех бэкендов, в частности для Claude, MistralAI, Google.",
"Use AI21 Tokenizer": "Использовать токенайзер AI21", "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.", "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).": "Если ИИ генерирует стоп-строку, то всё после неё будет вырезано из ответа (включая и саму стоп-строку).", "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.": "Вставляется в начале истории чата, если она начинается не с сообщения пользователя.", "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": "Щёлкните, чтобы развернуть", "Click to expand": "Щёлкните, чтобы развернуть",
"Insertion Strategy": "Как инжектить", "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", "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": "Сброс", "mui_reset": "Сброс",
"Quick 'Impersonate' button": "Быстрое перевоплощение", "Quick 'Impersonate' button": "Быстрое перевоплощение",
"Show a button in the input area to ask the AI to impersonate your character for a single message": "Показать в поле ввода кнопку, по нажатии на которую ИИ сгенерирует одно сообщение от лица вашего персонажа.", "Show a button in the input area to ask the AI to impersonate your character for a single message": "Показать в поле ввода кнопку, по нажатии на которую ИИ сгенерирует одно сообщение от лица вашего персонажа.",
"Separators as Stop Strings": "Разделители как стоп-строки", "Separators as Stop Strings": "Разделители в качестве стоп-строк",
"Names as Stop Strings": "Имена как стоп-строки", "Names as Stop Strings": "Имена в качестве стоп-строк",
"Add Character and User names to a list of stopping strings.": "Добавлять имена персонажа и пользователя в список стоп-строк.", "Add Character and User names to a list of stopping strings.": "Добавлять имена персонажа и пользователя в список стоп-строк.",
"context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.", "context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.",
"First User Prefix": "Первый префикс пользователя", "First User Prefix": "Первый префикс пользователя",
@ -1914,8 +1914,8 @@
"Cannot restore GUI preset": "Пресет для Gui восстановить нельзя", "Cannot restore GUI preset": "Пресет для Gui восстановить нельзя",
"Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию", "Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию",
"Default template 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 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 template</b> will restore the default settings.": "Сброс <b>комплектного шаблона</b> восстановит настройки по умолчанию.",
"Are you sure?": "Вы уверены?", "Are you sure?": "Вы уверены?",
"Default preset restored": "Стандартный пресет восстановлен", "Default preset restored": "Стандартный пресет восстановлен",
"Default template restored": "Стандартный шаблон восстановлен", "Default template restored": "Стандартный шаблон восстановлен",
@ -2046,11 +2046,11 @@
"prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью", "prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью",
"prompt_post_processing_semi": "Semi-strict (чередовать роли)", "prompt_post_processing_semi": "Semi-strict (чередовать роли)",
"prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)", "prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)",
"Select Horde models": "Выбрать модель из Horde", "Select Horde models": "Выберите модель из Horde",
"Model ID (optional)": "Идентификатор модели (необязательно)", "Model ID (optional)": "Идентификатор модели (необязательно)",
"Derive context size from backend": "Использовать бэкенд для определения размера контекста", "Derive context size from backend": "Использовать бэкенд для определения размера контекста",
"Rename current preset": "Переименовать пресет", "Rename current preset": "Переименовать пресет",
"No Worlds active. Click here to select.": "Нет активных миров. Нажмите, чтобы выбрать.", "No Worlds active. Click here to select.": "Активных миров нет, ЛКМ для выбора.",
"Title/Memo": "Название", "Title/Memo": "Название",
"Strategy": "Статус", "Strategy": "Статус",
"Position": "Позиция", "Position": "Позиция",
@ -2169,7 +2169,7 @@
"instruct_derived": "Считывать из метаданных модели (по возможности)", "instruct_derived": "Считывать из метаданных модели (по возможности)",
"Confirm token parsing with": "Чтобы убедиться в правильности выделения токенов, используйте", "Confirm token parsing with": "Чтобы убедиться в правильности выделения токенов, используйте",
"Reasoning Effort": "Рассуждения", "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_low": "Поверхностные",
"openai_reasoning_effort_medium": "Обычные", "openai_reasoning_effort_medium": "Обычные",
"openai_reasoning_effort_high": "Подробные", "openai_reasoning_effort_high": "Подробные",
@ -2274,8 +2274,8 @@
"Persona Name Not Set": "У персоны отсутствует имя", "Persona Name Not Set": "У персоны отсутствует имя",
"You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.", "You must bind a name to this persona before you can set a lorebook.": "Перед привязкой лорбука персоне необходимо присвоить имя.",
"Default Persona Removed": "Персона по умолчанию снята", "Default Persona Removed": "Персона по умолчанию снята",
"Persona is locked to the current character": "Персона закреплена за этим персонажем", "Persona is locked to the current character": "Персона закреплена за текущим персонажем",
"Persona is locked to the current chat": "Персона закреплена за этим чатом", "Persona is locked to the current chat": "Персона закреплена за текущим чатом",
"characters": "перс.", "characters": "перс.",
"character": "персонаж", "character": "персонаж",
"in this group": "в группе", "in this group": "в группе",
@ -2336,5 +2336,88 @@
"Reasoning already exists.": "Рассуждения уже присутствуют.", "Reasoning already exists.": "Рассуждения уже присутствуют.",
"Edit Message": "Редактирование", "Edit Message": "Редактирование",
"Status check bypassed": "Проверка статуса отключена", "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, getHordeModels,
adjustHordeGenerationParams, adjustHordeGenerationParams,
MIN_LENGTH, MIN_LENGTH,
initHorde,
} from './scripts/horde.js'; } from './scripts/horde.js';
import { import {
@ -175,6 +176,9 @@ import {
saveBase64AsFile, saveBase64AsFile,
uuidv4, uuidv4,
equalsIgnoreCaseAndAccents, equalsIgnoreCaseAndAccents,
localizePagination,
renderPaginationDropdown,
paginationDropdownChangeHandler,
} from './scripts/utils.js'; } from './scripts/utils.js';
import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js';
@ -994,6 +998,7 @@ async function firstLoadInit() {
initAuthorsNote(); initAuthorsNote();
await initPersonas(); await initPersonas();
initWorldInfo(); initWorldInfo();
initHorde();
initRossMods(); initRossMods();
initStats(); initStats();
initCfg(); initCfg();
@ -1423,30 +1428,26 @@ function getBackBlock() {
return template; return template;
} }
function getEmptyBlock() { async function getEmptyBlock() {
const icons = ['fa-dragon', 'fa-otter', 'fa-kiwi-bird', 'fa-crow', 'fa-frog']; 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 roll = new Date().getMinutes() % icons.length;
const emptyBlock = ` const params = {
<div class="text_block empty_block"> text: texts[roll],
<i class="fa-solid ${icons[roll]} fa-4x"></i> icon: icons[roll],
<h1>${texts[roll]}</h1> };
<p>There are no items to display.</p> const emptyBlock = await renderTemplateAsync('emptyBlock', params);
</div>`;
return $(emptyBlock); return $(emptyBlock);
} }
/** /**
* @param {number} hidden Number of hidden characters * @param {number} hidden Number of hidden characters
*/ */
function getHiddenBlock(hidden) { async function getHiddenBlock(hidden) {
const hiddenBlock = ` const params = {
<div class="text_block hidden_block"> text: (hidden > 1 ? t`${hidden} characters hidden.` : t`${hidden} character hidden.`),
<small> };
<p>${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.</p> const hiddenBlock = await renderTemplateAsync('hiddenBlock', params);
<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>`;
return $(hiddenBlock); return $(hiddenBlock);
} }
@ -1526,10 +1527,11 @@ export async function printCharacters(fullRefresh = false) {
const entities = getEntitiesList({ doFilter: true }); 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({ $('#rm_print_characters_pagination').pagination({
dataSource: entities, dataSource: entities,
pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default, pageSize,
sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000],
pageRange: 1, pageRange: 1,
pageNumber: saveCharactersPage || 1, pageNumber: saveCharactersPage || 1,
position: 'top', position: 'top',
@ -1538,14 +1540,16 @@ export async function printCharacters(fullRefresh = false) {
prevText: '<', prevText: '<',
nextText: '>', nextText: '>',
formatNavigator: PAGINATION_TEMPLATE, formatNavigator: PAGINATION_TEMPLATE,
formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions),
showNavigator: true, showNavigator: true,
callback: function (/** @type {Entity[]} */ data) { callback: async function (/** @type {Entity[]} */ data) {
$(listId).empty(); $(listId).empty();
if (power_user.bogus_folders && isBogusFolderOpen()) { if (power_user.bogus_folders && isBogusFolderOpen()) {
$(listId).append(getBackBlock()); $(listId).append(getBackBlock());
} }
if (!data.length) { if (!data.length) {
$(listId).append(getEmptyBlock()); const emptyBlock = await getEmptyBlock();
$(listId).append(emptyBlock);
} }
let displayCount = 0; let displayCount = 0;
for (const i of data) { for (const i of data) {
@ -1566,13 +1570,16 @@ export async function printCharacters(fullRefresh = false) {
const hidden = (characters.length + groups.length) - displayCount; const hidden = (characters.length + groups.length) - displayCount;
if (hidden > 0 && entitiesFilter.hasAnyFilter()) { 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); eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
}, },
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e, size) {
accountStorage.setItem(storageKey, e.target.value); accountStorage.setItem(storageKey, e.target.value);
paginationDropdownChangeHandler(e, size);
}, },
afterPaging: function (e) { afterPaging: function (e) {
saveCharactersPage = e; saveCharactersPage = e;
@ -8430,15 +8437,15 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w
function getOkButtonText() { function getOkButtonText() {
if (['text', 'char_not_selected'].includes(popup_type)) { if (['text', 'char_not_selected'].includes(popup_type)) {
$dialoguePopupCancel.css('display', 'none'); $dialoguePopupCancel.css('display', 'none');
return okButton ?? 'Ok'; return okButton ?? t`Ok`;
} else if (['delete_extension'].includes(popup_type)) { } else if (['delete_extension'].includes(popup_type)) {
return okButton ?? 'Ok'; return okButton ?? t`Ok`;
} else if (['new_chat', 'confirm'].includes(popup_type)) { } else if (['new_chat', 'confirm'].includes(popup_type)) {
return okButton ?? 'Yes'; return okButton ?? t`Yes`;
} else if (['input'].includes(popup_type)) { } else if (['input'].includes(popup_type)) {
return okButton ?? t`Save`; return okButton ?? t`Save`;
} }
return okButton ?? 'Delete'; return okButton ?? t`Delete`;
} }
dialogueCloseStop = true; dialogueCloseStop = true;

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ import { debounce_timeout } from './constants.js';
import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js'; import { INTERACTABLE_CONTROL_CLASS } from './keyboard.js';
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { t } from './i18n.js'; import { t, translate } from './i18n.js';
export { export {
TAG_FOLDER_TYPES, 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('.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('.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_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); template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
if (isUseless) template.addClass('useless'); if (isUseless) template.addClass('useless');
@ -1057,7 +1057,7 @@ function appendTagToList(listElement, tag, { removable = false, isFilter = false
tagElement.attr('title', tag.title); tagElement.attr('title', tag.title);
} }
if (tag.icon) { 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'); tagElement.addClass('actionable');
} }
@ -1644,6 +1644,7 @@ function updateDrawTagFolder(element, tag) {
// Draw/update css attributes for this class // Draw/update css attributes for this class
folderElement.attr('title', tagFolder.tooltip); folderElement.attr('title', tagFolder.tooltip);
folderElement.attr('data-i18n', '[title]' + tagFolder.tooltip);
const indicator = folderElement.find('.tag_folder_indicator'); const indicator = folderElement.find('.tag_folder_indicator');
indicator.text(tagFolder.icon); indicator.text(tagFolder.icon);
indicator.css('color', tagFolder.color); indicator.css('color', tagFolder.color);
@ -1655,14 +1656,7 @@ async function onTagDeleteClick() {
const tag = tags.find(x => x.id === id); 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 otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name })));
const popupContent = $(` const popupContent = $(await renderTemplateAsync('deleteTag', { otherTags }));
<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>`);
appendTagToList(popupContent.find('#tag_to_delete'), tag); 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 { POPUP_TYPE, callGenericPopup } from './popup.js';
import { t } from './i18n.js'; import { t } from './i18n.js';
import { accountStorage } from './util/AccountStorage.js'; import { accountStorage } from './util/AccountStorage.js';
import { localizePagination, PAGINATION_TEMPLATE } from './utils.js';
let mancerModels = []; let mancerModels = [];
let togetherModels = []; let togetherModels = [];
@ -361,9 +362,7 @@ export async function loadFeatherlessModels(data) {
showSizeChanger: false, showSizeChanger: false,
prevText: '<', prevText: '<',
nextText: '>', nextText: '>',
formatNavigator: function (currentPage, totalPage) { formatNavigator: PAGINATION_TEMPLATE,
return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ' of ' + totalPage * perPage;
},
showNavigator: true, showNavigator: true,
callback: function (modelsOnPage, pagination) { callback: function (modelsOnPage, pagination) {
modelCardBlock.innerHTML = ''; modelCardBlock.innerHTML = '';
@ -385,15 +384,15 @@ export async function loadFeatherlessModels(data) {
const modelClassDiv = document.createElement('div'); const modelClassDiv = document.createElement('div');
modelClassDiv.classList.add('model-class'); 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'); const contextLengthDiv = document.createElement('div');
contextLengthDiv.classList.add('model-context-length'); 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'); const dateAddedDiv = document.createElement('div');
dateAddedDiv.classList.add('model-date-added'); 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(modelClassDiv);
detailsContainer.appendChild(contextLengthDiv); detailsContainer.appendChild(contextLengthDiv);
@ -417,6 +416,7 @@ export async function loadFeatherlessModels(data) {
// Update the current page value whenever the page changes // Update the current page value whenever the page changes
featherlessCurrentPage = pagination.pageNumber; featherlessCurrentPage = pagination.pageNumber;
localizePagination(paginationContainer);
}, },
afterSizeSelectorChange: function (e) { afterSizeSelectorChange: function (e) {
const newPerPage = e.target.value; const newPerPage = e.target.value;

View File

@ -20,7 +20,46 @@ import { getCurrentLocale, t } from './i18n.js';
* Pagination status string template. * Pagination status string template.
* @type {string} * @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. * Navigation options for pagination.

View File

@ -2658,7 +2658,7 @@ export async function getWorldEntry(name, data, entry) {
if (!isMobile()) { if (!isMobile()) {
$(characterFilter).select2({ $(characterFilter).select2({
width: '100%', 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, allowClear: true,
closeOnSelect: false, closeOnSelect: false,
}); });
@ -3258,7 +3258,7 @@ export async function getWorldEntry(name, data, entry) {
// Create wrapper div // Create wrapper div
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.textContent = t`Move "${sourceName}" to:`; wrapper.textContent = t`Move '${sourceName}' to:`;
// Create container and append elements // Create container and append elements
const container = document.createElement('div'); const container = document.createElement('div');

View File

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