-
+
@@ -5734,7 +5734,7 @@
☰
-
+
@@ -5993,7 +5993,7 @@
-
+
diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json
index 5c506a88b..3ffcffea3 100644
--- a/public/locales/ru-ru.json
+++ b/public/locales/ru-ru.json
@@ -294,7 +294,7 @@
"Avoid sending sensitive information to the Horde.": "Избегайте отправки личной информации Horde",
"Review the Privacy statement": "Ознакомиться с заявлением о конфиденциальности",
"Trusted workers only": "Только доверенные рабочие машины",
- "For privacy reasons, your API key will be hidden after you reload the page.": "По причинам безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
+ "For privacy reasons, your API key will be hidden after you reload the page.": "Из соображений безопасности ваш API-ключ будет скрыт после перезагрузки страницы.",
"-- Horde models not loaded --": "--Модель Horde не загружена--",
"Example: http://127.0.0.1:5000/api ": "Пример: http://127.0.0.1:5000/api",
"No connection...": "Нет соединения...",
@@ -331,7 +331,6 @@
"UID ↘": "UID ↘",
"Trigger% ↗": "Триггер% ↗",
"Trigger% ↘": "Триггер% ↘",
- "Order:": "Порядок:",
"Depth:": "Глубина:",
"Character Lore First": "Сначала лор персонажа",
"Global Lore First": "Сначала глобальный лор",
@@ -347,7 +346,7 @@
"After Char Defs": "↓Перс.",
"Before AN": "↑АЗ",
"After AN": "↓АЗ",
- "Order": "Порядок:",
+ "Order": "Очерёдность:",
"Update a theme file": "Обновить файл темы",
"Save as a new theme": "Сохранить как новую тему",
"Minimum number of blacklisted words detected to trigger an auto-swipe": "Минимальное количество обнаруженных запрещённых слов, при котором срабатывает авто-свайп.",
@@ -497,7 +496,7 @@
"What this keyword should mean to the AI, sent verbatim": "Что это ключевое слово должно означать для ИИ, отправляется дословно",
"Filter to Character(s)": "Фильтр по персонажу(ам)",
"Character Exclusion": "Исключить персонажей",
- "Inclusion Group": "Включить персонажей",
+ "Inclusion Group": "Группа записей",
"Only one entry with the same label will be activated": "Будет активна только одна запись с одинаковой меткой",
"-- Characters not found --": "-- Персонажей не найдено --",
"(This will be the first message from the character that starts every chat)": "(Это будет первое сообщение от персонажа, когда вы начинаете новый чат)",
@@ -541,7 +540,7 @@
"NOT ANY": "НЕ ЛЮБОЙ",
"Optional Filter": "Дополнительный фильтр",
"New Entry": "Новая запись",
- "Fill empty Memo/Titles with Keywords": "Заполните пустые Заметки/Названия ключевыми словами",
+ "Fill empty Memo/Titles with Keywords": "Заполнить пустые названия ключевыми словами",
"AI Response Formatting": "Формат ответа ИИ",
"Change Background Image": "Изменить фон",
"Extensions": "Расширения",
@@ -628,7 +627,7 @@
"UI Theme": "Тема UI",
"This message is invisible for the AI": "Это сообщение невидимо для ИИ",
"Sampler Priority": "Приоритет сэмплеров",
- "Ooba only. Determines the order of samplers.": "Только Ooba. Определяет порядок сэмплеров.",
+ "Ooba only. Determines the order of samplers.": "Только oobabooga. Определяет порядок сэмплеров.",
"Load default order": "Загрузить стандартный порядок",
"Max Tokens Second": "Макс. кол-во токенов в секунду",
"CFG": "CFG",
@@ -1342,7 +1341,7 @@
"Chat Lorebook": "Chat Lorebook for",
"chat_world_template_txt": "A selected World Info will be bound to this chat. When generating an AI reply,\n it will be combined with the entries from global and character lorebooks.",
"Use tag as folder": "Tag as folder",
- "WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized❌ Disabled": "Статус записи:\n 🔵 Постоянная\n 🟢 Обычная\n 🔗 Векторизованная\n ❌ Отключена",
+ "WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized": "Статус записи:\n 🔵 Постоянная\n 🟢 Обычная\n 🔗 Векторизованная",
"WI_Entry_Status_Constant": "Постоянная",
"WI_Entry_Status_Normal": "Обычная",
"WI_Entry_Status_Vectorized": "Векторизованная",
@@ -1366,13 +1365,7 @@
"Can be used to automatically activate Quick Replies": "Can be used to automatically activate Quick Replies",
"Automation ID": "Automation ID",
"( None )": "( None )",
- "Delay until recursion (this entry can only be activated on recursive checking)": "Delay until recursion (this entry can only be activated on recursive checking)",
- "Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.\rSupports multiple comma-separated groups.\r\rDocumentation: World Info - Inclusion Group",
- "Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Prioritize this entry: When checked, this entry is prioritized out of all selections.\rIf multiple are prioritized, the one with the highest 'Order' is chosen.\r",
- "A relative likelihood of entry activation within the group": "A relative likelihood of entry activation within the group",
- "Group Weight": "Group Weight",
"Add Memo": "Add Memo",
- "close": "close",
"reset": "reset",
"save": "save",
"Open checkpoint chat": "Открыть чат из чекпоинта",
@@ -2035,5 +2028,121 @@
"tags_sorting_desc": "Автоматически сортировать теги по алфавиту после создания или переименования одного из них.\nПри выключении новые теги будут просто добавляться в конец.\n\nЕсли вручную перетащить один из тегов на другое место, автосортировка отключится.",
"Imported tags:": "Импортируемые теги:",
"Importing Tags": "Импорт тегов",
- "Couldn't import tags:": "Не удалось импортировать теги:"
+ "Couldn't import tags:": "Не удалось импортировать теги:",
+ "Allow fallback models": "Разрешить fallback-модели",
+ "Allow fallback providers": "Разрешить fallback-провайдеров",
+ "To use instruct formatting, switch to OpenRouter under Text Completion API.": "Переключитесь на OpenRouter в Text Completion API, чтобы использовать форматирование Instruct-режима.",
+ "Select providers. No selection = all providers.": "Выберите провайдера. Нет выбранного = выбраны все.",
+ "Model Providers": "Провайдеры моделей",
+ "Select a model": "Выберите модель",
+ "Search models...": "Искать по моделям...",
+ "[Currently loaded]": "[Загруженная сейчас]",
+ "Search providers...": "Искать по провайдерам...",
+ "Automatically chooses an alternative provider if chosen providers can't serve your request.": "Автоматически переключаться на другого провайдера, если текущий не может обслужить запрос.",
+ "Example: 127.0.0.1:8000": "Пример: 127.0.0.1:8000",
+ "Edit a connection profile": "Редактировать профиль соединения",
+ "System Prompt Name": "Название системного промпта",
+ "Use System Prompt": "Использовать системный промпт",
+ "Hint:": "Подсказка:",
+ "Click on the setting name to omit it from the profile.": "Нажмите на название настройки, чтобы исключить её из профиля",
+ "Included settings:": "Сохранённые параметры:",
+ "Server URL": "Адрес сервера",
+ "NanoGPT API Key": "Ключ от API NanoGPT",
+ "NanoGPT Model": "Модель NanoGPT",
+ "Use extension settings": "Использовать настройки из расширения",
+ "DeepSeek API Key": "Ключ от API DeepSeek",
+ "DeepSeek Model": "Модель DeepSeek",
+ "prompt_post_processing_merge": "Объединять идущие подряд сообщения с одной ролью",
+ "prompt_post_processing_semi": "Semi-strict (чередовать роли)",
+ "prompt_post_processing_strict": "Strict (чередовать роли, сначала пользователь)",
+ "Select Horde models": "Выбрать модель из Horde",
+ "Model ID (optional)": "Идентификатор модели (необязательно)",
+ "Derive context size from backend": "Использовать бэкенд для определения размера контекста",
+ "Rename current preset": "Переименовать пресет",
+ "No Worlds active. Click here to select.": "Нет активных миров. Нажмите, чтобы выбрать.",
+ "Title/Memo": "Название",
+ "Strategy": "Статус",
+ "Position": "Позиция",
+ "Trigger %": "% срабатывания",
+ "Use global": "Глоб. настройка",
+ "Whole Words": "Целые слова",
+ "Non-recursable (will not be activated by another)": "Не рекурсивная (не активируется другими)",
+ "Delay until recursion (can only be activated on recursive checking)": "Рекурсивная (активируется только рекурсией)",
+ "Toggle entry's active state.": "Вкл/выкл запись.",
+ "Prioritize": "Важная",
+ "Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.": "Важная запись получает приоритет среди всех выбранных. Если важных записей несколько, выбирается та, у которой выше \"Очерёдность\".",
+ "Group Weight": "Вес в группе",
+ "A relative likelihood of entry activation within the group": "Относительная вероятность активации записи в рамках группы",
+ "Sticky": "Липучка",
+ "Sticky entries will stay active for N messages after being triggered.": "Запись-\"липучка\" останется активной в течение N сообщений после срабатывания.",
+ "Cooldown": "Кулдаун",
+ "Entries with a cooldown can't be activated N messages after being triggered.": "Запись с заданным кулдауном не активируется следующие N сообщений после срабатывания.",
+ "Delay": "Задержка",
+ "Entries with a delay can't be activated until there are N messages present in the chat.": "Запись с заданной задержкой может активироваться только после того, как в чате наберётся N сообщений.",
+ "No-sticky": "Нет",
+ "No cooldown": "Нет",
+ "No delay": "Нет",
+ "Filter to Characters or Tags": "Фильтровать по персонажам или тегам",
+ "Switch to plaintext mode": "Вкл/выкл режим чистого текста",
+ "Exclude": "Режим исключения",
+ "Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry": "Инвертировать логику: для выбранных в фильтре персонажей/тегов данная запись активна НЕ БУДЕТ",
+ "Apply current sorting as Order": "Настроить Очерёдность в соответствии с текущей сортировкой",
+ "Create a new World Info": "Создать новый мир",
+ "Enter a name for the new file:": "Название нового файла:",
+ "Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group": "Если сразу несколько записей из одной группы окажутся активированными, по факту сработает только одна. Одна запись может входить в несколько групп, отделяются запятыми. Раздел в документации: World Info - Inclusion Group",
+ "Valid World Info file name is required": "Требуется корректное имя для файла мира",
+ "World Info file has an invalid format": "Файл мира имеет неизвестный формат",
+ "World Info file has no entries": "В файле нет ни одной записи",
+ "Character not found.": "Персонаж не найден",
+ "Open a chat to get a name of the chat-bound lorebook": "Чтобы получить название привязанного к чату лорбука, требуется открыть чат.",
+ "File is not valid: ${0}": "Файл повреждён или имеет неизвестный формат: ${0}",
+ "The world with ${0} is invalid or corrupted.": "Мир ${0} повреждён или имеет неизвестный формат.",
+ "Deactivated all worlds": "Все миры отключены",
+ "No world found named: ${0}": "Не найдено мира с название ${0}",
+ "Activated world: ${0}": "Мир ${0} включён",
+ "Deactivated world: ${0}": "Мир ${0} отключён",
+ "World was not active: ${0}": "Мир ${0} не включён",
+ "The world '${0}' has been imported and linked to the character successfully.": "Мир ${0} успешно импортирован и привязан к персонажу.",
+ "World/Lorebook imported": "Мир/лорбук импортирован",
+ "Are you sure you want to import '${0}'?": "Вы точно хотите импортировать '${0}'?",
+ "It will overwrite the World/Lorebook with the same name.": "Существующий мир с таким же названием будет перезаписан.",
+ "Automatically 'continue' a response if the model stopped before reaching a certain amount of tokens.": "Автоматически \"продолжать\" ответ, если он оказался короче, чем целевая длина (в токенах).",
+ "None (not injected)": "Никуда",
+ "ext_sum_injection_position_none": "Пересказ не будет вставляться в промпт. Однако его по-прежнему можно будет вставить с помощью макроса {{summary}}",
+ "ext_sum_include_wi_scan": "Учитывать при сканировании лорбуком",
+ "ext_sum_include_wi_scan_desc": "Учитывать актуальный пересказ при сканировании промпта лорбуком.",
+ "ext_sum_force_tip": "Отправить запрос на создание пересказа прямо сейчас.",
+ "ext_sum_restore_tip": "Откатиться к предыдущему пересказу; используйте несколько раз, чтобы очистить историю пересказов для чата",
+ "Built-in Extensions:": "Комплектные расширения:",
+ "Installed Extensions:": "Установленные расширения:",
+ "Loading third-party extensions... Please wait...": "Загрузка сторонних расширений... Пожалуйста, подождите...",
+ "The page will be reloaded shortly...": "Страница будет перезагружена...",
+ "Extensions state changed": "Изменено состояние расширения",
+ "Error loading extensions. See browser console for details.": "Не удалось загрузить расширения. Подробности см. в консоли браузера.",
+ "You don't have permission to update global extensions.": "Отсутствуют права на обновление глобальных расширений.",
+ "Extension update failed": "Не удалось обновить расширение",
+ "Extension ${0} updated to ${1}": "Расширение ${0} обновлено до ${1}",
+ "Reload the page to apply updates": "Чтобы изменения вступили в силу, обновите страницу",
+ "You don't have permission to delete global extensions.": "Отсутствуют права на удаление глобальных расширений.",
+ "Are you sure you want to delete ${0}?": "Вы точно хотите удалить ${0}?",
+ "You don't have permission to move extensions.": "Отсутствуют права на перемещение расширений.",
+ "Are you sure you want to move ${0} to your local extensions? This will make it available only for you.": "Вы точно хотите сделать ${0} локальным расширением? После этого оно будет доступно только вам.",
+ "Are you sure you want to move ${0} to the global extensions? This will make it available for all users.": "Вы точно хотите сделать ${0} глобальным расширением? После этого оно будет доступно всем пользователям.",
+ "Extension ${0} moved.": "Расширение ${0} перемещено.",
+ "Extension ${0} deleted": "Расширение ${0} удалено.",
+ "Please wait...": "Пожалуйста, подождите...",
+ "Installing extension": "Идёт установка расширения",
+ "Extension installation failed": "Не удалось установить расширение",
+ "Extension '${0}' by ${1} (version ${2}) has been installed successfully!": "Расширение ${0} от автора ${1} (версия ${2}) успешно установлено!",
+ "Extension installation successful": "Расширение установлено",
+ "Extension updates available": "Доступных обновлений расширений",
+ "Auto-updating extensions. This may take several minutes.": "Запущено автоматическое обновление расширений. Это может занять несколько минут.",
+ "Install just for me": "Установить только мне",
+ "Install": "Установить",
+ "Install for all users": "Установить для всех пользователей",
+ "Modules provided by your Extras API:": "Модули из Extras API:",
+ "Not connected to the API!": "Нет соединения с API!",
+ "ext_type_system": "Это комплектное расширение. Его нельзя удалить, а обновляется оно вместе со всей системой.",
+ "Update all": "Обновить все",
+ "Close": "Закрыть"
}
diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js
index 0c3dbfa2e..e8da94a6f 100644
--- a/public/scripts/extensions.js
+++ b/public/scripts/extensions.js
@@ -682,9 +682,9 @@ function getExtensionData(extension) {
* @return {string} - The HTML string for the module information.
*/
function getModuleInformation() {
- let moduleInfo = modules.length ? `
${DOMPurify.sanitize(modules.join(', '))}
` : '
Not connected to the API!
';
+ let moduleInfo = modules.length ? `
${DOMPurify.sanitize(modules.join(', '))}
` : '
' + t`Not connected to the API!` + '
';
return `
-
Modules provided by your Extras API:
+
` + t`Modules provided by your Extras API:` + `
${moduleInfo}
`;
}
@@ -703,11 +703,11 @@ async function showExtensionsDetails() {
initialScrollTop = oldPopup.content.scrollTop;
await oldPopup.completeCancelled();
}
- const htmlDefault = $('
Built-in Extensions:
');
- const htmlExternal = $('
Installed Extensions:
');
+ const htmlDefault = $('
' + t`Built-in Extensions:` + '
');
+ const htmlExternal = $('
' + t`Installed Extensions:` + '
');
const htmlLoading = $(`
- Loading third-party extensions... Please wait...
+ ` + t`Loading third-party extensions... Please wait...` + `
`);
htmlExternal.append(htmlLoading);
@@ -728,7 +728,7 @@ async function showExtensionsDetails() {
/** @type {import('./popup.js').CustomPopupButton} */
const updateAllButton = {
- text: 'Update all',
+ text: t`Update all`,
appendAtEnd: true,
action: async () => {
requiresReload = true;
@@ -740,7 +740,7 @@ async function showExtensionsDetails() {
let waitingForSave = false;
const popup = new Popup(html, POPUP_TYPE.TEXT, '', {
- okButton: 'Close',
+ okButton: t`Close`,
wide: true,
large: true,
customButtons: [updateAllButton],
@@ -833,7 +833,7 @@ async function updateExtension(extensionName, quiet) {
toastr.success('Extension is already up to date');
}
} else {
- toastr.success(`Extension ${extensionName} updated to ${data.shortCommitHash}`, 'Reload the page to apply updates');
+ toastr.success(t`Extension ${extensionName} updated to ${data.shortCommitHash}`, t`Reload the page to apply updates`);
}
} catch (error) {
console.error('Error:', error);
@@ -1001,7 +1001,7 @@ export async function installExtension(url, global) {
}
const response = await request.json();
- toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been installed successfully!`, 'Extension installation successful');
+ toastr.success(t`Extension '${response.display_name}' by ${response.author} (version ${response.version}) has been installed successfully!`, t`Extension installation successful`);
console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);
await loadExtensionSettings({}, false, false);
await eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
@@ -1175,7 +1175,7 @@ async function checkForExtensionUpdates(force) {
await Promise.allSettled(promises);
if (updatesAvailable.length > 0) {
- toastr.info(`${updatesAvailable.map(x => `• ${x}`).join('\n')}`, 'Extension updates available');
+ toastr.info(`${updatesAvailable.map(x => `• ${x}`).join('\n')}`, t`Extension updates available`);
}
}
@@ -1189,7 +1189,7 @@ async function autoUpdateExtensions(forceAll) {
return;
}
- const banner = toastr.info('Auto-updating extensions. This may take several minutes.', 'Please wait...', { timeOut: 10000, extendedTimeOut: 10000 });
+ const banner = toastr.info(t`Auto-updating extensions. This may take several minutes.`, t`Please wait...`, { timeOut: 10000, extendedTimeOut: 10000 });
const isCurrentUserAdmin = isAdmin();
const promises = [];
for (const [id, manifest] of Object.entries(manifests)) {
diff --git a/public/scripts/extensions/connection-manager/edit.html b/public/scripts/extensions/connection-manager/edit.html
index 845fbc97f..b8064ad8b 100644
--- a/public/scripts/extensions/connection-manager/edit.html
+++ b/public/scripts/extensions/connection-manager/edit.html
@@ -1,10 +1,10 @@
-
Included settings:
+
Included settings:
{{#each settings}}
{{/each}}
diff --git a/public/scripts/extensions/connection-manager/profile.html b/public/scripts/extensions/connection-manager/profile.html
index 3611b85dd..b7d79c2c0 100644
--- a/public/scripts/extensions/connection-manager/profile.html
+++ b/public/scripts/extensions/connection-manager/profile.html
@@ -12,8 +12,8 @@
- Hint:
- Click on the setting name to omit it from the profile.
+ Hint:
+ Click on the setting name to omit it from the profile.
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 485cc639c..4ec0c7749 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -5491,8 +5491,8 @@ export function initOpenAI() {
if (!isMobile()) {
$('#model_openrouter_select').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getOpenRouterModelTemplate,
diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js
index 851c3940d..8f4366333 100644
--- a/public/scripts/textgen-models.js
+++ b/public/scripts/textgen-models.js
@@ -5,6 +5,7 @@ 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 } from './i18n.js';
let mancerModels = [];
let togetherModels = [];
@@ -936,71 +937,71 @@ export function initTextGenModels() {
if (!isMobile()) {
$('#mancer_model').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getMancerModelTemplate,
});
$('#model_togetherai_select').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getTogetherModelTemplate,
});
$('#ollama_model').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
});
$('#tabby_model').select2({
- placeholder: '[Currently loaded]',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`[Currently loaded]`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
allowClear: true,
});
$('#model_infermaticai_select').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getInfermaticAIModelTemplate,
});
$('#model_dreamgen_select').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getDreamGenModelTemplate,
});
$('#openrouter_model').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getOpenRouterModelTemplate,
});
$('#vllm_model').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getVllmModelTemplate,
});
$('#aphrodite_model').select2({
- placeholder: 'Select a model',
- searchInputPlaceholder: 'Search models...',
+ placeholder: t`Select a model`,
+ searchInputPlaceholder: t`Search models...`,
searchInputCssClass: 'text_pole',
width: '100%',
templateResult: getAphroditeModelTemplate,
});
providersSelect.select2({
sorter: data => data.sort((a, b) => a.text.localeCompare(b.text)),
- placeholder: 'Select providers. No selection = all providers.',
- searchInputPlaceholder: 'Search providers...',
+ placeholder: t`Select providers. No selection = all providers.`,
+ searchInputPlaceholder: t`Search providers...`,
searchInputCssClass: 'text_pole',
width: '100%',
closeOnSelect: false,
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index 9f18f46c5..ee4db166a 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -20,6 +20,7 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
import { StructuredCloneMap } from './util/StructuredCloneMap.js';
import { renderTemplateAsync } from './templates.js';
+import { t } from './i18n.js';
export const world_info_insertion_strategy = {
evenly: 0,
@@ -909,21 +910,21 @@ function registerWorldInfoSlashCommands() {
async function getEntriesFromFile(file) {
if (!file || !world_names.includes(file)) {
- toastr.warning('Valid World Info file name is required');
+ toastr.warning(t`Valid World Info file name is required`);
return '';
}
const data = await loadWorldInfo(file);
if (!data || !('entries' in data)) {
- toastr.warning('World Info file has an invalid format');
+ toastr.warning(t`World Info file has an invalid format`);
return '';
}
const entries = Object.values(data.entries);
if (!entries || entries.length === 0) {
- toastr.warning('World Info file has no entries');
+ toastr.warning(t`World Info file has no entries`);
return '';
}
@@ -951,7 +952,7 @@ function registerWorldInfoSlashCommands() {
name = String(name ?? '') || context.characters[context.characterId]?.avatar || null;
const character = findChar({ name });
if (!character) {
- toastr.error('Character not found.');
+ toastr.error(t`Character not found.`);
return '';
}
const books = [];
@@ -977,7 +978,7 @@ function registerWorldInfoSlashCommands() {
const chatId = getCurrentChatId();
if (!chatId) {
- toastr.warning('Open a chat to get a name of the chat-bound lorebook');
+ toastr.warning(t`Open a chat to get a name of the chat-bound lorebook`);
return '';
}
@@ -4773,7 +4774,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
const bookName = characters[chid]?.data?.character_book?.name || `${characters[chid]?.name}'s Lorebook`;
if (!skipPopup) {
- const confirmation = await Popup.show.confirm(`Are you sure you want to import "${bookName}"?`, world_names.includes(bookName) ? 'It will overwrite the World/Lorebook with the same name.' : '');
+ const confirmation = await Popup.show.confirm(t`Are you sure you want to import '${bookName}'?`, world_names.includes(bookName) ? t`It will overwrite the World/Lorebook with the same name.` : '');
if (!confirmation) {
return;
}
@@ -4785,7 +4786,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
await updateWorldInfoList();
$('#character_world').val(bookName).trigger('change');
- toastr.success(`The world "${bookName}" has been imported and linked to the character successfully.`, 'World/Lorebook imported');
+ toastr.success(t`The world '${bookName}' has been imported and linked to the character successfully.`, t`World/Lorebook imported`);
const newIndex = world_names.indexOf(bookName);
if (newIndex >= 0) {
@@ -4813,9 +4814,9 @@ export function onWorldInfoChange(args, text) {
if (selected_world_info.includes(name)) {
selected_world_info.splice(selected_world_info.indexOf(name), 1);
wiElement.prop('selected', false);
- if (!silent) toastr.success(`Deactivated world: ${name}`);
+ if (!silent) toastr.success(t`Deactivated world: ${name}`);
} else {
- if (!silent) toastr.error(`World was not active: ${name}`);
+ if (!silent) toastr.error(t`World was not active: ${name}`);
}
break;
}
@@ -4823,11 +4824,11 @@ export function onWorldInfoChange(args, text) {
if (selected_world_info.includes(name)) {
selected_world_info.splice(selected_world_info.indexOf(name), 1);
wiElement.prop('selected', false);
- if (!silent) toastr.success(`Deactivated world: ${name}`);
+ if (!silent) toastr.success(t`Deactivated world: ${name}`);
} else {
selected_world_info.push(name);
wiElement.prop('selected', true);
- if (!silent) toastr.success(`Activated world: ${name}`);
+ if (!silent) toastr.success(t`Activated world: ${name}`);
}
break;
}
@@ -4835,16 +4836,16 @@ export function onWorldInfoChange(args, text) {
default: {
selected_world_info.push(name);
wiElement.prop('selected', true);
- if (!silent) toastr.success(`Activated world: ${name}`);
+ if (!silent) toastr.success(t`Activated world: ${name}`);
}
}
} else {
- if (!silent) toastr.error(`No world found named: ${worldName}`);
+ if (!silent) toastr.error(t`No world found named: ${worldName}`);
}
});
$('#world_info').trigger('change');
} else { // if no args, unset all worlds
- if (!silent) toastr.success('Deactivated all worlds');
+ if (!silent) toastr.success(t`Deactivated all worlds`);
selected_world_info = [];
$('#world_info').val(null).trigger('change');
}
@@ -4860,7 +4861,7 @@ export function onWorldInfoChange(args, text) {
} else {
const wiElement = getWIElement(existingWorldName);
wiElement.prop('selected', false);
- toastr.error(`The world with ${existingWorldName} is invalid or corrupted.`);
+ toastr.error(t`The world with ${existingWorldName} is invalid or corrupted.`);
}
});
}
@@ -4892,7 +4893,7 @@ export async function importWorldInfo(file) {
}
if (jsonData === undefined || jsonData === null) {
- toastr.error(`File is not valid: ${file.name}`);
+ toastr.error(t`File is not valid: ${file.name}`);
return;
}
@@ -5038,7 +5039,7 @@ jQuery(() => {
$('#world_create_button').on('click', async () => {
const tempName = getFreeWorldName();
- const finalName = await Popup.show.input('Create a new World Info', 'Enter a name for the new file:', tempName);
+ const finalName = await Popup.show.input(t`Create a new World Info`, t`Enter a name for the new file:`, tempName);
if (finalName) {
await createNewWorldInfo(finalName, { interactive: true });