Update tl

9d664bc679b5ff61a9490ee29421beabbf1d15dd

Yokayo <nonameutubecomm@gmail.com>

14 files changed, +181 -94Ignore whitespace
public/css/promptmanager.css+1 -1
@@ -333,7 +333,7 @@
333}333}
334334
335.completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt:disabled)>div:first-child::after {335.completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt:disabled)>div:first-child::after {
336 content: 'The content of this prompt is pulled from elsewhere and cannot be edited here.';336 content: attr(external_piece_text);
337 display: block;337 display: block;
338 width: 100%;338 width: 100%;
339 font-weight: 600;339 font-weight: 600;
public/index.html+2 -2
@@ -2782,7 +2782,7 @@
2782 </div>2782 </div>
2783 </form>2783 </form>
2784 <form id="claude_form" data-source="claude" action="javascript:void(null);" method="post" enctype="multipart/form-data">2784 <form id="claude_form" data-source="claude" action="javascript:void(null);" method="post" enctype="multipart/form-data">
2785 <h4>Claude API Key</h4>2785 <h4 data-i18n="Claude API Key">Claude API Key</h4>
2786 <div>2786 <div>
2787 <span data-i18n="Get your key from">Get your key from </span> <a target="_blank" href="https://console.anthropic.com/account/keys" data-i18n="Anthropic's developer console">Anthropic's developer console</a>.2787 <span data-i18n="Get your key from">Get your key from </span> <a target="_blank" href="https://console.anthropic.com/account/keys" data-i18n="Anthropic's developer console">Anthropic's developer console</a>.
2788 </div>2788 </div>
@@ -5999,7 +5999,7 @@
5999 </div>5999 </div>
6000 </div>6000 </div>
6001 <div class="completion_prompt_manager_popup_entry_form_control">6001 <div class="completion_prompt_manager_popup_entry_form_control">
6002 <div class="flex-container alignItemsCenter">6002 <div class="flex-container alignItemsCenter" data-i18n="[external_piece_text]The content of this prompt is pulled from elsewhere and cannot be edited here." external_piece_text="The content of this prompt is pulled from elsewhere and cannot be edited here.">
6003 <div class="flex1">6003 <div class="flex1">
6004 <label for="completion_prompt_manager_popup_entry_form_prompt">6004 <label for="completion_prompt_manager_popup_entry_form_prompt">
6005 <span data-i18n="Prompt">Prompt</span>6005 <span data-i18n="Prompt">Prompt</span>
public/locales/ru-ru.json+91 -7
@@ -177,7 +177,7 @@
177 "(Saved to Context Template)": "(Сохраняется в шаблоне контекста)",177 "(Saved to Context Template)": "(Сохраняется в шаблоне контекста)",
178 "Tokenizer": "Токенайзер",178 "Tokenizer": "Токенайзер",
179 "Token Padding": "Кол-во добавочных токенов",179 "Token Padding": "Кол-во добавочных токенов",
180 "Save preset as": "Сохранить пресет как",180 "Save preset as": "Сохранить пресет как...",
181 "Always add character's name to prompt": "Всегда добавлять имя персонажа в промпт",181 "Always add character's name to prompt": "Всегда добавлять имя персонажа в промпт",
182 "Use as Stop Strings": "Использовать в качестве стоп-строк",182 "Use as Stop Strings": "Использовать в качестве стоп-строк",
183 "Bind to Context": "Привязка к контексту",183 "Bind to Context": "Привязка к контексту",
@@ -275,12 +275,12 @@
275 "Chatty": "Разговорчивый",275 "Chatty": "Разговорчивый",
276 "Examples of dialogue": "Примеры диалога",276 "Examples of dialogue": "Примеры диалога",
277 "Save": "Сохранить",277 "Save": "Сохранить",
278 "Chat History": "История чата",278 "Chat History": "История чатов",
279 "Content": "Содержание",279 "Content": "Содержание",
280 "Selective": "Выборочно",280 "Selective": "Выборочно",
281 "Disable": "Отключено",281 "Disable": "Отключено",
282 "Back to parent chat": "Вернуться в основной чат",282 "Back to parent chat": "Вернуться в основной чат",
283 "Convert to group": "Превратить в группу",283 "Convert to group": "Сделать групповым",
284 "Start new chat": "Начать новый чат",284 "Start new chat": "Начать новый чат",
285 "Delete messages": "Удалить сообщения",285 "Delete messages": "Удалить сообщения",
286 "Impersonate": "Перевоплощение",286 "Impersonate": "Перевоплощение",
@@ -434,7 +434,7 @@
434 "Persona Description": "Описание персоны",434 "Persona Description": "Описание персоны",
435 "Your Persona": "Ваша персона",435 "Your Persona": "Ваша персона",
436 "Show notifications on switching personas": "Показывать уведомления при смене персоны",436 "Show notifications on switching personas": "Показывать уведомления при смене персоны",
437 "In Story String / Prompt Manager": "В строке истории / Менеджер промптов",437 "In Story String / Prompt Manager": "В строке истории / Менеджере промптов",
438 "Top of Author's Note": "Сверху от заметок автора",438 "Top of Author's Note": "Сверху от заметок автора",
439 "Bottom of Author's Note": "Снизу от заметок автора",439 "Bottom of Author's Note": "Снизу от заметок автора",
440 "How do I use this?": "Как пользоваться?",440 "How do I use this?": "Как пользоваться?",
@@ -1160,7 +1160,8 @@
1160 "ext_regex_global_scripts_desc": "Распространяются на всех персонажей. Сохраняются как часть настроек.",1160 "ext_regex_global_scripts_desc": "Распространяются на всех персонажей. Сохраняются как часть настроек.",
1161 "ext_regex_scoped_scripts": "Локальные скрипты",1161 "ext_regex_scoped_scripts": "Локальные скрипты",
1162 "ext_regex_scoped_scripts_desc": "Распространяются только на этого персонажа. Сохраняются в его карточку.",1162 "ext_regex_scoped_scripts_desc": "Распространяются только на этого персонажа. Сохраняются в его карточку.",
1163 "ext_regex_allow_scoped": "Разрешить использование локальных скриптов",1163 "ext_regex_allow_scoped": "Включить локальные скрипты",
1164 "ext_regex_disallow_scoped": "Отключить локальные скрипты",
1164 "ext_regex_move_to_global": "Сделать глобальным",1165 "ext_regex_move_to_global": "Сделать глобальным",
1165 "openai_logit_bias_no_items": "Правил нет",1166 "openai_logit_bias_no_items": "Правил нет",
1166 "Enable function calling": "Включить функции",1167 "Enable function calling": "Включить функции",
@@ -1903,7 +1904,7 @@
1903 "No sections selected for export": "Не выбрано ни одной секции для экспорта",1904 "No sections selected for export": "Не выбрано ни одной секции для экспорта",
1904 "Cannot update GUI preset": "Пресет для GUI обновить невозможно",1905 "Cannot update GUI preset": "Пресет для GUI обновить невозможно",
1905 "Template updated": "Шаблон сохранён",1906 "Template updated": "Шаблон сохранён",
1906 "Hint: Use a character/group name to bind preset to a specific chat.": "Совет: введите имя персонажа/группы, чтобы привязать пресет к определённому чату.",1907 "Hint: Use a character/group name to bind preset to a specific chat.": "Совет: введите имя персонажа/группы, чтобы привязать пресет к определённым чатам.",
1907 "Preset name:": "Название пресета:",1908 "Preset name:": "Название пресета:",
1908 "Template name:": "Название шаблона:",1909 "Template name:": "Название шаблона:",
1909 "Preset saved": "Пресет сохранён",1910 "Preset saved": "Пресет сохранён",
@@ -1951,5 +1952,88 @@
1951 "from other websites": "с других сайтов.",1952 "from other websites": "с других сайтов.",
1952 "Go to the": "Загляните в",1953 "Go to the": "Загляните в",
1953 "to install additional features.": ", чтобы установить разные дополнительные ресурсы.",1954 "to install additional features.": ", чтобы установить разные дополнительные ресурсы.",
1954 "or_welcome": "; также доступен"1955 "or_welcome": "; также доступен",
1956 "Claude API Key": "Ключ от API Claude",
1957 "Block Entropy API Key": "Ключ от API Block Entropy",
1958 "Select a Model": "Выберите модель",
1959 "The content of this prompt is pulled from elsewhere and cannot be edited here.": "Эта часть промпта подтягивается откуда-то извне. Отредактировать её здесь невозможно.",
1960 "save": "сохранить",
1961 "reset": "сбросить",
1962 "close": "закрыть",
1963 "Your preset contains proxy and/or custom endpoint settings.": "Ваш пресет содержит настройки прокси и/или настройки кастомного эндпоинта.",
1964 "Do you want to remove these fields before exporting?": "Желаете ли удалить эти поля перед экспортом?",
1965 "Save": "Сохранить",
1966 "Chat Lorebook": "Лорбук для чата",
1967 "chat_world_template_txt": "Выбранный мир будет привязан к этому чату. Будет добавляться в промпт наряду с глобальным лорбуком и лором персонажа.",
1968 "world_button_title": "Лор персонажа\n\nНажмите, чтобы загрузить\nShift + клик, чтобы открыть диалог привязки мира",
1969 "No auxillary Lorebooks set. Click here to select.": "Вспомогательный лорбук не выбран. Нажмите, чтобы выбрать.",
1970 "ext_regex_user_input_desc": "Отправленные вами сообщения.",
1971 "ext_regex_ai_input_desc": "Полученные от API ответы.",
1972 "ext_regex_slash_desc": "Сообщения, отправленные с помощью команд STscript.",
1973 "ext_regex_wi_desc": "Содержимое лорбуков/информации о мире. Работает только со включенной галочкой \"Только промпт\"!",
1974 "ext_regex_run_on_edit_desc": "Выполнять скрипт при редактировании принадлежащего обозначенной роли (ролям) сообщений.",
1975 "Convert to group chat": "Сделать чат групповым",
1976 "Are you sure you want to convert this chat to a group chat?": "Вы точно хотите сделать этот чат групповым?",
1977 "This cannot be reverted.": "Отменить будет невозможно.",
1978 "Enter a name for this persona:": "Введите имя для этой персоны:",
1979 "Cancel if you're just uploading an avatar.": "Отмените, если просто загружаете аватарку.",
1980 "Set the crop position of the avatar image": "Обозначьте зону, по которой будет обрезана аватарка",
1981 "popup-button-crop": "Обрезать",
1982 "Duplicate persona": "Клонировать персону",
1983 "Are you sure you want to duplicate this persona?": "Вы точно хотите клонировать эту персону?",
1984 "Enter a description for this persona:": "Введите описание для этой персоны:",
1985 "You can always add or change it later.": "Его можно будет добавить или изменить в любое время.",
1986 "You can now pick ${0} as a persona in the Persona Management menu.": "Теперь ${0} может быть выбран(а) в качестве персоны в меню \"Управление персоной\".",
1987 "Persona Created": "Персона создана",
1988 "Overwrite Existing Persona": "Перезаписать существующую персону",
1989 "This character exists as a persona already. Do you want to overwrite it?": "Этот персонаж уже существует в виде персоны. Желаете перезаписать?",
1990 "Persona Description Macros": "Макросы в описании персоны",
1991 "This character has a description that uses <code>{{char}}</code> or <code>{{user}}</code> macros. Do you want to swap them in the persona description?": "В описании персонажа присутствуют макросы <code>{{char}}</code> и/или <code>{{user}}</code>. Желаете поменять их местами?",
1992 "(If empty name is provided, this will unbind the name from this avatar)": "(если оставить пустым, то имя будет отвязано от этой аватарки)",
1993 "To permanently set \"${0}\" as the selected persona, unlock and relock it using the \"Lock\" button. Otherwise, the selection resets upon reloading the chat.": "Чтобы перманентно привязать персону \"${0}\" к этому чату, открепите текущую персону и затем закрепите новую кнопкой \"Закрепить\". Иначе при следующем заходе персона будет сброшена.",
1994 "This chat is locked to a different persona (${0}).": "К этому чату уже привязана другая персона (${0}).",
1995 "User persona is now unlocked for this chat. Click the \"Lock\" again to revert.": "Персона откреплена. Нажмите \"Закрепить\", чтобы снова зафиксировать её в этом чате.",
1996 "Persona unlocked": "Персона откреплена",
1997 "Creating a new persona for currently selected user name and avatar...": "Создаём новую персону для этого имени и аватарки...",
1998 "Persona not set for this avatar": "К этой аватарке не привязано ни одной персоны",
1999 "User persona is locked to ${0} in this chat": "${0} установлен(а) в качестве фиксированной персоны в этом чате",
2000 "You cannot delete the avatar you are currently using": "Невозможно удалить аватарку, которую вы используете прямо сейчас",
2001 "Warning": "Внимание",
2002 "Are you sure you want to delete this avatar?": "Вы точно хотите удалить эту аватарку?",
2003 "All information associated with its linked persona will be lost.": "Вся информация о связанной с ней персоне будет утеряна.",
2004 "The default persona was deleted. You will need to set a new default persona.": "Удалена персона по умолчанию. Вам будет необходимо выбрать новую вместо неё.",
2005 "Default persona deleted": "Удалена персона по умолчанию",
2006 "The locked persona was deleted. You will need to set a new persona for this chat.": "Удалена привязанная к чату персона. Вам будет необходимо выбрать новую фиксированную персону для этого чата.",
2007 "Persona deleted": "Персона удалена",
2008 "You must bind a name to this persona before you can set it as the default.": "Прежде чем установить эту персону в качестве персоны по умолчанию, ей необходимо задать имя.",
2009 "Persona name not set": "У персоны отсутствует имя",
2010 "Are you sure you want to remove the default persona?": "Вы точно хотите снять статус персоны по умолчанию?",
2011 "This persona will no longer be used by default when you open a new chat.": "Эта персона больше не будет автоматически выбираться при старте нового чата",
2012 "Default persona removed": "Персона по умолчанию снята",
2013 "Are you sure you want to set \"${0}\" as the default persona?": "Вы точно хотите установить \"${0}\" в качестве персоны по умолчанию?",
2014 "This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.": "Это имя и аватарка будут автоматически выбираться при старте нового чата, а также в любом существующем чате, где нет фиксированной персоны.",
2015 "This persona will be used by default when you open a new chat.": "Эта персона будет выбираться автоматически при старте нового чата.",
2016 "Default persona set to ${0}": "${0} установлен(а) в качестве персоны по умолчанию",
2017 "Invalid file selected": "Загружен невалидный файл",
2018 "Invalid file format": "Некорректный формат файла",
2019 "Personas restored with warnings. Check console for details.": "Персона восстановлена, но были сгенерированы варнинги. Подробности см. в консоли.",
2020 "Personas restored successfully.": "Персона успешно восстановлена.",
2021 "All user-sent messages in this chat will be attributed to ${0}.": "Все ваши сообщения в этом чате будут приписаны ${0}.",
2022 "Forbid Media Override explanation": "Способность текущего персонажа/группы использовать в чате внешние медиа.",
2023 "forbid_media_global_state_forbidden": "(запрещены)",
2024 "forbid_media_global_state_allowed": "(разрешены)",
2025 "Always forbidden": "Всегда запрещены",
2026 "Always allowed": "Всегда разрешены",
2027 "Forbid Media Override subtitle": "Под медиа подразумеваются картинки, видео, аудио. Под внешними - любые, не хостящиеся на локальном сервере.",
2028 "Tag Management": "Управление тегами",
2029 "Save your tags to a file": "Сохранить ваши теги в файл",
2030 "Restore tags from a file": "Восстановить теги из файла",
2031 "Create a new tag": "Создать новый тег",
2032 "Drag handle to reorder. Click name to rename. Click color to change display.": "Перемещайте мышкой, чтобы менять порядок. Нажмите, чтобы переименовать. Нажмите на цвет, чтобы сменить его.",
2033 "Click on the folder icon to use this tag as a folder.": "Нажмите на иконку папки, чтобы использовать тег как папку.",
2034 "Use alphabetical sorting": "Сортировать по алфавиту",
2035 "tags_sorting_desc": "Автоматически сортировать теги по алфавиту после создания или переименования одного из них.\nПри выключении новые теги будут просто добавляться в конец.\n\nЕсли вручную перетащить один из тегов на другое место, автосортировка отключится.",
2036 "Imported tags:": "Импортируемые теги:",
2037 "Importing Tags": "Импорт тегов",
2038 "Couldn't import tags:": "Не удалось импортировать теги:"
1955}2039}
public/script.js+2 -2
@@ -7491,7 +7491,7 @@ export function callPopup(text, type, inputValue = '', { okButton, rows, wide, w
7491 } else if (['new_chat', 'confirm'].includes(popup_type)) {7491 } else if (['new_chat', 'confirm'].includes(popup_type)) {
7492 return okButton ?? 'Yes';7492 return okButton ?? 'Yes';
7493 } else if (['input'].includes(popup_type)) {7493 } else if (['input'].includes(popup_type)) {
7494 return okButton ?? 'Save';7494 return okButton ?? t`Save`;
7495 }7495 }
7496 return okButton ?? 'Delete';7496 return okButton ?? 'Delete';
7497 }7497 }
@@ -7861,7 +7861,7 @@ function openCharacterWorldPopup() {
7861 if (!isMobile()) {7861 if (!isMobile()) {
7862 $(extraSelect).select2({7862 $(extraSelect).select2({
7863 width: '100%',7863 width: '100%',
7864 placeholder: 'No auxillary Lorebooks set. Click here to select.',7864 placeholder: t`No auxillary Lorebooks set. Click here to select.`,
7865 allowClear: true,7865 allowClear: true,
7866 closeOnSelect: false,7866 closeOnSelect: false,
7867 });7867 });
public/scripts/bookmarks.js+2 -1
@@ -32,6 +32,7 @@ import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsPro
32import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';32import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
33import { createTagMapFromList } from './tags.js';33import { createTagMapFromList } from './tags.js';
34import { renderTemplateAsync } from './templates.js';34import { renderTemplateAsync } from './templates.js';
35import { t } from './i18n.js';
3536
36import {37import {
37 getUniqueName,38 getUniqueName,
@@ -263,7 +264,7 @@ export async function convertSoloToGroupChat() {
263 return;264 return;
264 }265 }
265266
266 const confirm = await Popup.show.confirm('Convert to group chat', 'Are you sure you want to convert this chat to a group chat?<br />This cannot be reverted.');267 const confirm = await Popup.show.confirm(t`Convert to group chat`, t`Are you sure you want to convert this chat to a group chat?` + `<br />` + t`This cannot be reverted.`);
267 if (!confirm) {268 if (!confirm) {
268 return;269 return;
269 }270 }
public/scripts/extensions/regex/dropdown.html+1 -1
@@ -38,7 +38,7 @@
38 <strong class="flex1" data-i18n="ext_regex_scoped_scripts">Scoped Scripts</strong>38 <strong class="flex1" data-i18n="ext_regex_scoped_scripts">Scoped Scripts</strong>
39 <label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">39 <label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">
40 <input type="checkbox" id="regex_scoped_toggle" class="enable_scoped" />40 <input type="checkbox" id="regex_scoped_toggle" class="enable_scoped" />
41 <span class="regex-toggle-on fa-solid fa-toggle-on fa-lg" title="Disallow using scoped regex"></span>41 <span class="regex-toggle-on fa-solid fa-toggle-on fa-lg" data-i18n="[title]ext_regex_disallow_scoped" title="Disallow using scoped regex"></span>
42 <span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" data-i18n="[title]ext_regex_allow_scoped" title="Allow using scoped regex"></span>42 <span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" data-i18n="[title]ext_regex_allow_scoped" title="Allow using scoped regex"></span>
43 </label>43 </label>
44 </div>44 </div>
public/scripts/extensions/regex/editor.html+5 -5
@@ -70,25 +70,25 @@
70 <div class="flex-container">70 <div class="flex-container">
71 <div class="flex1 wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">71 <div class="flex1 wi-enter-footer-text flex-container flexFlowColumn flexNoGap alignitemsstart">
72 <small data-i18n="ext_regex_affects">Affects</small>72 <small data-i18n="ext_regex_affects">Affects</small>
73 <div title="Messages sent by the user.">73 <div data-i18n="[title]ext_regex_user_input_desc" title="Messages sent by the user.">
74 <label class="checkbox flex-container">74 <label class="checkbox flex-container">
75 <input type="checkbox" name="replace_position" value="1">75 <input type="checkbox" name="replace_position" value="1">
76 <span data-i18n="ext_regex_user_input">User Input</span>76 <span data-i18n="ext_regex_user_input">User Input</span>
77 </label>77 </label>
78 </div>78 </div>
79 <div title="Messages received from the Generation API.">79 <div data-i18n="[title]ext_regex_ai_input_desc" title="Messages received from the Generation API.">
80 <label class="checkbox flex-container">80 <label class="checkbox flex-container">
81 <input type="checkbox" name="replace_position" value="2">81 <input type="checkbox" name="replace_position" value="2">
82 <span data-i18n="ext_regex_ai_output">AI Output</span>82 <span data-i18n="ext_regex_ai_output">AI Output</span>
83 </label>83 </label>
84 </div>84 </div>
85 <div title="Messages sent using STscript commands.">85 <div data-i18n="[title]ext_regex_slash_desc" title="Messages sent using STscript commands.">
86 <label class="checkbox flex-container">86 <label class="checkbox flex-container">
87 <input type="checkbox" name="replace_position" value="3">87 <input type="checkbox" name="replace_position" value="3">
88 <span data-i18n="Slash Commands">Slash Commands</span>88 <span data-i18n="Slash Commands">Slash Commands</span>
89 </label>89 </label>
90 </div>90 </div>
91 <div title="Lorebook/World Info entry contents. Requires 'Only Format Prompt' to be checked!">91 <div data-i18n="[title]ext_regex_wi_desc" title="Lorebook/World Info entry contents. Requires 'Only Format Prompt' to be checked!">
92 <label class="checkbox flex-container">92 <label class="checkbox flex-container">
93 <input type="checkbox" name="replace_position" value="5">93 <input type="checkbox" name="replace_position" value="5">
94 <span data-i18n="World Info">World Info</span>94 <span data-i18n="World Info">World Info</span>
@@ -117,7 +117,7 @@
117 <input type="checkbox" name="disabled" />117 <input type="checkbox" name="disabled" />
118 <span data-i18n="Disabled">Disabled</span>118 <span data-i18n="Disabled">Disabled</span>
119 </label>119 </label>
120 <label class="checkbox flex-container" title="Run the regex script when the message belonging a to specified role(s) is edited.">120 <label class="checkbox flex-container" data-i18n="[title]ext_regex_run_on_edit_desc" title="Run the regex script when the message belonging a to specified role(s) is edited.">
121 <input type="checkbox" name="run_on_edit" />121 <input type="checkbox" name="run_on_edit" />
122 <span data-i18n="Run On Edit">Run On Edit</span>122 <span data-i18n="Run On Edit">Run On Edit</span>
123 </label>123 </label>
public/scripts/extensions/regex/index.js+2 -1
@@ -9,6 +9,7 @@ import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashComm
9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
10import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';10import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
11import { regex_placement, runRegexScript } from './engine.js';11import { regex_placement, runRegexScript } from './engine.js';
12import { t } from '../../i18n.js';
1213
13/**14/**
14 * @typedef {object} RegexScript15 * @typedef {object} RegexScript
@@ -275,7 +276,7 @@ async function onRegexEditorOpenClick(existingId, isScoped) {
275276
276 editorHtml.find('input, textarea, select').on('input', updateTestResult);277 editorHtml.find('input, textarea, select').on('input', updateTestResult);
277278
278 const popupResult = await callPopup(editorHtml, 'confirm', undefined, { okButton: 'Save' });279 const popupResult = await callPopup(editorHtml, 'confirm', undefined, { okButton: t`Save` });
279 if (popupResult) {280 if (popupResult) {
280 const newRegexScript = {281 const newRegexScript = {
281 id: existingId ? String(existingId) : uuidv4(),282 id: existingId ? String(existingId) : uuidv4(),
public/scripts/openai.js+4 -4
@@ -3608,8 +3608,8 @@ async function onExportPresetClick() {
36083608
3609 const fieldValues = sensitiveFields.filter(field => preset[field]).map(field => `<b>${field}</b>: <code>${preset[field]}</code>`);3609 const fieldValues = sensitiveFields.filter(field => preset[field]).map(field => `<b>${field}</b>: <code>${preset[field]}</code>`);
3610 const shouldConfirm = fieldValues.length > 0;3610 const shouldConfirm = fieldValues.length > 0;
3611 const textHeader = 'Your preset contains proxy and/or custom endpoint settings.';3611 const textHeader = t`Your preset contains proxy and/or custom endpoint settings.`;
3612 const textMessage = `<div>Do you want to remove these fields before exporting?</div><br>${DOMPurify.sanitize(fieldValues.join('<br>'))}`;3612 const textMessage = `<div>` + t`Do you want to remove these fields before exporting?` + `</div><br>${DOMPurify.sanitize(fieldValues.join('<br>'))}`;
3613 const cancelButton = { text: 'Cancel', result: POPUP_RESULT.CANCELLED, appendAtEnd: true };3613 const cancelButton = { text: 'Cancel', result: POPUP_RESULT.CANCELLED, appendAtEnd: true };
3614 const popupOptions = { customButtons: [cancelButton] };3614 const popupOptions = { customButtons: [cancelButton] };
3615 const popupResult = await Popup.show.confirm(textHeader, textMessage, popupOptions);3615 const popupResult = await Popup.show.confirm(textHeader, textMessage, popupOptions);
@@ -4365,8 +4365,8 @@ async function onOpenrouterModelSortChange() {
43654365
4366async function onNewPresetClick() {4366async function onNewPresetClick() {
4367 const popupText = `4367 const popupText = `
4368 <h3>Preset name:</h3>4368 <h3>` + t`Preset name:` + `</h3>
4369 <h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;4369 <h4>` + t`Hint: Use a character/group name to bind preset to a specific chat.` + `</h4>`;
4370 const name = await callPopup(popupText, 'input', oai_settings.preset_settings_openai);4370 const name = await callPopup(popupText, 'input', oai_settings.preset_settings_openai);
43714371
4372 if (!name) {4372 if (!name) {
public/scripts/personas.js+32 -31
@@ -22,6 +22,7 @@ import { debounce_timeout } from './constants.js';
22import { FILTER_TYPES, FilterHelper } from './filters.js';22import { FILTER_TYPES, FilterHelper } from './filters.js';
23import { selected_group } from './group-chats.js';23import { selected_group } from './group-chats.js';
24import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';24import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js';
25import { t } from './i18n.js';
2526
26let savePersonasPage = 0;27let savePersonasPage = 0;
27const GRID_STORAGE_KEY = 'Personas_GridView';28const GRID_STORAGE_KEY = 'Personas_GridView';
@@ -276,7 +277,7 @@ async function changeUserAvatar(e) {
276 let url = '/api/avatars/upload';277 let url = '/api/avatars/upload';
277278
278 if (!power_user.never_resize_avatars) {279 if (!power_user.never_resize_avatars) {
279 const dlg = new Popup('Set the crop position of the avatar image', POPUP_TYPE.CROP, '', { cropImage: dataUrl });280 const dlg = new Popup(t`Set the crop position of the avatar image`, POPUP_TYPE.CROP, '', { cropImage: dataUrl });
280 const result = await dlg.show();281 const result = await dlg.show();
281282
282 if (!result) {283 if (!result) {
@@ -331,23 +332,23 @@ async function changeUserAvatar(e) {
331 * @returns {Promise} Promise that resolves when the persona is set332 * @returns {Promise} Promise that resolves when the persona is set
332 */333 */
333export async function createPersona(avatarId) {334export async function createPersona(avatarId) {
334 const personaName = await Popup.show.input('Enter a name for this persona:', 'Cancel if you\'re just uploading an avatar.', '');335 const personaName = await Popup.show.input(t`Enter a name for this persona:`, t`Cancel if you're just uploading an avatar.`, '');
335336
336 if (!personaName) {337 if (!personaName) {
337 console.debug('User cancelled creating a persona');338 console.debug('User cancelled creating a persona');
338 return;339 return;
339 }340 }
340341
341 const personaDescription = await Popup.show.input('Enter a description for this persona:', 'You can always add or change it later.', '', { rows: 4 });342 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });
342343
343 initPersona(avatarId, personaName, personaDescription);344 initPersona(avatarId, personaName, personaDescription);
344 if (power_user.persona_show_notifications) {345 if (power_user.persona_show_notifications) {
345 toastr.success(`You can now pick ${personaName} as a persona in the Persona Management menu.`, 'Persona Created');346 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);
346 }347 }
347}348}
348349
349async function createDummyPersona() {350async function createDummyPersona() {
350 const personaName = await Popup.show.input('Enter a name for this persona:', null);351 const personaName = await Popup.show.input(t`Enter a name for this persona:`, null);
351352
352 if (!personaName) {353 if (!personaName) {
353 console.debug('User cancelled creating dummy persona');354 console.debug('User cancelled creating dummy persona');
@@ -393,7 +394,7 @@ export async function convertCharacterToPersona(characterId = null) {
393 const overwriteName = `${name} (Persona).png`;394 const overwriteName = `${name} (Persona).png`;
394395
395 if (overwriteName in power_user.personas) {396 if (overwriteName in power_user.personas) {
396 const confirm = await Popup.show.confirm('Overwrite Existing Persona', 'This character exists as a persona already. Do you want to overwrite it?');397 const confirm = await Popup.show.confirm(t`Overwrite Existing Persona`, t`This character exists as a persona already. Do you want to overwrite it?`);
397 if (!confirm) {398 if (!confirm) {
398 console.log('User cancelled the overwrite of the persona');399 console.log('User cancelled the overwrite of the persona');
399 return;400 return;
@@ -401,7 +402,7 @@ export async function convertCharacterToPersona(characterId = null) {
401 }402 }
402403
403 if (description.includes('{{char}}') || description.includes('{{user}}')) {404 if (description.includes('{{char}}') || description.includes('{{user}}')) {
404 const confirm = await Popup.show.confirm('Persona Description Macros', 'This character has a description that uses <code>{{char}}</code> or <code>{{user}}</code> macros. Do you want to swap them in the persona description?');405 const confirm = await Popup.show.confirm(t`Persona Description Macros`, t`This character has a description that uses <code>{{char}}</code> or <code>{{user}}</code> macros. Do you want to swap them in the persona description?`);
405 if (confirm) {406 if (confirm) {
406 description = description.replace(/{{char}}/gi, '{{personaChar}}').replace(/{{user}}/gi, '{{personaUser}}');407 description = description.replace(/{{char}}/gi, '{{personaChar}}').replace(/{{user}}/gi, '{{personaUser}}');
407 description = description.replace(/{{personaUser}}/gi, '{{char}}').replace(/{{personaChar}}/gi, '{{user}}');408 description = description.replace(/{{personaUser}}/gi, '{{char}}').replace(/{{personaChar}}/gi, '{{user}}');
@@ -427,7 +428,7 @@ export async function convertCharacterToPersona(characterId = null) {
427 saveSettingsDebounced();428 saveSettingsDebounced();
428429
429 console.log('Persona for character created');430 console.log('Persona for character created');
430 toastr.success(`You can now select ${name} as a persona in the Persona Management menu.`, 'Persona Created');431 toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`);
431432
432 // Refresh the persona selector433 // Refresh the persona selector
433 await getUserAvatars(true, overwriteName);434 await getUserAvatars(true, overwriteName);
@@ -509,8 +510,8 @@ async function bindUserNameToPersona(e) {
509 let personaUnbind = false;510 let personaUnbind = false;
510 const existingPersona = power_user.personas[avatarId];511 const existingPersona = power_user.personas[avatarId];
511 const personaName = await Popup.show.input(512 const personaName = await Popup.show.input(
512 'Enter a name for this persona:',513 t`Enter a name for this persona:`,
513 '(If empty name is provided, this will unbind the name from this avatar)',514 t`(If empty name is provided, this will unbind the name from this avatar)`,
514 existingPersona || '',515 existingPersona || '',
515 { onClose: (p) => { personaUnbind = p.value === '' && p.result === POPUP_RESULT.AFFIRMATIVE; } });516 { onClose: (p) => { personaUnbind = p.value === '' && p.result === POPUP_RESULT.AFFIRMATIVE; } });
516517
@@ -560,8 +561,8 @@ function selectCurrentPersona() {
560 const lockedPersona = chat_metadata['persona'];561 const lockedPersona = chat_metadata['persona'];
561 if (lockedPersona && lockedPersona !== user_avatar && power_user.persona_show_notifications) {562 if (lockedPersona && lockedPersona !== user_avatar && power_user.persona_show_notifications) {
562 toastr.info(563 toastr.info(
563 `To permanently set "${personaName}" as the selected persona, unlock and relock it using the "Lock" button. Otherwise, the selection resets upon reloading the chat.`,564 t`To permanently set "${personaName}" as the selected persona, unlock and relock it using the "Lock" button. Otherwise, the selection resets upon reloading the chat.`,
564 `This chat is locked to a different persona (${power_user.personas[lockedPersona]}).`,565 t`This chat is locked to a different persona (${power_user.personas[lockedPersona]}).`,
565 { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },566 { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },
566 );567 );
567 }568 }
@@ -626,7 +627,7 @@ async function unlockPersona() {
626 delete chat_metadata['persona'];627 delete chat_metadata['persona'];
627 await saveMetadata();628 await saveMetadata();
628 if (power_user.persona_show_notifications) {629 if (power_user.persona_show_notifications) {
629 toastr.info('User persona is now unlocked for this chat. Click the "Lock" again to revert.', 'Persona unlocked');630 toastr.info(t`User persona is now unlocked for this chat. Click the "Lock" again to revert.`, t`Persona unlocked`);
630 }631 }
631 updateUserLockIcon();632 updateUserLockIcon();
632 }633 }
@@ -640,8 +641,8 @@ async function lockPersona() {
640 console.log(`Creating a new persona ${user_avatar}`);641 console.log(`Creating a new persona ${user_avatar}`);
641 if (power_user.persona_show_notifications) {642 if (power_user.persona_show_notifications) {
642 toastr.info(643 toastr.info(
643 'Creating a new persona for currently selected user name and avatar...',644 t`Creating a new persona for currently selected user name and avatar...`,
644 'Persona not set for this avatar',645 t`Persona not set for this avatar`,
645 { timeOut: 10000, extendedTimeOut: 20000 },646 { timeOut: 10000, extendedTimeOut: 20000 },
646 );647 );
647 }648 }
@@ -659,7 +660,7 @@ async function lockPersona() {
659 saveSettingsDebounced();660 saveSettingsDebounced();
660 console.log(`Locking persona for this chat ${user_avatar}`);661 console.log(`Locking persona for this chat ${user_avatar}`);
661 if (power_user.persona_show_notifications) {662 if (power_user.persona_show_notifications) {
662 toastr.success(`User persona is locked to ${name1} in this chat`);663 toastr.success(t`User persona is locked to ${name1} in this chat`);
663 }664 }
664 updateUserLockIcon();665 updateUserLockIcon();
665}666}
@@ -676,11 +677,11 @@ async function deleteUserAvatar(e) {
676677
677 if (avatarId == user_avatar) {678 if (avatarId == user_avatar) {
678 console.warn(`User tried to delete their current avatar ${avatarId}`);679 console.warn(`User tried to delete their current avatar ${avatarId}`);
679 toastr.warning('You cannot delete the avatar you are currently using', 'Warning');680 toastr.warning(t`You cannot delete the avatar you are currently using`, t`Warning`);
680 return;681 return;
681 }682 }
682683
683 const confirm = await Popup.show.confirm('Are you sure you want to delete this avatar?', 'All information associated with its linked persona will be lost.');684 const confirm = await Popup.show.confirm(t`Are you sure you want to delete this avatar?`, t`All information associated with its linked persona will be lost.`);
684685
685 if (!confirm) {686 if (!confirm) {
686 console.debug('User cancelled deleting avatar');687 console.debug('User cancelled deleting avatar');
@@ -701,12 +702,12 @@ async function deleteUserAvatar(e) {
701 delete power_user.persona_descriptions[avatarId];702 delete power_user.persona_descriptions[avatarId];
702703
703 if (avatarId === power_user.default_persona) {704 if (avatarId === power_user.default_persona) {
704 toastr.warning('The default persona was deleted. You will need to set a new default persona.', 'Default persona deleted');705 toastr.warning(t`The default persona was deleted. You will need to set a new default persona.`, t`Default persona deleted`);
705 power_user.default_persona = null;706 power_user.default_persona = null;
706 }707 }
707708
708 if (avatarId === chat_metadata['persona']) {709 if (avatarId === chat_metadata['persona']) {
709 toastr.warning('The locked persona was deleted. You will need to set a new persona for this chat.', 'Persona deleted');710 toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona deleted`);
710 delete chat_metadata['persona'];711 delete chat_metadata['persona'];
711 await saveMetadata();712 await saveMetadata();
712 }713 }
@@ -807,14 +808,14 @@ async function setDefaultPersona(e) {
807808
808 if (power_user.personas[avatarId] === undefined) {809 if (power_user.personas[avatarId] === undefined) {
809 console.warn(`No persona name found for avatar ${avatarId}`);810 console.warn(`No persona name found for avatar ${avatarId}`);
810 toastr.warning('You must bind a name to this persona before you can set it as the default.', 'Persona name not set');811 toastr.warning(t`You must bind a name to this persona before you can set it as the default.`, t`Persona name not set`);
811 return;812 return;
812 }813 }
813814
814 const personaName = power_user.personas[avatarId];815 const personaName = power_user.personas[avatarId];
815816
816 if (avatarId === currentDefault) {817 if (avatarId === currentDefault) {
817 const confirm = await Popup.show.confirm('Are you sure you want to remove the default persona?', personaName);818 const confirm = await Popup.show.confirm(t`Are you sure you want to remove the default persona?`, personaName);
818819
819 if (!confirm) {820 if (!confirm) {
820 console.debug('User cancelled removing default persona');821 console.debug('User cancelled removing default persona');
@@ -823,11 +824,11 @@ async function setDefaultPersona(e) {
823824
824 console.log(`Removing default persona ${avatarId}`);825 console.log(`Removing default persona ${avatarId}`);
825 if (power_user.persona_show_notifications) {826 if (power_user.persona_show_notifications) {
826 toastr.info('This persona will no longer be used by default when you open a new chat.', 'Default persona removed');827 toastr.info(t`This persona will no longer be used by default when you open a new chat.`, t`Default persona removed`);
827 }828 }
828 delete power_user.default_persona;829 delete power_user.default_persona;
829 } else {830 } else {
830 const confirm = await Popup.show.confirm(`Are you sure you want to set "${personaName}" as the default persona?`, 'This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.');831 const confirm = await Popup.show.confirm(t`Are you sure you want to set "${personaName}" as the default persona?`, t`This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`);
831832
832 if (!confirm) {833 if (!confirm) {
833 console.debug('User cancelled setting default persona');834 console.debug('User cancelled setting default persona');
@@ -836,7 +837,7 @@ async function setDefaultPersona(e) {
836837
837 power_user.default_persona = avatarId;838 power_user.default_persona = avatarId;
838 if (power_user.persona_show_notifications) {839 if (power_user.persona_show_notifications) {
839 toastr.success('This persona will be used by default when you open a new chat.', `Default persona set to ${personaName}`);840 toastr.success(t`This persona will be used by default when you open a new chat.`, t`Default persona set to ${personaName}`);
840 }841 }
841 }842 }
842843
@@ -918,13 +919,13 @@ async function onPersonasRestoreInput(e) {
918 const data = await parseJsonFile(file);919 const data = await parseJsonFile(file);
919920
920 if (!data) {921 if (!data) {
921 toastr.warning('Invalid file selected', 'Persona Management');922 toastr.warning(t`Invalid file selected`, t`Persona Management`);
922 console.debug('Invalid file selected');923 console.debug('Invalid file selected');
923 return;924 return;
924 }925 }
925926
926 if (!data.personas || !data.persona_descriptions || typeof data.personas !== 'object' || typeof data.persona_descriptions !== 'object') {927 if (!data.personas || !data.persona_descriptions || typeof data.personas !== 'object' || typeof data.persona_descriptions !== 'object') {
927 toastr.warning('Invalid file format', 'Persona Management');928 toastr.warning(t`Invalid file format`, t`Persona Management`);
928 console.debug('Invalid file selected');929 console.debug('Invalid file selected');
929 return;930 return;
930 }931 }
@@ -972,10 +973,10 @@ async function onPersonasRestoreInput(e) {
972 }973 }
973974
974 if (warnings.length) {975 if (warnings.length) {
975 toastr.success('Personas restored with warnings. Check console for details.');976 toastr.success(t`Personas restored with warnings. Check console for details.`);
976 console.warn(`PERSONA RESTORE REPORT\n====================\n${warnings.join('\n')}`);977 console.warn(`PERSONA RESTORE REPORT\n====================\n${warnings.join('\n')}`);
977 } else {978 } else {
978 toastr.success('Personas restored successfully.');979 toastr.success(t`Personas restored successfully.`);
979 }980 }
980981
981 await getUserAvatars();982 await getUserAvatars();
@@ -985,7 +986,7 @@ async function onPersonasRestoreInput(e) {
985}986}
986987
987async function syncUserNameToPersona() {988async function syncUserNameToPersona() {
988 const confirmation = await Popup.show.confirm('Are you sure?', `All user-sent messages in this chat will be attributed to ${name1}.`);989 const confirmation = await Popup.show.confirm(t`Are you sure?`, t`All user-sent messages in this chat will be attributed to ${name1}.`);
989990
990 if (!confirmation) {991 if (!confirmation) {
991 return;992 return;
@@ -1021,7 +1022,7 @@ async function duplicatePersona(avatarId) {
1021 return;1022 return;
1022 }1023 }
10231024
1024 const confirm = await Popup.show.confirm('Are you sure you want to duplicate this persona?', personaName);1025 const confirm = await Popup.show.confirm(t`Are you sure you want to duplicate this persona?`, personaName);
10251026
1026 if (!confirm) {1027 if (!confirm) {
1027 console.debug('User cancelled duplicating persona');1028 console.debug('User cancelled duplicating persona');
public/scripts/tags.js+3 -36
@@ -728,9 +728,9 @@ async function importTags(character, { importSetting = null } = {}) {
728 const added = addTagsToEntity(tagsToImport, character.avatar);728 const added = addTagsToEntity(tagsToImport, character.avatar);
729729
730 if (added) {730 if (added) {
731 toastr.success(`Imported tags:<br />${tagsToImport.map(x => x.name).join(', ')}`, 'Importing Tags', { escapeHtml: false });731 toastr.success(t`Imported tags:` + `<br />${tagsToImport.map(x => x.name).join(', ')}`, t`Importing Tags`, { escapeHtml: false });
732 } else {732 } else {
733 toastr.error(`Couldn't import tags:<br />${tagsToImport.map(x => x.name).join(', ')}`, 'Importing Tags', { escapeHtml: false });733 toastr.error(t`Couldn't import tags:` + `<br />${tagsToImport.map(x => x.name).join(', ')}`, t`Importing Tags`, { escapeHtml: false });
734 }734 }
735735
736 return added;736 return added;
@@ -1299,40 +1299,7 @@ export function createTagInput(inputSelector, listSelector, tagListOptions = {})
1299async function onViewTagsListClick() {1299async function onViewTagsListClick() {
1300 const html = $(document.createElement('div'));1300 const html = $(document.createElement('div'));
1301 html.attr('id', 'tag_view_list');1301 html.attr('id', 'tag_view_list');
1302 html.append(`1302 html.append(await renderTemplateAsync('tagManagement', {bogus_folders: power_user.bogus_folders, auto_sort_tags: power_user.auto_sort_tags}));
1303 <div class="title_restorable alignItemsBaseline">
1304 <h3>Tag Management</h3>
1305 <div class="flex-container alignItemsBaseline">
1306 <div class="menu_button menu_button_icon tag_view_backup" title="Save your tags to a file">
1307 <i class="fa-solid fa-file-export"></i>
1308 <span data-i18n="Backup">Backup</span>
1309 </div>
1310 <div class="menu_button menu_button_icon tag_view_restore" title="Restore tags from a file">
1311 <i class="fa-solid fa-file-import"></i>
1312 <span data-i18n="Restore">Restore</span>
1313 </div>
1314 <div class="menu_button menu_button_icon tag_view_create" title="Create a new tag">
1315 <i class="fa-solid fa-plus"></i>
1316 <span data-i18n="Create">Create</span>
1317 </div>
1318 <input type="file" id="tag_view_restore_input" hidden accept=".json">
1319 </div>
1320 </div>
1321 <div class="justifyLeft m-b-1">
1322 <small>
1323 Drag handle to reorder. Click name to rename. Click color to change display.<br>
1324 ${(power_user.bogus_folders ? 'Click on the folder icon to use this tag as a folder.<br>' : '')}
1325 <label class="checkbox flex-container alignitemscenter flexNoGap m-t-1" for="auto_sort_tags">
1326 <input type="checkbox" id="auto_sort_tags" name="auto_sort_tags" ${power_user.auto_sort_tags ? ' checked' : ''} />
1327 <span data-i18n="Use alphabetical sorting">
1328 Use alphabetical sorting
1329 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]If enabled, tags will automatically be sorted alphabetically on creation or rename.\nIf disabled, new tags will be appended at the end.\n\nIf a tag is manually reordered by dragging, automatic sorting will be disabled."
1330 title="If enabled, tags will automatically be sorted alphabetically on creation or rename.\nIf disabled, new tags will be appended at the end.\n\nIf a tag is manually reordered by dragging, automatic sorting will be disabled.">
1331 </div>
1332 </span>
1333 </label>
1334 </small>
1335 </div>`);
13361303
1337 const tagContainer = $('<div class="tag_view_list_tags ui-sortable"></div>');1304 const tagContainer = $('<div class="tag_view_list_tags ui-sortable"></div>');
1338 html.append(tagContainer);1305 html.append(tagContainer);
public/scripts/templates/forbidMedia.html+2 -2
@@ -9,8 +9,8 @@
9 <input type="radio" id="forbid_media_override_global" name="forbid_media_override" />9 <input type="radio" id="forbid_media_override_global" name="forbid_media_override" />
10 <span>10 <span>
11 <span data-i18n="Use global setting">Use global setting</span>11 <span data-i18n="Use global setting">Use global setting</span>
12 <b class="forbid_media_global_state_forbidden">(forbidden)</b>12 <b data-i18n="forbid_media_global_state_forbidden" class="forbid_media_global_state_forbidden">(forbidden)</b>
13 <b class="forbid_media_global_state_allowed">(allowed)</b>13 <b data-i18n="forbid_media_global_state_allowed" class="forbid_media_global_state_allowed">(allowed)</b>
14 </span>14 </span>
15 </label>15 </label>
16 <label class="checkbox_label" for="forbid_media_override_forbidden">16 <label class="checkbox_label" for="forbid_media_override_forbidden">
public/scripts/templates/tagManagement.html+33 -0
@@ -0,0 +1,33 @@
1<div class="title_restorable alignItemsBaseline">
2 <h3 data-i18n="Tag Management">Tag Management</h3>
3 <div class="flex-container alignItemsBaseline">
4 <div class="menu_button menu_button_icon tag_view_backup" data-i18n="[title]Save your tags to a file" title="Save your tags to a file">
5 <i class="fa-solid fa-file-export"></i>
6 <span data-i18n="Backup">Backup</span>
7 </div>
8 <div class="menu_button menu_button_icon tag_view_restore" data-i18n="[title]Restore tags from a file" title="Restore tags from a file">
9 <i class="fa-solid fa-file-import"></i>
10 <span data-i18n="Restore">Restore</span>
11 </div>
12 <div class="menu_button menu_button_icon tag_view_create" data-i18n="[title]Create a new tag" title="Create a new tag">
13 <i class="fa-solid fa-plus"></i>
14 <span data-i18n="Create">Create</span>
15 </div>
16 <input type="file" id="tag_view_restore_input" hidden accept=".json">
17 </div>
18 </div>
19 <div class="justifyLeft m-b-1">
20 <small>
21 <span data-i18n="Drag handle to reorder. Click name to rename. Click color to change display.">Drag handle to reorder. Click name to rename. Click color to change display.</span><br>
22 {{#if bogus_folders}}<span data-i18n="Click on the folder icon to use this tag as a folder.">Click on the folder icon to use this tag as a folder.</span><br>{{/if}}
23 <label class="checkbox flex-container alignitemscenter flexNoGap m-t-1" for="auto_sort_tags">
24 <input type="checkbox" id="auto_sort_tags" name="auto_sort_tags" {{#if auto_sort_tags}} checked{{/if}} />
25 <span data-i18n="Use alphabetical sorting">
26 Use alphabetical sorting
27 </span>
28 <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]tags_sorting_desc"
29 title="If enabled, tags will automatically be sorted alphabetically on creation or rename.\nIf disabled, new tags will be appended at the end.\n\nIf a tag is manually reordered by dragging, automatic sorting will be disabled.">
30 </div>
31 </label>
32 </small>
33 </div>
\ No newline at end of file33 \ No newline at end of file
public/scripts/templates/welcome.html+1 -1
@@ -33,7 +33,7 @@
33 <i class="fa-solid fa-image-portrait"></i>33 <i class="fa-solid fa-image-portrait"></i>
34 <span data-i18n="Sample characters">Sample characters</span>34 <span data-i18n="Sample characters">Sample characters</span>
35 </button>35 </button>
36 <span data-i18n="or_welcome">or</span>36 <span data-i18n="or;or_welcome">or</span>
37 <button class="external_import_button menu_button menu_button_icon inline-flex">37 <button class="external_import_button menu_button menu_button_icon inline-flex">
38 <i class="fa-solid fa-cloud-arrow-down"></i>38 <i class="fa-solid fa-cloud-arrow-down"></i>
39 <span data-i18n="Import Characters">Import characters</span>39 <span data-i18n="Import Characters">Import characters</span>