diff --git a/static/js/components/Header.js b/static/js/components/Header.js index c6c63185..ab0e940d 100644 --- a/static/js/components/Header.js +++ b/static/js/components/Header.js @@ -59,7 +59,7 @@ export class HeaderManager { if (typeof toggleTheme === 'function') { const newTheme = toggleTheme(); // 使用i18nHelpers更新themeToggle的title - await this.updateThemeTooltip(themeToggle, newTheme); + this.updateThemeTooltip(themeToggle, newTheme); } }); } @@ -124,7 +124,7 @@ export class HeaderManager { this.updateHeaderForPage(); } - async updateHeaderForPage() { + updateHeaderForPage() { const headerSearch = document.getElementById('headerSearch'); const searchInput = headerSearch?.querySelector('#searchInput'); const searchButtons = headerSearch?.querySelectorAll('button'); @@ -134,22 +134,22 @@ export class HeaderManager { headerSearch.classList.add('disabled'); if (searchInput) { searchInput.disabled = true; - // 使用i18nHelpers更新placeholder - await updateElementAttribute(searchInput, 'placeholder', 'header.search.notAvailable', {}, 'Search not available on statistics page'); + // Use i18nHelpers to update placeholder + updateElementAttribute(searchInput, 'placeholder', 'header.search.notAvailable', {}, 'Search not available on statistics page'); } searchButtons?.forEach(btn => btn.disabled = true); } else if (headerSearch) { headerSearch.classList.remove('disabled'); if (searchInput) { searchInput.disabled = false; - // 使用i18nHelpers更新placeholder - await updateElementAttribute(searchInput, 'placeholder', placeholderKey, {}, ''); + // Use i18nHelpers to update placeholder + updateElementAttribute(searchInput, 'placeholder', placeholderKey, {}, ''); } searchButtons?.forEach(btn => btn.disabled = false); } } - async updateThemeTooltip(themeToggle, currentTheme) { + updateThemeTooltip(themeToggle, currentTheme) { if (!themeToggle) return; let key; if (currentTheme === 'light') { @@ -159,7 +159,7 @@ export class HeaderManager { } else { key = 'header.theme.toggle'; } - // 使用i18nHelpers更新title - await updateElementAttribute(themeToggle, 'title', key, {}, ''); + // Use i18nHelpers to update title + updateElementAttribute(themeToggle, 'title', key, {}, ''); } } diff --git a/static/js/components/shared/ModelCard.js b/static/js/components/shared/ModelCard.js index da245bcb..c0951eac 100644 --- a/static/js/components/shared/ModelCard.js +++ b/static/js/components/shared/ModelCard.js @@ -143,15 +143,15 @@ async function toggleFavorite(card) { }); if (newFavoriteState) { - const addedText = await safeTranslate('modelCard.favorites.added', {}, 'Added to favorites'); + const addedText = safeTranslate('modelCard.favorites.added', {}, 'Added to favorites'); showToast(addedText, 'success'); } else { - const removedText = await safeTranslate('modelCard.favorites.removed', {}, 'Removed from favorites'); + const removedText = safeTranslate('modelCard.favorites.removed', {}, 'Removed from favorites'); showToast(removedText, 'success'); } } catch (error) { console.error('Failed to update favorite status:', error); - const errorText = await safeTranslate('modelCard.favorites.updateFailed', {}, 'Failed to update favorite status'); + const errorText = safeTranslate('modelCard.favorites.updateFailed', {}, 'Failed to update favorite status'); showToast(errorText, 'error'); } } @@ -164,8 +164,8 @@ function handleSendToWorkflow(card, replaceMode, modelType) { sendLoraToWorkflow(loraSyntax, replaceMode, 'lora'); } else { // Checkpoint send functionality - to be implemented - safeTranslate('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'Send checkpoint to workflow - feature to be implemented') - .then(text => showToast(text, 'info')); + const text = safeTranslate('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'Send checkpoint to workflow - feature to be implemented'); + showToast(text, 'info'); } } @@ -200,8 +200,8 @@ async function handleExampleImagesAccess(card, modelType) { } } catch (error) { console.error('Error checking for example images:', error); - safeTranslate('modelCard.exampleImages.checkError', {}, 'Error checking for example images') - .then(text => showToast(text, 'error')); + const text = safeTranslate('modelCard.exampleImages.checkError', {}, 'Error checking for example images'); + showToast(text, 'error'); } } @@ -283,8 +283,8 @@ function showExampleAccessModal(card, modelType) { // Get the model hash const modelHash = card.dataset.sha256; if (!modelHash) { - safeTranslate('modelCard.exampleImages.missingHash', {}, 'Missing model hash information.') - .then(text => showToast(text, 'error')); + const text = safeTranslate('modelCard.exampleImages.missingHash', {}, 'Missing model hash information.'); + showToast(text, 'error'); return; } diff --git a/static/js/components/shared/ModelDescription.js b/static/js/components/shared/ModelDescription.js index fab3f705..3299e75d 100644 --- a/static/js/components/shared/ModelDescription.js +++ b/static/js/components/shared/ModelDescription.js @@ -63,7 +63,7 @@ async function loadModelDescription() { const description = await getModelApiClient().fetchModelDescription(filePath); // Update content - const noDescriptionText = await safeTranslate('modals.model.description.noDescription', {}, 'No model description available'); + const noDescriptionText = safeTranslate('modals.model.description.noDescription', {}, 'No model description available'); descriptionContent.innerHTML = description || `
${noDescriptionText}
`; descriptionContent.dataset.loaded = 'true'; @@ -72,7 +72,7 @@ async function loadModelDescription() { } catch (error) { console.error('Error loading model description:', error); - const failedText = await safeTranslate('modals.model.description.failedToLoad', {}, 'Failed to load model description'); + const failedText = safeTranslate('modals.model.description.failedToLoad', {}, 'Failed to load model description'); descriptionContent.innerHTML = `
${failedText}
`; } finally { // Hide loading state @@ -96,7 +96,7 @@ export async function setupModelDescriptionEditing(filePath) { editBtn = document.createElement('button'); editBtn.className = 'edit-model-description-btn'; // Set title using i18n - const editTitle = await safeTranslate('modals.model.description.editTitle', {}, 'Edit model description'); + const editTitle = safeTranslate('modals.model.description.editTitle', {}, 'Edit model description'); editBtn.title = editTitle; editBtn.innerHTML = ''; descContainer.insertBefore(editBtn, descContent); @@ -154,7 +154,7 @@ export async function setupModelDescriptionEditing(filePath) { } if (!newValue) { this.innerHTML = originalValue; - const emptyErrorText = await safeTranslate('modals.model.description.validation.cannotBeEmpty', {}, 'Description cannot be empty'); + const emptyErrorText = safeTranslate('modals.model.description.validation.cannotBeEmpty', {}, 'Description cannot be empty'); showToast(emptyErrorText, 'error'); exitEditMode(); return; @@ -163,11 +163,11 @@ export async function setupModelDescriptionEditing(filePath) { // Save to backend const { getModelApiClient } = await import('../../api/modelApiFactory.js'); await getModelApiClient().saveModelMetadata(filePath, { modelDescription: newValue }); - const successText = await safeTranslate('modals.model.description.messages.updated', {}, 'Model description updated'); + const successText = safeTranslate('modals.model.description.messages.updated', {}, 'Model description updated'); showToast(successText, 'success'); } catch (err) { this.innerHTML = originalValue; - const errorText = await safeTranslate('modals.model.description.messages.updateFailed', {}, 'Failed to update model description'); + const errorText = safeTranslate('modals.model.description.messages.updateFailed', {}, 'Failed to update model description'); showToast(errorText, 'error'); } finally { exitEditMode(); diff --git a/static/js/components/shared/ModelMetadata.js b/static/js/components/shared/ModelMetadata.js index c3fb0d82..05293f25 100644 --- a/static/js/components/shared/ModelMetadata.js +++ b/static/js/components/shared/ModelMetadata.js @@ -83,8 +83,8 @@ export function setupModelNameEditing(filePath) { sel.removeAllRanges(); sel.addRange(range); - safeTranslate('modelMetadata.validation.nameTooLong', {}, 'Model name is limited to 100 characters') - .then(text => showToast(text, 'warning')); + const text = safeTranslate('modelMetadata.validation.nameTooLong', {}, 'Model name is limited to 100 characters'); + showToast(text, 'warning'); } }); diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js index 6018fa1d..98a6a252 100644 --- a/static/js/components/shared/ModelModal.js +++ b/static/js/components/shared/ModelModal.js @@ -62,9 +62,9 @@ export async function showModelModal(model, modelType) { } // Generate tabs based on model type - const examplesText = await safeTranslate('modals.model.tabs.examples', {}, 'Examples'); - const descriptionText = await safeTranslate('modals.model.tabs.description', {}, 'Model Description'); - const recipesText = await safeTranslate('modals.model.tabs.recipes', {}, 'Recipes'); + const examplesText = safeTranslate('modals.model.tabs.examples', {}, 'Examples'); + const descriptionText = safeTranslate('modals.model.tabs.description', {}, 'Model Description'); + const recipesText = safeTranslate('modals.model.tabs.recipes', {}, 'Recipes'); const tabsContent = modelType === 'loras' ? ` @@ -73,10 +73,10 @@ export async function showModelModal(model, modelType) { ` `; - const loadingExampleImagesText = await safeTranslate('modals.model.loading.exampleImages', {}, 'Loading example images...'); - const loadingDescriptionText = await safeTranslate('modals.model.loading.description', {}, 'Loading model description...'); - const loadingRecipesText = await safeTranslate('modals.model.loading.recipes', {}, 'Loading recipes...'); - const loadingExamplesText = await safeTranslate('modals.model.loading.examples', {}, 'Loading examples...'); + const loadingExampleImagesText = safeTranslate('modals.model.loading.exampleImages', {}, 'Loading example images...'); + const loadingDescriptionText = safeTranslate('modals.model.loading.description', {}, 'Loading model description...'); + const loadingRecipesText = safeTranslate('modals.model.loading.recipes', {}, 'Loading recipes...'); + const loadingExamplesText = safeTranslate('modals.model.loading.examples', {}, 'Loading examples...'); const tabPanesContent = modelType === 'loras' ? `
diff --git a/static/js/components/shared/ModelTags.js b/static/js/components/shared/ModelTags.js index 499b5889..3db60515 100644 --- a/static/js/components/shared/ModelTags.js +++ b/static/js/components/shared/ModelTags.js @@ -217,10 +217,10 @@ async function saveTags() { // Exit edit mode editBtn.click(); - showToast(await safeTranslate('modelTags.messages.updated', {}, 'Tags updated successfully'), 'success'); + showToast(safeTranslate('modelTags.messages.updated', {}, 'Tags updated successfully'), 'success'); } catch (error) { console.error('Error saving tags:', error); - showToast(await safeTranslate('modelTags.messages.updateFailed', {}, 'Failed to update tags'), 'error'); + showToast(safeTranslate('modelTags.messages.updateFailed', {}, 'Failed to update tags'), 'error'); } } @@ -362,24 +362,24 @@ function addNewTag(tag) { // Validation: Check length if (tag.length > 30) { - safeTranslate('modelTags.validation.maxLength', {}, 'Tag should not exceed 30 characters') - .then(text => showToast(text, 'error')); + const text = safeTranslate('modelTags.validation.maxLength', {}, 'Tag should not exceed 30 characters'); + showToast(text, 'error'); return; } // Validation: Check total number const currentTags = tagsContainer.querySelectorAll('.metadata-item'); if (currentTags.length >= 30) { - safeTranslate('modelTags.validation.maxCount', {}, 'Maximum 30 tags allowed') - .then(text => showToast(text, 'error')); + const text = safeTranslate('modelTags.validation.maxCount', {}, 'Maximum 30 tags allowed'); + showToast(text, 'error'); return; } // Validation: Check for duplicates const existingTags = Array.from(currentTags).map(tag => tag.dataset.tag); if (existingTags.includes(tag)) { - safeTranslate('modelTags.validation.duplicate', {}, 'This tag already exists') - .then(text => showToast(text, 'error')); + const text = safeTranslate('modelTags.validation.duplicate', {}, 'This tag already exists'); + showToast(text, 'error'); return; } diff --git a/static/js/core.js b/static/js/core.js index adc5868c..6f659a71 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -29,6 +29,8 @@ export class AppCore { // Initialize i18n first window.i18n = i18n; + // Wait for i18n to be ready + await window.i18n.waitForReady(); console.log(`AppCore: Language set: ${i18n.getCurrentLocale()}`); diff --git a/static/js/utils/i18nHelpers.js b/static/js/utils/i18nHelpers.js index 141ef91f..0f717a91 100644 --- a/static/js/utils/i18nHelpers.js +++ b/static/js/utils/i18nHelpers.js @@ -23,21 +23,18 @@ export function translate(key, params = {}, fallback = null) { } /** - * Safe translation function that waits for i18n to be ready + * Safe translation function. Assumes i18n is already ready. * @param {string} key - Translation key * @param {Object} params - Parameters for interpolation * @param {string} fallback - Fallback text if translation fails - * @returns {Promise} Translated text + * @returns {string} Translated text */ -export async function safeTranslate(key, params = {}, fallback = null) { +export 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 @@ -55,11 +52,11 @@ export async function safeTranslate(key, params = {}, fallback = null) { * @param {Object} params - Parameters for interpolation * @param {string} fallback - Fallback text */ -export async function updateElementText(element, key, params = {}, fallback = null) { +export 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); + const text = safeTranslate(key, params, fallback); el.textContent = text; } @@ -71,11 +68,11 @@ export async function updateElementText(element, key, params = {}, fallback = nu * @param {Object} params - Parameters for interpolation * @param {string} fallback - Fallback text */ -export async function updateElementAttribute(element, attribute, key, params = {}, fallback = null) { +export 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); + const text = safeTranslate(key, params, fallback); el.setAttribute(attribute, text); } @@ -88,10 +85,9 @@ export async function updateElementAttribute(element, attribute, key, params = { export function createReactiveTranslation(key, params = {}, callback) { let currentLanguage = null; - const updateTranslation = async () => { + const updateTranslation = () => { if (!window.i18n) return; - await window.i18n.waitForReady(); const newLanguage = window.i18n.getCurrentLocale(); // Only update if language changed or first time @@ -121,18 +117,16 @@ export function createReactiveTranslation(key, params = {}, callback) { * @param {Array} updates - Array of update configurations * Each update should have: { element, key, type: 'text'|'attribute', attribute?, params?, fallback? } */ -export async function batchUpdateTranslations(updates) { +export 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); + updateElementText(element, key, params, fallback); } else if (type === 'attribute' && attribute) { - await updateElementAttribute(element, attribute, key, params, fallback); + updateElementAttribute(element, attribute, key, params, fallback); } } } \ No newline at end of file