mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-01-07 15:11:57 +01:00
Merge pull request #2436 from SillyTavern/translation-improvements
Translation improvements
This commit is contained in:
commit
175a91f979
@ -209,7 +209,7 @@ import {
|
||||
instruct_presets,
|
||||
selectContextPreset,
|
||||
} from './scripts/instruct-mode.js';
|
||||
import { initLocales } from './scripts/i18n.js';
|
||||
import { initLocales, t, translate } from './scripts/i18n.js';
|
||||
import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, getTokenizerModel, initTokenizers, saveTokenCache } from './scripts/tokenizers.js';
|
||||
import {
|
||||
user_avatar,
|
||||
@ -7826,6 +7826,8 @@ window['SillyTavern'].getContext = function () {
|
||||
messageFormatting: messageFormatting,
|
||||
shouldSendOnEnter: shouldSendOnEnter,
|
||||
isMobile: isMobile,
|
||||
t: t,
|
||||
translate: translate,
|
||||
tags: tags,
|
||||
tagMap: tag_map,
|
||||
menuType: menu_type,
|
||||
|
@ -9,6 +9,71 @@ const langs = await fetch('/locales/lang.json').then(response => response.json()
|
||||
// eslint-disable-next-line prefer-const
|
||||
var localeData = await getLocaleData(localeFile);
|
||||
|
||||
/**
|
||||
* An observer that will check if any new i18n elements are added to the document
|
||||
* @type {MutationObserver}
|
||||
*/
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE && node instanceof Element) {
|
||||
if (node.hasAttribute('data-i18n')) {
|
||||
translateElement(node);
|
||||
}
|
||||
node.querySelectorAll('[data-i18n]').forEach(element => {
|
||||
translateElement(element);
|
||||
});
|
||||
}
|
||||
});
|
||||
if (mutation.attributeName === 'data-i18n' && mutation.target instanceof Element) {
|
||||
translateElement(mutation.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Translates a template string with named arguments
|
||||
*
|
||||
* Uses the template literal with all values replaced by index placeholder for translation key.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* toastr.warn(t`Tag ${tagName} not found.`);
|
||||
* ```
|
||||
* Should be translated in the translation files as:
|
||||
* ```
|
||||
* Tag ${0} not found. -> Tag ${0} nicht gefunden.
|
||||
* ```
|
||||
*
|
||||
* @param {TemplateStringsArray} strings - Template strings array
|
||||
* @param {...any} values - Values for placeholders in the template string
|
||||
* @returns {string} Translated and formatted string
|
||||
*/
|
||||
export function t(strings, ...values) {
|
||||
let str = strings.reduce((result, string, i) => result + string + (values[i] !== undefined ? `\${${i}}` : ''), '');
|
||||
let translatedStr = translate(str);
|
||||
|
||||
// Replace indexed placeholders with actual values
|
||||
return translatedStr.replace(/\$\{(\d+)\}/g, (match, index) => values[index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a given key or text
|
||||
*
|
||||
* If the translation is based on a key, that one is used to find a possible translation in the translation file.
|
||||
* The original text still has to be provided, as that is the default value being returned if no translation is found.
|
||||
*
|
||||
* For in-code text translation on a format string, using the template literal `t` is preferred.
|
||||
*
|
||||
* @param {string} text - The text to translate
|
||||
* @param {string?} key - The key to use for translation. If not provided, text is used as the key.
|
||||
* @returns {string} - The translated text
|
||||
*/
|
||||
export function translate(text, key = null) {
|
||||
const translationKey = key || text;
|
||||
return localeData?.[translationKey] || text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the locale data for the given language.
|
||||
* @param {string} language Language code
|
||||
@ -40,6 +105,29 @@ function findLang(language) {
|
||||
return supportedLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a given element based on its data-i18n attribute.
|
||||
* @param {Element} element The element to translate
|
||||
*/
|
||||
function translateElement(element) {
|
||||
const keys = element.getAttribute('data-i18n').split(';'); // Multi-key entries are ; delimited
|
||||
for (const key of keys) {
|
||||
const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
|
||||
if (attributeMatch) { // attribute-tagged key
|
||||
const localizedValue = localeData?.[attributeMatch[2]];
|
||||
if (localizedValue || localizedValue === '') {
|
||||
element.setAttribute(attributeMatch[1], localizedValue);
|
||||
}
|
||||
} else { // No attribute tag, treat as 'text'
|
||||
const localizedValue = localeData?.[key];
|
||||
if (localizedValue || localizedValue === '') {
|
||||
element.textContent = localizedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function getMissingTranslations() {
|
||||
const missingData = [];
|
||||
|
||||
@ -103,22 +191,7 @@ export function applyLocale(root = document) {
|
||||
|
||||
//find all the elements with `data-i18n` attribute
|
||||
$root.find('[data-i18n]').each(function () {
|
||||
//read the translation from the language data
|
||||
const keys = $(this).data('i18n').split(';'); // Multi-key entries are ; delimited
|
||||
for (const key of keys) {
|
||||
const attributeMatch = key.match(/\[(\S+)\](.+)/); // [attribute]key
|
||||
if (attributeMatch) { // attribute-tagged key
|
||||
const localizedValue = localeData?.[attributeMatch[2]];
|
||||
if (localizedValue || localizedValue == '') {
|
||||
$(this).attr(attributeMatch[1], localizedValue);
|
||||
}
|
||||
} else { // No attribute tag, treat as 'text'
|
||||
const localizedValue = localeData?.[key];
|
||||
if (localizedValue || localizedValue == '') {
|
||||
$(this).text(localizedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
translateElement(this);
|
||||
});
|
||||
|
||||
if (root !== document) {
|
||||
@ -126,7 +199,6 @@ export function applyLocale(root = document) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function addLanguagesToDropdown() {
|
||||
const uiLanguageSelects = $('#ui_language_select, #onboarding_ui_language_select');
|
||||
for (const langObj of langs) { // Set the value to the language code
|
||||
@ -159,6 +231,13 @@ export function initLocales() {
|
||||
location.reload();
|
||||
});
|
||||
|
||||
observer.observe(document, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['data-i18n'],
|
||||
});
|
||||
|
||||
registerDebugFunction('getMissingTranslations', 'Get missing translations', 'Detects missing localization data in the current locale and dumps the data into the browser console. If the current locale is English, searches all other locales.', getMissingTranslations);
|
||||
registerDebugFunction('applyLocale', 'Apply locale', 'Reapplies the currently selected locale to the page.', applyLocale);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user