From 11908f7363651bf434cf23c1d8bb39feab12e9ad Mon Sep 17 00:00:00 2001 From: Yokayo Date: Mon, 28 Apr 2025 18:45:16 +0700 Subject: [PATCH 01/13] Work on tl --- public/css/rm-groups.css | 4 +- public/index.html | 16 ++-- public/locales/ru-ru.json | 126 +++++++++++++++++++++++++------ public/script.js | 14 ++-- public/scripts/backgrounds.js | 2 +- public/scripts/group-chats.js | 6 +- public/scripts/horde.js | 6 +- public/scripts/openai.js | 2 +- public/scripts/personas.js | 6 +- public/scripts/tags.js | 11 +-- public/scripts/textgen-models.js | 21 ++++-- public/scripts/utils.js | 34 ++++++++- public/scripts/world-info.js | 4 +- 13 files changed, 191 insertions(+), 61 deletions(-) diff --git a/public/css/rm-groups.css b/public/css/rm-groups.css index 499f59873..6e12164b2 100644 --- a/public/css/rm-groups.css +++ b/public/css/rm-groups.css @@ -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%; diff --git a/public/index.html b/public/index.html index 6f1dfa016..3f8cc407f 100644 --- a/public/index.html +++ b/public/index.html @@ -2052,8 +2052,8 @@
@@ -5534,7 +5534,7 @@
-
+
@@ -5552,7 +5552,7 @@
-
+
@@ -5972,7 +5972,7 @@
-
 entries
+
 entries
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json index 00e075a68..730500d77 100644 --- a/public/locales/ru-ru.json +++ b/public/locales/ru-ru.json @@ -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. В единицах 1e-4; разумное значение - 3. Установите в 0, чтобы отключить. См. статью 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", @@ -1278,7 +1278,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", @@ -1647,8 +1647,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.": "Добавлять имена персонажа и пользователя в список стоп-строк.", "Allow Post-History Instructions": "Разрешить инструкции после истории", "context_allow_post_history_instructions": "Добавлять в конец промпта инструкции после истории. Работает только при наличии таких инструкций в карточке И при включенной опции ''Приоритет инструкциям из карточек''.\nНЕ РЕКОМЕНДУЕТСЯ ДЛЯ МОДЕЛЕЙ TEXT COMPLETION, МОЖЕТ ПОРТИТЬ ВЫХОДНОЙ ТЕКСТ.", @@ -1916,8 +1916,8 @@ "Cannot restore GUI preset": "Пресет для Gui восстановить нельзя", "Default preset cannot be restored": "Невозможно восстановить пресет по умолчанию", "Default template cannot be restored": "Невозможно восстановить шаблон по умолчанию", - "Resetting a default preset will restore the default settings": "Сброс стандартного пресета восстановит настройки по умолчанию.", - "Resetting a default template will restore the default settings.": "Сброс стандартного шаблона восстановит настройки по умолчанию.", + "Resetting a default preset will restore the default settings.": "Сброс комплектного пресета восстановит настройки по умолчанию.", + "Resetting a default template will restore the default settings.": "Сброс комплектного шаблона восстановит настройки по умолчанию.", "Are you sure?": "Вы уверены?", "Default preset restored": "Стандартный пресет восстановлен", "Default template restored": "Стандартный шаблон восстановлен", @@ -2048,11 +2048,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": "Позиция", @@ -2171,7 +2171,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": "Подробные", @@ -2276,8 +2276,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": "в группе", @@ -2338,5 +2338,87 @@ "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} ${1} hidden.": "Персонажей скрыто: ${0}.", + "pagination_of": "из", + "/ page": "/ стр.", + "Context Length": "Размер контекста", + "Added On": "Добавлена", + "Class": "Класс", + "Bulk_edit_characters": "Массовое редактирование персонажей\n\nЛКМ, чтобы выделить либо отменить выделение персонажа\nShift+ЛКМ, чтобы массово выделить либо отменить выделение персонажей\nПКМ, чтобы выбрать действие", + "Bulk select all characters": "Выбрать всех персонажей", + "Duplicate": "Клонировать", + "Next page": "След. страница", + "Previous page": "Пред. страница", + "Group:": "Группа:", + "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 установлено" } diff --git a/public/script.js b/public/script.js index 8421305f4..d3e964031 100644 --- a/public/script.js +++ b/public/script.js @@ -143,6 +143,7 @@ import { getHordeModels, adjustHordeGenerationParams, MIN_LENGTH, + initHorde } from './scripts/horde.js'; import { @@ -175,6 +176,7 @@ import { saveBase64AsFile, uuidv4, equalsIgnoreCaseAndAccents, + localizePagination } from './scripts/utils.js'; import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; @@ -229,7 +231,7 @@ import { instruct_presets, selectContextPreset, } from './scripts/instruct-mode.js'; -import { initLocales, t } from './scripts/i18n.js'; +import { initLocales, t, translate } from './scripts/i18n.js'; import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js'; import { user_avatar, @@ -994,6 +996,7 @@ async function firstLoadInit() { initAuthorsNote(); await initPersonas(); initWorldInfo(); + initHorde(); initRossMods(); initStats(); initCfg(); @@ -1425,13 +1428,13 @@ function getBackBlock() { 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 = `

${texts[roll]}

-

There are no items to display.

+

` + t`There are no items to display.` + `

`; return $(emptyBlock); } @@ -1443,7 +1446,7 @@ function getHiddenBlock(hidden) { const hiddenBlock = `
-

${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.

+

` + t`${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.` + `

`; @@ -1568,6 +1571,7 @@ export async function printCharacters(fullRefresh = false) { if (hidden > 0 && entitiesFilter.hasAnyFilter()) { $(listId).append(getHiddenBlock(hidden)); } + localizePagination($('#rm_print_characters_pagination')); eventSource.emit(event_types.CHARACTER_PAGE_LOADED); }, @@ -8429,7 +8433,7 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w } else if (['delete_extension'].includes(popup_type)) { return okButton ?? '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`; } diff --git a/public/scripts/backgrounds.js b/public/scripts/backgrounds.js index 308aac532..2352df253 100644 --- a/public/scripts/backgrounds.js +++ b/public/scripts/backgrounds.js @@ -291,7 +291,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('

Delete the background?

', 'confirm'); + const confirm = await callPopup('

' + t`Delete the background?` + '

', 'confirm'); const bg = bgToDelete.attr('bgfile'); if (confirm) { diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 91cf0a88d..15250a1fd 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -13,6 +13,7 @@ import { getBase64Async, resetScrollHeight, initScrollHeight, + localizePagination } from './utils.js'; import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js'; import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js'; @@ -1394,6 +1395,7 @@ function printGroupCandidates() { for (const i of data) { $('#rm_group_add_members').append(getGroupCharacterBlock(i.item)); } + localizePagination($('#rm_group_add_members_pagination')); }, }); } @@ -1401,6 +1403,7 @@ function printGroupCandidates() { function printGroupMembers() { const storageKey = 'GroupMembers_PerPage'; $('.rm_group_members_pagination').each(function () { + let that = this; $(this).pagination({ dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }), pageRange: 1, @@ -1421,6 +1424,7 @@ function printGroupMembers() { for (const i of data) { $('.rm_group_members').append(getGroupCharacterBlock(i.item)); } + localizePagination($(that)); }, }); }); @@ -1804,7 +1808,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'); diff --git a/public/scripts/horde.js b/public/scripts/horde.js index fc5ca93f2..27a17fe22 100644 --- a/public/scripts/horde.js +++ b/public/scripts/horde.js @@ -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, }); } -}); +}; diff --git a/public/scripts/openai.js b/public/scripts/openai.js index d1b996c37..832bdd6e4 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -3851,7 +3851,7 @@ function createLogitBiasListItem(entry) { } async function createNewLogitBiasPreset() { - const name = await callPopup('Preset name:', 'input'); + const name = await callPopup(t`Preset name:`, 'input'); if (!name) { return; diff --git a/public/scripts/personas.js b/public/scripts/personas.js index b302c37a0..70d5c68fa 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -22,12 +22,12 @@ 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 } from './utils.js'; import { debounce_timeout } from './constants.js'; import { FILTER_TYPES, FilterHelper } from './filters.js'; import { groups, selected_group } from './group-chats.js'; import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; -import { t } from './i18n.js'; +import { t, translate } from './i18n.js'; import { openWorldInfoEditor, world_names } from './world-info.js'; import { renderTemplateAsync } from './templates.js'; import { saveMetadataDebounced } from './extensions.js'; @@ -263,6 +263,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { prevText: '<', nextText: '>', formatNavigator: PAGINATION_TEMPLATE, + afterRender: function(a) {console.log(a)}, showNavigator: true, callback: function (data) { $(listId).empty(); @@ -270,6 +271,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { $(listId).append(getUserAvatarBlock(item)); } updatePersonaUIStates(); + localizePagination($('#persona_pagination_container')); }, afterSizeSelectorChange: function (e) { accountStorage.setItem(storageKey, e.target.value); diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 6f7f98298..635067cbe 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -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, @@ -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); @@ -1656,9 +1657,9 @@ async function onTagDeleteClick() { const otherTags = sortTags(tags.filter(x => x.id !== id).map(x => ({ id: x.id, name: x.name }))); const popupContent = $(` -

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:
+

` + t`Delete Tag` + `

+
` + t`Do you want to delete the tag` + `
?
+
` + t`If you want to merge all references to this tag into another tag, select it below:` + `
- - ${otherTags.map(x => ``).join('')} - `); + const popupContent = $(await renderTemplateAsync('deleteTag', {otherTags})); appendTagToList(popupContent.find('#tag_to_delete'), tag); diff --git a/public/scripts/templates/deleteTag.html b/public/scripts/templates/deleteTag.html new file mode 100644 index 000000000..667ccc00f --- /dev/null +++ b/public/scripts/templates/deleteTag.html @@ -0,0 +1,9 @@ +

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:
+ \ No newline at end of file From e4d389a5b6b70b6bda7c293cc80c7fb3c4121d2a Mon Sep 17 00:00:00 2001 From: Yokayo Date: Tue, 29 Apr 2025 17:24:49 +0700 Subject: [PATCH 06/13] eslint fix --- public/scripts/tags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index bbbf71eb8..1b9ba8502 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -1656,7 +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 = $(await renderTemplateAsync('deleteTag', {otherTags})); + const popupContent = $(await renderTemplateAsync('deleteTag', { otherTags })); appendTagToList(popupContent.find('#tag_to_delete'), tag); From a08972759178c9704583c46a0820ce512c3ea32c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 1 May 2025 17:46:02 +0300 Subject: [PATCH 07/13] Extract templates, replace pagination format --- public/script.js | 34 +++++++++++------------ public/scripts/templates/emptyBlock.html | 7 +++++ public/scripts/templates/hiddenBlock.html | 6 ++++ public/scripts/textgen-models.js | 17 ++---------- public/scripts/utils.js | 15 +--------- 5 files changed, 33 insertions(+), 46 deletions(-) create mode 100644 public/scripts/templates/emptyBlock.html create mode 100644 public/scripts/templates/hiddenBlock.html diff --git a/public/script.js b/public/script.js index b071e7e34..960ed5c55 100644 --- a/public/script.js +++ b/public/script.js @@ -1426,30 +1426,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 = [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 = ` -
- -

${texts[roll]}

-

` + t`There are no items to display.` + `

-
`; + 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 = ` -
- -

` + (hidden > 1 ? t`${hidden} characters hidden.` : t`${hidden} character hidden.`) + `

-
-
-
`; +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); } @@ -1542,13 +1538,14 @@ export async function printCharacters(fullRefresh = false) { nextText: '>', formatNavigator: PAGINATION_TEMPLATE, 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) { @@ -1569,7 +1566,8 @@ 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')); diff --git a/public/scripts/templates/emptyBlock.html b/public/scripts/templates/emptyBlock.html new file mode 100644 index 000000000..3ff057acd --- /dev/null +++ b/public/scripts/templates/emptyBlock.html @@ -0,0 +1,7 @@ +
+ +

{{text}}

+

+ There are no items to display. +

+
diff --git a/public/scripts/templates/hiddenBlock.html b/public/scripts/templates/hiddenBlock.html new file mode 100644 index 000000000..6b1d84406 --- /dev/null +++ b/public/scripts/templates/hiddenBlock.html @@ -0,0 +1,6 @@ +
+ +

{{text}}

+
+
+
diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js index dc1a1e644..a607d87b9 100644 --- a/public/scripts/textgen-models.js +++ b/public/scripts/textgen-models.js @@ -5,9 +5,9 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from import { tokenizers } from './tokenizers.js'; import { renderTemplateAsync } from './templates.js'; import { POPUP_TYPE, callGenericPopup } from './popup.js'; -import { t, translate } from './i18n.js'; +import { t } from './i18n.js'; import { accountStorage } from './util/AccountStorage.js'; -import { localizePagination } from './utils.js'; +import { localizePagination, PAGINATION_TEMPLATE } from './utils.js'; let mancerModels = []; let togetherModels = []; @@ -362,18 +362,7 @@ export async function loadFeatherlessModels(data) { showSizeChanger: false, prevText: '<', nextText: '>', - formatNavigator: function (currentPage, totalPage) { - let translated_of; - try { - translated_of = translate('pagination_of'); - if (translated_of == 'pagination_of') { - translated_of = 'of'; - } - } catch (e) { - translated_of = 'of'; - } - return (currentPage - 1) * perPage + 1 + ' - ' + currentPage * perPage + ` ${translated_of} ` + totalPage * perPage; - }, + formatNavigator: PAGINATION_TEMPLATE, showNavigator: true, callback: function (modelsOnPage, pagination) { modelCardBlock.innerHTML = ''; diff --git a/public/scripts/utils.js b/public/scripts/utils.js index a12492aa4..c760b9f97 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -18,21 +18,8 @@ import { getCurrentLocale, t, translate } from './i18n.js'; /** * Function returning pagination status string template. - * @type {function} */ -export const PAGINATION_TEMPLATE = function() { - let translated_of; - try { - translated_of = translate('pagination_of'); - if (translated_of == 'pagination_of') { - translated_of = 'of'; - } - } catch (e) { - console.error(e); - translated_of = 'of'; - } - return `<%= rangeStart %>-<%= rangeEnd %> ${translated_of} <%= totalNumber %>`; -}; +export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>'; export const localizePagination = function(container) { let options = container.find('option'); From 1822c4f91b99ec15f64c4ac4b0bdc01360427af3 Mon Sep 17 00:00:00 2001 From: Yokayo Date: Sat, 3 May 2025 18:12:18 +0700 Subject: [PATCH 08/13] More work on tl --- public/index.html | 2 +- public/locales/ru-ru.json | 4 ++-- public/script.js | 13 ++++++++++--- public/scripts/group-chats.js | 20 ++++++++++++++------ public/scripts/personas.js | 9 ++++++--- public/scripts/tags.js | 2 +- public/scripts/utils.js | 30 +++++++++++++++++++----------- 7 files changed, 53 insertions(+), 27 deletions(-) diff --git a/public/index.html b/public/index.html index 3f8cc407f..bf8d129ac 100644 --- a/public/index.html +++ b/public/index.html @@ -6707,7 +6707,7 @@
- Go back + Go back
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json index 00f94e88d..328051038 100644 --- a/public/locales/ru-ru.json +++ b/public/locales/ru-ru.json @@ -2403,7 +2403,6 @@ "Croak it": "Только кваканье лягушек и стрёкот сверчков", "${0} character hidden.": "Персонажей скрыто: ${0}.", "${0} characters hidden.": "Персонажей скрыто: ${0}.", - "pagination_of": "из", "/ page": "/ стр.", "Context Length": "Размер контекста", "Added On": "Добавлена", @@ -2421,5 +2420,6 @@ "Text Completion Preset": "Пресет для режима Text Completion", "Update enabled": "Обновить включенные", "Could not connect to API": "Не удалось подключиться к API", - "Connected to API": "Соединение с API установлено" + "Connected to API": "Соединение с API установлено", + "Go back": "Назад" } diff --git a/public/script.js b/public/script.js index 960ed5c55..c73efb23b 100644 --- a/public/script.js +++ b/public/script.js @@ -177,6 +177,8 @@ import { uuidv4, equalsIgnoreCaseAndAccents, localizePagination, + renderPaginationDropdown, + paginationDropdownChangeHandler, } from './scripts/utils.js'; import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; @@ -1525,10 +1527,12 @@ export async function printCharacters(fullRefresh = false) { const entities = getEntitiesList({ doFilter: true }); + const pageSize = Number(accountStorage.getItem(storageKey)) || per_page_default; + console.log('pageSize for characters = ' + pageSize); + 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', @@ -1537,6 +1541,7 @@ export async function printCharacters(fullRefresh = false) { prevText: '<', nextText: '>', formatNavigator: PAGINATION_TEMPLATE, + formatSizeChanger: renderPaginationDropdown(pageSize, sizeChangerOptions), showNavigator: true, callback: async function (/** @type {Entity[]} */ data) { $(listId).empty(); @@ -1573,8 +1578,10 @@ export async function printCharacters(fullRefresh = false) { eventSource.emit(event_types.CHARACTER_PAGE_LOADED); }, - afterSizeSelectorChange: function (e) { + afterSizeSelectorChange: function (e, size) { accountStorage.setItem(storageKey, e.target.value); + console.log('pageSize for characters after getting updated = ' + accountStorage.getItem(storageKey)); + paginationDropdownChangeHandler(e, size); }, afterPaging: function (e) { saveCharactersPage = e; diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 78ce0b275..b3b0f3806 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -14,6 +14,8 @@ import { 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'; @@ -1375,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, @@ -1383,12 +1387,13 @@ 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(); @@ -1404,6 +1409,8 @@ 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, @@ -1414,10 +1421,11 @@ 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(); diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 6b9fc1e0d..1aeefa0b9 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -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, localizePagination } 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, @@ -272,8 +274,9 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { 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; diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 1b9ba8502..2705da21c 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -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'); diff --git a/public/scripts/utils.js b/public/scripts/utils.js index c760b9f97..dd500efb4 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -22,21 +22,29 @@ import { getCurrentLocale, t, translate } from './i18n.js'; export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>'; export const localizePagination = function(container) { - let options = container.find('option'); - for (let option of options) { - option = $(option); - try { - option.text(option.text().replace('/ page', translate('/ page'))); - } catch (e) { - // means that i18n facilities aren't ready, so there's no point doing anything - console.error(e); - return; - } - } container.find('[title="Next page"]').attr('title', translate('Next page')); container.find('[title="Previous page"]').attr('title', translate('Previous page')); }; +export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) { + let sizeSelect = ``; + return sizeSelect; +} + +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. * @enum {number} From e27fca66283b26e74b8afbc80f9c39b0270b5f4c Mon Sep 17 00:00:00 2001 From: Yokayo Date: Sat, 3 May 2025 18:14:26 +0700 Subject: [PATCH 09/13] eslint fixes --- public/scripts/utils.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/scripts/utils.js b/public/scripts/utils.js index dd500efb4..85b4bf861 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -27,23 +27,23 @@ export const localizePagination = function(container) { }; export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) { - let sizeSelect = `'; if (sizeChangerOptions.indexOf(pageSize) === -1) { - sizeChangerOptions.unshift(pageSize); - sizeChangerOptions.sort((a, b) => a - b); + sizeChangerOptions.unshift(pageSize); + sizeChangerOptions.sort((a, b) => a - b); } for (let i = 0; i < sizeChangerOptions.length; i++) { - sizeSelect += ``; + sizeSelect += ``; } sizeSelect += ``; return sizeSelect; -} +}; 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. From b9383ace1e602a7356806e96f667f42e8b24681e Mon Sep 17 00:00:00 2001 From: Yokayo Date: Sat, 3 May 2025 18:16:02 +0700 Subject: [PATCH 10/13] eslint fixes 2 --- public/scripts/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 85b4bf861..47ff7d139 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -33,9 +33,9 @@ export const renderPaginationDropdown = function(pageSize, sizeChangerOptions) { sizeChangerOptions.sort((a, b) => a - b); } for (let i = 0; i < sizeChangerOptions.length; i++) { - sizeSelect += ``; + sizeSelect += `'; } - sizeSelect += ``; + sizeSelect += ''; return sizeSelect; }; From 1e573426398b00adb49316dd8a1eab7d9618b0c1 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 4 May 2025 12:56:23 +0300 Subject: [PATCH 11/13] Use objects for pagination select creation --- public/scripts/utils.js | 29 ++++++++++++++++++++++------- public/style.css | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 47ff7d139..4c41d547a 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -14,7 +14,7 @@ import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; import { getTagsList } from './tags.js'; import { groups, selected_group } from './group-chats.js'; -import { getCurrentLocale, t, translate } from './i18n.js'; +import { getCurrentLocale, t } from './i18n.js'; /** * Function returning pagination status string template. @@ -22,21 +22,36 @@ import { getCurrentLocale, t, translate } from './i18n.js'; export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>'; export const localizePagination = function(container) { - container.find('[title="Next page"]').attr('title', translate('Next page')); - container.find('[title="Previous page"]').attr('title', translate('Previous page')); + 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) { - let sizeSelect = ''; - return sizeSelect; + + return sizeSelect.outerHTML; }; export const paginationDropdownChangeHandler = function(event, size) { diff --git a/public/style.css b/public/style.css index faa7524bc..cc29f1551 100644 --- a/public/style.css +++ b/public/style.css @@ -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 { From bf9ef8fa0fd56525f004ae6375d54a439edf1d91 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 4 May 2025 14:00:55 +0300 Subject: [PATCH 12/13] Remove debug logs --- public/script.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/script.js b/public/script.js index c73efb23b..3d07b782a 100644 --- a/public/script.js +++ b/public/script.js @@ -1528,7 +1528,6 @@ export async function printCharacters(fullRefresh = false) { const entities = getEntitiesList({ doFilter: true }); const pageSize = Number(accountStorage.getItem(storageKey)) || per_page_default; - console.log('pageSize for characters = ' + pageSize); const sizeChangerOptions = [10, 25, 50, 100, 250, 500, 1000]; $('#rm_print_characters_pagination').pagination({ dataSource: entities, @@ -1580,7 +1579,6 @@ export async function printCharacters(fullRefresh = false) { }, afterSizeSelectorChange: function (e, size) { accountStorage.setItem(storageKey, e.target.value); - console.log('pageSize for characters after getting updated = ' + accountStorage.getItem(storageKey)); paginationDropdownChangeHandler(e, size); }, afterPaging: function (e) { From 4e0685f9985542e86896fa94943d0a0eac03b2d6 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 4 May 2025 14:05:44 +0300 Subject: [PATCH 13/13] Revert comment --- public/scripts/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 4c41d547a..6f13a9794 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -17,7 +17,8 @@ import { groups, selected_group } from './group-chats.js'; import { getCurrentLocale, t } from './i18n.js'; /** - * Function returning pagination status string template. + * Pagination status string template. + * @type {string} */ export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> .. <%= totalNumber %>';