mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
refactor(i18n): streamline i18n initialization and update translation methods
This commit is contained in:
@@ -4,7 +4,6 @@ import { createPageControls } from './components/controls/index.js';
|
||||
import { CheckpointContextMenu } from './components/ContextMenu/index.js';
|
||||
import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js';
|
||||
import { MODEL_TYPES } from './api/apiConfig.js';
|
||||
import { initializePageI18n } from './utils/i18nHelpers.js';
|
||||
|
||||
// Initialize the Checkpoints page
|
||||
class CheckpointsPageManager {
|
||||
@@ -37,9 +36,6 @@ class CheckpointsPageManager {
|
||||
// Initialize common page features
|
||||
appCore.initializePageFeatures();
|
||||
|
||||
// Initialize i18n for the page
|
||||
initializePageI18n();
|
||||
|
||||
console.log('Checkpoints Manager initialized');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SearchManager } from '../managers/SearchManager.js';
|
||||
import { FilterManager } from '../managers/FilterManager.js';
|
||||
import { initPageState } from '../state/index.js';
|
||||
import { getStorageItem } from '../utils/storageHelpers.js';
|
||||
import { updateSearchPlaceholder } from '../utils/i18nHelpers.js';
|
||||
import { updateElementAttribute } from '../utils/i18nHelpers.js';
|
||||
|
||||
/**
|
||||
* Header.js - Manages the application header behavior across different pages
|
||||
@@ -49,18 +49,17 @@ export class HeaderManager {
|
||||
// Handle theme toggle
|
||||
const themeToggle = document.querySelector('.theme-toggle');
|
||||
if (themeToggle) {
|
||||
// Set initial state based on current theme
|
||||
const currentTheme = getStorageItem('theme') || 'auto';
|
||||
themeToggle.classList.add(`theme-${currentTheme}`);
|
||||
|
||||
// Set initial tooltip text
|
||||
|
||||
// 使用i18nHelpers更新themeToggle的title
|
||||
this.updateThemeTooltip(themeToggle, currentTheme);
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
|
||||
themeToggle.addEventListener('click', async () => {
|
||||
if (typeof toggleTheme === 'function') {
|
||||
const newTheme = toggleTheme();
|
||||
// Update tooltip based on next toggle action
|
||||
this.updateThemeTooltip(themeToggle, newTheme);
|
||||
// 使用i18nHelpers更新themeToggle的title
|
||||
await this.updateThemeTooltip(themeToggle, newTheme);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -124,41 +123,43 @@ export class HeaderManager {
|
||||
// Hide search functionality on Statistics page
|
||||
this.updateHeaderForPage();
|
||||
}
|
||||
|
||||
updateHeaderForPage() {
|
||||
|
||||
async updateHeaderForPage() {
|
||||
const headerSearch = document.getElementById('headerSearch');
|
||||
|
||||
const searchInput = headerSearch?.querySelector('#searchInput');
|
||||
const searchButtons = headerSearch?.querySelectorAll('button');
|
||||
const placeholderKey = 'header.search.placeholders.' + this.currentPage;
|
||||
|
||||
if (this.currentPage === 'statistics' && headerSearch) {
|
||||
headerSearch.classList.add('disabled');
|
||||
// Disable search functionality
|
||||
const searchInput = headerSearch.querySelector('#searchInput');
|
||||
const searchButtons = headerSearch.querySelectorAll('button');
|
||||
if (searchInput) {
|
||||
searchInput.disabled = true;
|
||||
searchInput.placeholder = window.i18n?.t('header.search.notAvailable') || 'Search not available on statistics page';
|
||||
// 使用i18nHelpers更新placeholder
|
||||
await updateElementAttribute(searchInput, 'placeholder', 'header.search.notAvailable', {}, 'Search not available on statistics page');
|
||||
}
|
||||
searchButtons.forEach(btn => btn.disabled = true);
|
||||
searchButtons?.forEach(btn => btn.disabled = true);
|
||||
} else if (headerSearch) {
|
||||
headerSearch.classList.remove('disabled');
|
||||
// Re-enable search functionality
|
||||
const searchInput = headerSearch.querySelector('#searchInput');
|
||||
const searchButtons = headerSearch.querySelectorAll('button');
|
||||
if (searchInput) {
|
||||
searchInput.disabled = false;
|
||||
// Update placeholder based on current page
|
||||
updateSearchPlaceholder(window.location.pathname);
|
||||
// 使用i18nHelpers更新placeholder
|
||||
await updateElementAttribute(searchInput, 'placeholder', placeholderKey, {}, '');
|
||||
}
|
||||
searchButtons.forEach(btn => btn.disabled = false);
|
||||
searchButtons?.forEach(btn => btn.disabled = false);
|
||||
}
|
||||
}
|
||||
|
||||
updateThemeTooltip(themeToggle, currentTheme) {
|
||||
if (!window.i18n) return;
|
||||
|
||||
|
||||
async updateThemeTooltip(themeToggle, currentTheme) {
|
||||
if (!themeToggle) return;
|
||||
let key;
|
||||
if (currentTheme === 'light') {
|
||||
themeToggle.title = window.i18n.t('header.theme.switchToDark');
|
||||
key = 'header.theme.switchToDark';
|
||||
} else if (currentTheme === 'dark') {
|
||||
themeToggle.title = window.i18n.t('header.theme.switchToLight');
|
||||
key = 'header.theme.switchToLight';
|
||||
} else {
|
||||
key = 'header.theme.toggle';
|
||||
}
|
||||
// 使用i18nHelpers更新title
|
||||
await updateElementAttribute(themeToggle, 'title', key, {}, '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createPageControls } from './components/controls/index.js';
|
||||
import { EmbeddingContextMenu } from './components/ContextMenu/index.js';
|
||||
import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js';
|
||||
import { MODEL_TYPES } from './api/apiConfig.js';
|
||||
import { initializePageI18n } from './utils/i18nHelpers.js';
|
||||
|
||||
// Initialize the Embeddings page
|
||||
class EmbeddingsPageManager {
|
||||
@@ -37,9 +36,6 @@ class EmbeddingsPageManager {
|
||||
// Initialize common page features
|
||||
appCore.initializePageFeatures();
|
||||
|
||||
// Initialize i18n for the page
|
||||
initializePageI18n();
|
||||
|
||||
console.log('Embeddings Manager initialized');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ class I18nManager {
|
||||
this.locales = {};
|
||||
this.translations = {};
|
||||
this.loadedLocales = new Set();
|
||||
this.ready = false;
|
||||
this.readyPromise = null;
|
||||
|
||||
// Available locales configuration
|
||||
this.availableLocales = {
|
||||
@@ -25,8 +27,9 @@ class I18nManager {
|
||||
};
|
||||
|
||||
this.currentLocale = this.getLanguageFromSettings();
|
||||
// Initialize with current locale
|
||||
this.initializeWithLocale(this.currentLocale);
|
||||
|
||||
// Initialize with current locale and create ready promise
|
||||
this.readyPromise = this.initializeWithLocale(this.currentLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,12 +81,43 @@ class I18nManager {
|
||||
try {
|
||||
this.translations = await this.loadLocale(locale);
|
||||
this.currentLocale = locale;
|
||||
this.ready = true;
|
||||
|
||||
// Dispatch ready event
|
||||
window.dispatchEvent(new CustomEvent('i18nReady', {
|
||||
detail: { language: locale }
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn(`Failed to initialize with locale ${locale}, falling back to English`, error);
|
||||
this.translations = await this.loadLocale('en');
|
||||
this.currentLocale = 'en';
|
||||
this.ready = true;
|
||||
|
||||
window.dispatchEvent(new CustomEvent('i18nReady', {
|
||||
detail: { language: 'en' }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for i18n to be ready
|
||||
* @returns {Promise} Promise that resolves when i18n is ready
|
||||
*/
|
||||
async waitForReady() {
|
||||
if (this.ready) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.readyPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if i18n is ready
|
||||
* @returns {boolean} True if ready
|
||||
*/
|
||||
isReady() {
|
||||
return this.ready && this.translations && Object.keys(this.translations).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language from user settings with fallback to English
|
||||
* @returns {string} Language code
|
||||
@@ -124,8 +158,12 @@ class I18nManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// Reset ready state
|
||||
this.ready = false;
|
||||
|
||||
// Load the new locale
|
||||
await this.initializeWithLocale(languageCode);
|
||||
this.readyPromise = this.initializeWithLocale(languageCode);
|
||||
await this.readyPromise;
|
||||
|
||||
// Save to localStorage
|
||||
const STORAGE_PREFIX = 'lora_manager_';
|
||||
@@ -172,6 +210,12 @@ class I18nManager {
|
||||
* @returns {string} Translated text
|
||||
*/
|
||||
t(key, params = {}) {
|
||||
// If not ready, return key as fallback
|
||||
if (!this.isReady()) {
|
||||
console.warn(`i18n not ready, returning key: ${key}`);
|
||||
return key;
|
||||
}
|
||||
|
||||
const keys = key.split('.');
|
||||
let value = this.translations;
|
||||
|
||||
@@ -282,18 +326,11 @@ class I18nManager {
|
||||
/**
|
||||
* Initialize i18n from user settings
|
||||
* This prevents language flashing on page load
|
||||
* @deprecated Use waitForReady() instead
|
||||
*/
|
||||
async initializeFromSettings() {
|
||||
const targetLanguage = this.getLanguageFromSettings();
|
||||
|
||||
// Set language immediately without animation/transition
|
||||
this.currentLocale = targetLanguage;
|
||||
this.translations = this.locales[targetLanguage] || this.locales['en'];
|
||||
|
||||
// Dispatch event to notify that language has been initialized
|
||||
window.dispatchEvent(new CustomEvent('languageInitialized', {
|
||||
detail: { language: targetLanguage }
|
||||
}));
|
||||
console.warn('initializeFromSettings() is deprecated, use waitForReady() instead');
|
||||
return this.waitForReady();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,4 +338,4 @@ class I18nManager {
|
||||
export const i18n = new I18nManager();
|
||||
|
||||
// Export for global access (will be attached to window)
|
||||
export default i18n;
|
||||
export default i18n;
|
||||
@@ -5,7 +5,7 @@ import { modalManager } from './ModalManager.js';
|
||||
import { moveManager } from './MoveManager.js';
|
||||
import { getModelApiClient } from '../api/modelApiFactory.js';
|
||||
import { MODEL_TYPES, MODEL_CONFIG } from '../api/apiConfig.js';
|
||||
import { updateBulkSelectionCount } from '../utils/i18nHelpers.js';
|
||||
import { updateElementText } from '../utils/i18nHelpers.js';
|
||||
|
||||
export class BulkManager {
|
||||
constructor() {
|
||||
@@ -185,9 +185,9 @@ export class BulkManager {
|
||||
const countElement = document.getElementById('selectedCount');
|
||||
|
||||
if (countElement) {
|
||||
// Use i18n helper to update the count text
|
||||
updateBulkSelectionCount(state.selectedModels.size);
|
||||
|
||||
// Use i18nHelpers.js to update the count text
|
||||
updateElementText(countElement, 'loras.bulkOperations.selected', { count: state.selectedModels.size });
|
||||
|
||||
const existingCaret = countElement.querySelector('.dropdown-caret');
|
||||
if (existingCaret) {
|
||||
existingCaret.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { state } from '../state/index.js';
|
||||
import { resetAndReload } from '../api/modelApiFactory.js';
|
||||
import { setStorageItem, getStorageItem } from '../utils/storageHelpers.js';
|
||||
import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS, PATH_TEMPLATE_PLACEHOLDERS, DEFAULT_PATH_TEMPLATES } from '../utils/constants.js';
|
||||
import { switchLanguage } from '../utils/i18nHelpers.js';
|
||||
// import { switchLanguage } from '../utils/i18nHelpers.js';
|
||||
|
||||
export class SettingsManager {
|
||||
constructor() {
|
||||
@@ -982,7 +982,7 @@ export class SettingsManager {
|
||||
}
|
||||
|
||||
// Switch language immediately
|
||||
switchLanguage(selectedLanguage);
|
||||
// switchLanguage(selectedLanguage);
|
||||
|
||||
showToast('Language changed successfully.', 'success');
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
import { appCore } from './core.js';
|
||||
import { ImportManager } from './managers/ImportManager.js';
|
||||
import { RecipeModal } from './components/RecipeModal.js';
|
||||
import { getCurrentPageState, state } from './state/index.js';
|
||||
import { getCurrentPageState } from './state/index.js';
|
||||
import { getSessionItem, removeSessionItem } from './utils/storageHelpers.js';
|
||||
import { RecipeContextMenu } from './components/ContextMenu/index.js';
|
||||
import { DuplicatesManager } from './components/DuplicatesManager.js';
|
||||
import { refreshVirtualScroll } from './utils/infiniteScroll.js';
|
||||
import { refreshRecipes } from './api/recipeApi.js';
|
||||
import { initializePageI18n } from './utils/i18nHelpers.js';
|
||||
|
||||
class RecipeManager {
|
||||
constructor() {
|
||||
@@ -55,9 +54,6 @@ class RecipeManager {
|
||||
|
||||
// Initialize common page features
|
||||
appCore.initializePageFeatures();
|
||||
|
||||
// Initialize i18n for the page
|
||||
initializePageI18n();
|
||||
}
|
||||
|
||||
_initSearchOptions() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Statistics page functionality
|
||||
import { appCore } from './core.js';
|
||||
import { showToast } from './utils/uiHelpers.js';
|
||||
import { initializePageI18n } from './utils/i18nHelpers.js';
|
||||
|
||||
// Chart.js import (assuming it's available globally or via CDN)
|
||||
// If Chart.js isn't available, we'll need to add it to the project
|
||||
@@ -27,9 +26,6 @@ class StatisticsManager {
|
||||
// Initialize charts and visualizations
|
||||
this.initializeVisualizations();
|
||||
|
||||
// Initialize i18n for the page
|
||||
initializePageI18n();
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,226 +1,118 @@
|
||||
/**
|
||||
* DOM utilities for i18n text replacement
|
||||
* i18n utility functions for safe translation handling
|
||||
*/
|
||||
import { i18n } from '../i18n/index.js';
|
||||
|
||||
/**
|
||||
* Replace text content in DOM elements with translations
|
||||
* Uses data-i18n attribute to specify translation keys
|
||||
*/
|
||||
export function translateDOM() {
|
||||
if (!window.i18n) return;
|
||||
|
||||
// Select all elements with data-i18n attributes, including optgroups and options
|
||||
const elements = document.querySelectorAll('[data-i18n]');
|
||||
|
||||
elements.forEach(element => {
|
||||
const key = element.getAttribute('data-i18n');
|
||||
const target = element.getAttribute('data-i18n-target') || 'textContent';
|
||||
|
||||
if (key) {
|
||||
const translation = window.i18n.t(key);
|
||||
|
||||
// Handle different target attributes
|
||||
switch (target) {
|
||||
case 'placeholder':
|
||||
element.placeholder = translation;
|
||||
break;
|
||||
case 'title':
|
||||
element.title = translation;
|
||||
break;
|
||||
case 'label':
|
||||
// For optgroup elements
|
||||
element.label = translation;
|
||||
break;
|
||||
case 'value':
|
||||
element.value = translation;
|
||||
break;
|
||||
case 'textContent':
|
||||
default:
|
||||
element.textContent = translation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update placeholder text based on current page
|
||||
* @param {string} currentPath - Current page path
|
||||
*/
|
||||
export function updateSearchPlaceholder(currentPath) {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (!searchInput) return;
|
||||
|
||||
let placeholderKey = 'header.search.placeholder';
|
||||
|
||||
if (currentPath === '/loras') {
|
||||
placeholderKey = 'header.search.placeholders.loras';
|
||||
} else if (currentPath === '/loras/recipes') {
|
||||
placeholderKey = 'header.search.placeholders.recipes';
|
||||
} else if (currentPath === '/checkpoints') {
|
||||
placeholderKey = 'header.search.placeholders.checkpoints';
|
||||
} else if (currentPath === '/embeddings') {
|
||||
placeholderKey = 'header.search.placeholders.embeddings';
|
||||
}
|
||||
|
||||
searchInput.placeholder = i18n.t(placeholderKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text content for an element using i18n
|
||||
* @param {Element|string} element - DOM element or selector
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Translation parameters
|
||||
*/
|
||||
export function setTranslatedText(element, key, params = {}) {
|
||||
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
||||
if (el) {
|
||||
el.textContent = i18n.t(key, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute value for an element using i18n
|
||||
* @param {Element|string} element - DOM element or selector
|
||||
* @param {string} attribute - Attribute name
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Translation parameters
|
||||
*/
|
||||
export function setTranslatedAttribute(element, attribute, key, params = {}) {
|
||||
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
||||
if (el) {
|
||||
el.setAttribute(attribute, i18n.t(key, params));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translated element
|
||||
* @param {string} tagName - HTML tag name
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Translation parameters
|
||||
* @param {Object} attributes - Additional attributes
|
||||
* @returns {Element} Created element
|
||||
*/
|
||||
export function createTranslatedElement(tagName, key, params = {}, attributes = {}) {
|
||||
const element = document.createElement(tagName);
|
||||
element.textContent = i18n.t(key, params);
|
||||
|
||||
Object.entries(attributes).forEach(([attr, value]) => {
|
||||
element.setAttribute(attr, value);
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bulk selection count text
|
||||
* @param {number} count - Number of selected items
|
||||
*/
|
||||
export function updateBulkSelectionCount(count) {
|
||||
const selectedCountElement = document.getElementById('selectedCount');
|
||||
if (selectedCountElement) {
|
||||
const textNode = selectedCountElement.firstChild;
|
||||
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
|
||||
textNode.textContent = i18n.t('loras.bulkOperations.selected', { count });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size with localized units
|
||||
* @param {number} bytes - Size in bytes
|
||||
* @param {number} decimals - Number of decimal places
|
||||
* @returns {string} Formatted size string
|
||||
*/
|
||||
export function formatFileSize(bytes, decimals = 2) {
|
||||
return i18n.formatFileSize(bytes, decimals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date with current locale
|
||||
* @param {Date|string|number} date - Date to format
|
||||
* @param {Object} options - Intl.DateTimeFormat options
|
||||
* @returns {string} Formatted date string
|
||||
*/
|
||||
export function formatDate(date, options = {}) {
|
||||
return i18n.formatDate(date, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number with current locale
|
||||
* @param {number} number - Number to format
|
||||
* @param {Object} options - Intl.NumberFormat options
|
||||
* @returns {string} Formatted number string
|
||||
*/
|
||||
export function formatNumber(number, options = {}) {
|
||||
return i18n.formatNumber(number, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize i18n for the page
|
||||
* This should be called after DOM content is loaded
|
||||
*/
|
||||
export function initializePageI18n() {
|
||||
// Always use the client-side i18n with user settings
|
||||
if (window.i18n) {
|
||||
// Translate DOM elements
|
||||
translateDOM();
|
||||
|
||||
// Update search placeholder based on current page
|
||||
const currentPath = window.location.pathname;
|
||||
updateSearchPlaceholder(currentPath);
|
||||
|
||||
// Set document direction for RTL languages
|
||||
if (i18n.isRTL()) {
|
||||
document.documentElement.setAttribute('dir', 'rtl');
|
||||
document.body.classList.add('rtl');
|
||||
} else {
|
||||
document.documentElement.setAttribute('dir', 'ltr');
|
||||
document.body.classList.remove('rtl');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get translation directly
|
||||
* Safe translation function that waits for i18n to be ready
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Parameters for interpolation
|
||||
* @returns {string} Translated text
|
||||
* @param {string} fallback - Fallback text if translation fails
|
||||
* @returns {Promise<string>} Translated text
|
||||
*/
|
||||
export function t(key, params = {}) {
|
||||
return i18n.t(key, params);
|
||||
export async function safeTranslate(key, params = {}, fallback = null) {
|
||||
if (!window.i18n) {
|
||||
console.warn('i18n not available');
|
||||
return fallback || key;
|
||||
}
|
||||
|
||||
// Wait for i18n to be ready
|
||||
await window.i18n.waitForReady();
|
||||
|
||||
const translation = window.i18n.t(key, params);
|
||||
|
||||
// If translation returned the key (meaning not found), use fallback
|
||||
if (translation === key && fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch language and retranslate the page
|
||||
* @param {string} languageCode - The language code to switch to
|
||||
* @returns {boolean} True if language switch was successful
|
||||
* Update element text with translation
|
||||
* @param {HTMLElement|string} element - Element or selector
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Parameters for interpolation
|
||||
* @param {string} fallback - Fallback text
|
||||
*/
|
||||
export function switchLanguage(languageCode) {
|
||||
if (i18n.setLanguage(languageCode)) {
|
||||
// Retranslate the entire page
|
||||
translateDOM();
|
||||
|
||||
// Update search placeholder based on current page
|
||||
const currentPath = window.location.pathname;
|
||||
updateSearchPlaceholder(currentPath);
|
||||
|
||||
// Set document direction for RTL languages
|
||||
if (i18n.isRTL()) {
|
||||
document.documentElement.setAttribute('dir', 'rtl');
|
||||
document.body.classList.add('rtl');
|
||||
} else {
|
||||
document.documentElement.setAttribute('dir', 'ltr');
|
||||
document.body.classList.remove('rtl');
|
||||
}
|
||||
|
||||
// Dispatch a custom event for other components to react to language change
|
||||
window.dispatchEvent(new CustomEvent('languageChanged', {
|
||||
detail: { language: languageCode }
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
export async function updateElementText(element, key, params = {}, fallback = null) {
|
||||
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
||||
if (!el) return;
|
||||
|
||||
const text = await safeTranslate(key, params, fallback);
|
||||
el.textContent = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update element attribute with translation
|
||||
* @param {HTMLElement|string} element - Element or selector
|
||||
* @param {string} attribute - Attribute name (e.g., 'title', 'placeholder')
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Parameters for interpolation
|
||||
* @param {string} fallback - Fallback text
|
||||
*/
|
||||
export async function updateElementAttribute(element, attribute, key, params = {}, fallback = null) {
|
||||
const el = typeof element === 'string' ? document.querySelector(element) : element;
|
||||
if (!el) return;
|
||||
|
||||
const text = await safeTranslate(key, params, fallback);
|
||||
el.setAttribute(attribute, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reactive translation that updates when language changes
|
||||
* @param {string} key - Translation key
|
||||
* @param {Object} params - Parameters for interpolation
|
||||
* @param {Function} callback - Callback function to call with translated text
|
||||
*/
|
||||
export function createReactiveTranslation(key, params = {}, callback) {
|
||||
let currentLanguage = null;
|
||||
|
||||
const updateTranslation = async () => {
|
||||
if (!window.i18n) return;
|
||||
|
||||
await window.i18n.waitForReady();
|
||||
const newLanguage = window.i18n.getCurrentLocale();
|
||||
|
||||
// Only update if language changed or first time
|
||||
if (newLanguage !== currentLanguage) {
|
||||
currentLanguage = newLanguage;
|
||||
const translation = window.i18n.t(key, params);
|
||||
callback(translation);
|
||||
}
|
||||
};
|
||||
|
||||
// Initial update
|
||||
updateTranslation();
|
||||
|
||||
// Listen for language changes
|
||||
window.addEventListener('languageChanged', updateTranslation);
|
||||
window.addEventListener('i18nReady', updateTranslation);
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
window.removeEventListener('languageChanged', updateTranslation);
|
||||
window.removeEventListener('i18nReady', updateTranslation);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch update multiple elements with translations
|
||||
* @param {Array} updates - Array of update configurations
|
||||
* Each update should have: { element, key, type: 'text'|'attribute', attribute?, params?, fallback? }
|
||||
*/
|
||||
export async function batchUpdateTranslations(updates) {
|
||||
if (!window.i18n) return;
|
||||
|
||||
await window.i18n.waitForReady();
|
||||
|
||||
for (const update of updates) {
|
||||
const { element, key, type = 'text', attribute, params = {}, fallback } = update;
|
||||
|
||||
if (type === 'text') {
|
||||
await updateElementText(element, key, params, fallback);
|
||||
} else if (type === 'attribute' && attribute) {
|
||||
await updateElementAttribute(element, attribute, key, params, fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user