From 4e2029969e782cd0a5366602cdff29149a5a972a Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sat, 25 Apr 2026 22:55:42 +0200 Subject: [PATCH 1/6] refactor(ui): remove unnecessary checks (i18n is always defined) --- static/js/app/filesView.js | 2 +- static/js/app/main.js | 23 ++---- static/js/app/navigation.js | 2 +- static/js/app/trashView.js | 21 +++-- static/js/app/ui.js | 22 ++---- static/js/app/userMenu.js | 3 +- static/js/core/languageSelector.js | 7 +- static/js/core/modal.js | 9 +-- static/js/core/notifications.js | 6 +- static/js/features/auth/auth.js | 28 ++----- static/js/features/files/contextMenus.js | 46 ++++++----- static/js/features/files/fileOperations.js | 44 +++++------ static/js/features/files/multiSelect.js | 8 +- static/js/features/library/favorites.js | 12 +-- static/js/features/library/music.js | 92 ++++++++-------------- static/js/features/library/photos.js | 6 +- static/js/features/library/recent.js | 4 +- static/js/views/admin/admin.js | 10 +-- static/js/views/profile/profile.js | 5 +- static/js/views/shared/sharedView.js | 7 +- 20 files changed, 142 insertions(+), 215 deletions(-) diff --git a/static/js/app/filesView.js b/static/js/app/filesView.js index 30c5175b..a9650ee4 100644 --- a/static/js/app/filesView.js +++ b/static/js/app/filesView.js @@ -134,7 +134,7 @@ async function loadFiles(options = { insertHistory: true }) { ui.showError(`
- ${i18n ? i18n.t('files.loading') : 'Loading files…'} + ${i18n.t('files.loading')}
`); }, 100); diff --git a/static/js/app/main.js b/static/js/app/main.js index 0170c140..fa07b9d8 100644 --- a/static/js/app/main.js +++ b/static/js/app/main.js @@ -166,9 +166,7 @@ function setActionsBarMode(mode, force = false) { elements.gridViewBtn = document.getElementById('grid-view-btn'); elements.listViewBtn = document.getElementById('list-view-btn'); - if (i18n?.translateElement) { - i18n.translateElement(elements.actionsBar); - } + i18n.translateElement(elements.actionsBar); if (mode === 'files') { setupUploadDropdown(); @@ -395,7 +393,7 @@ function initApp() { }); // Wait for translations to load before checking authentication - if (i18n?.isLoaded?.()) { + if (i18n.isLoaded()) { // Translations already loaded, proceed with authentication checkAuthentication(); } else { @@ -408,7 +406,7 @@ function initApp() { // Set a timeout as a fallback in case translations take too long setTimeout(() => { - if (!i18n?.isLoaded?.()) { + if (!i18n.isLoaded()) { console.warn('Translations loading timeout, proceeding with authentication anyway'); checkAuthentication(); } @@ -727,16 +725,11 @@ function updateStorageUsageDisplay(userData) { // Remove data-i18n attribute to prevent i18n from overwriting our value storageInfo.removeAttribute('data-i18n'); - // Use i18n if available - if (i18n?.t) { - storageInfo.textContent = i18n.t('storage.used', { - percentage: usagePercentage, - used: usedFormatted, - total: quotaFormatted - }); - } else { - storageInfo.textContent = `${usagePercentage}% used (${usedFormatted} / ${quotaFormatted})`; - } + storageInfo.textContent = i18n.t('storage.used', { + percentage: usagePercentage, + used: usedFormatted, + total: quotaFormatted + }); } console.log(`Updated storage display: ${usagePercentage}% (${usedFormatted} / ${quotaFormatted})`); diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js index 018afd9e..bc1451d5 100644 --- a/static/js/app/navigation.js +++ b/static/js/app/navigation.js @@ -152,7 +152,7 @@ function setCurrentSection(section) { // Update page title const titleKey = `nav.${section}`; const defaultTitle = section.charAt(0).toUpperCase() + section.slice(1); - appElements.pageTitle.textContent = i18n ? i18n.t(titleKey) : defaultTitle; + appElements.pageTitle.textContent = i18n.t(titleKey); appElements.pageTitle.setAttribute('data-i18n', titleKey); // Hide sharedView when switching to any other section diff --git a/static/js/app/trashView.js b/static/js/app/trashView.js index 102d8e56..fc89defc 100644 --- a/static/js/app/trashView.js +++ b/static/js/app/trashView.js @@ -15,14 +15,13 @@ async function loadTrashItems() { try { if (multiSelect) multiSelect.clear(); ui.resetFilesList(); // ensure also list visible & error hidden - const _tt = i18n?.t ? i18n.t : (k) => k.split('.').pop(); elements.filesList.innerHTML = `
-
${_tt('files.name')}
-
${_tt('files.type')}
-
${_tt('trash.original_location')}
-
${_tt('trash.deleted_date')}
-
${_tt('trash.actions')}
+
${i18n.t('files.name')}
+
${i18n.t('files.type')}
+
${i18n.t('trash.original_location')}
+
${i18n.t('trash.deleted_date')}
+
${i18n.t('trash.actions')}
`; @@ -33,7 +32,7 @@ async function loadTrashItems() { if (trashItems.length === 0) { ui.showError(` -

${i18n ? i18n.t('trash.empty_state') : 'The trash is empty'}

+

${i18n.t('trash.empty_state')}

`); return; } @@ -58,12 +57,12 @@ function addTrashItemToView(item) { let iconSpecialClass = ''; if (!isFile) { iconClass = item.icon_class || 'fas fa-folder'; - typeLabel = i18n ? i18n.t('files.file_types.folder') : 'Folder'; + typeLabel = i18n.t('files.file_types.folder'); } else { iconClass = item.icon_class || (ui?.getIconClass ? ui.getIconClass(item.name) : 'fas fa-file'); iconSpecialClass = ui?.getIconSpecialClass ? ui.getIconSpecialClass(item.name) : ''; const cat = item.category || ''; - typeLabel = cat ? (i18n ? i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat : cat) : i18n ? i18n.t('files.file_types.document') : 'Document'; + typeLabel = cat ? (i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat) : i18n.t('files.file_types.document'); } const isFolder = !isFile; @@ -86,10 +85,10 @@ function addTrashItemToView(item) {
${escapeHtml(item.original_path || '--')}
${escapeHtml(formattedDate)}
- -
diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 4deac6bb..4c89c5a2 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -542,17 +542,11 @@ const ui = { breadcrumb.innerHTML = ''; const path = app.breadcrumbPath; // [{id, name}, ...] - // Helper function to safely get translation text - const getTranslatedText = (key, defaultValue) => { - if (!i18n?.t) return defaultValue; - return i18n.t(key); - }; - // -- Home icon (always present, clickable to go to root) -- const homeIcon = document.createElement('span'); homeIcon.className = 'breadcrumb-item breadcrumb-home'; homeIcon.innerHTML = ''; - homeIcon.title = getTranslatedText('breadcrumb.home', 'Home'); + homeIcon.title = i18n.t('breadcrumb.home'); // Home is always clickable if we have a home folder if (app.userHomeFolderId) { @@ -1265,7 +1259,7 @@ const ui = {
-
${i18n ? i18n.t('files.file_types.folder') : 'Folder'}
+
${i18n.t('files.file_types.folder')}
--
${formattedDate}
@@ -1288,7 +1282,7 @@ const ui = { const iconClass = file.icon_class || this.getIconClass(file.name); const iconSpecialClass = file.icon_special_class || this.getIconSpecialClass(file.name); const cat = file.category || ''; - const typeLabel = cat ? (i18n ? i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat : cat) : i18n ? i18n.t('files.file_types.document') : 'Document'; + const typeLabel = cat ? (i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat) : i18n.t('files.file_types.document'); const fileSize = file.size_formatted || formatFileSize(file.size); const formattedDate = formatDateTime(file.modified_at); const isFav = favorites?.isFavorite(file.id, 'file'); @@ -1352,7 +1346,7 @@ const ui = {
`; - if (i18n?.translateElement) i18n.translateElement(filesList); + i18n.translateElement(filesList); filesList.classList.remove('hidden'); filesContainerError?.classList.add('hidden'); @@ -1376,7 +1370,7 @@ const ui = { const filesList = document.getElementById('files-list'); if (filesContainerError) filesContainerError.innerHTML = content; - if (i18n?.translateElement) i18n.translateElement(filesContainerError); + i18n.translateElement(filesContainerError); filesContainerError?.classList.remove('hidden'); filesList?.classList.add('hidden'); @@ -1642,9 +1636,9 @@ if (document.readyState === 'loading') { * @returns {Promise} true if confirmed, false if cancelled */ function showConfirmDialog({ title, message, confirmText, cancelText, danger = true } = {}) { - const ct = confirmText || (i18n ? i18n.t('actions.delete') : 'Delete'); - const cc = cancelText || (i18n ? i18n.t('actions.cancel') : 'Cancel'); - const t = title || (i18n ? i18n.t('dialogs.confirm_title') : 'Confirm action'); + const ct = confirmText || i18n.t('actions.delete'); + const cc = cancelText || i18n.t('actions.cancel'); + const t = title || i18n.t('dialogs.confirm_title'); return new Promise((resolve) => { // Remove any previous confirm dialog diff --git a/static/js/app/userMenu.js b/static/js/app/userMenu.js index 80432567..c8cf7010 100644 --- a/static/js/app/userMenu.js +++ b/static/js/app/userMenu.js @@ -209,9 +209,10 @@ function showUserProfileModal() { const usedBytes = userData.storage_used_bytes || 0; const quotaBytes = userData.storage_quota_bytes == null ? 10 * 1024 * 1024 * 1024 : userData.storage_quota_bytes; const percentage = quotaBytes > 0 ? Math.min(Math.round((usedBytes / quotaBytes) * 100), 100) : 0; + // FIXME: use classes const barColor = percentage > 90 ? '#ef4444' : percentage > 70 ? '#f59e0b' : '#22c55e'; - const t = (key, fallback) => (i18n?.t ? i18n.t(key) || fallback : fallback); + const t = (key, fallback) => i18n.t(key) || fallback; const existing = document.getElementById('profile-modal-overlay'); if (existing) existing.remove(); diff --git a/static/js/core/languageSelector.js b/static/js/core/languageSelector.js index df012709..6468f545 100644 --- a/static/js/core/languageSelector.js +++ b/static/js/core/languageSelector.js @@ -66,7 +66,7 @@ function createLanguageSelector(containerId = 'language-selector') { // Get current language const languages = getAvailableLanguages(); - const currentLocale = i18n ? i18n.getCurrentLocale() : 'en'; + const currentLocale = i18n.getCurrentLocale(); const currentLang = languages.find((l) => l.code === currentLocale) || languages[0]; // Set initial HTML attributes @@ -185,10 +185,7 @@ function closeDropdown(container) { * Select a language */ async function selectLanguage(langCode, container) { - // Update i18n if available - if (i18n) { - await i18n.setLocale(langCode); - } + await i18n.setLocale(langCode); // Update HTML lang attribute and dir for RTL languages updateHtmlAttributes(langCode); diff --git a/static/js/core/modal.js b/static/js/core/modal.js index 6735c2ae..22f28522 100644 --- a/static/js/core/modal.js +++ b/static/js/core/modal.js @@ -108,16 +108,15 @@ const Modal = { this.input.placeholder = placeholder; this.input.value = value; - // Set button text (use i18n if available) if (confirmText) { this.confirmBtn.textContent = confirmText; - } else if (i18n) { + } else { this.confirmBtn.textContent = i18n.t('actions.confirm'); } if (cancelText) { this.cancelBtn.textContent = cancelText; - } else if (i18n) { + } else { this.cancelBtn.textContent = i18n.t('actions.cancel'); } @@ -138,7 +137,7 @@ const Modal = { * @returns {Promise} */ promptNewFolder() { - const t = i18n ? i18n.t.bind(i18n) : (k) => k; + const t = i18n.t.bind(i18n); return this.prompt({ title: t('dialogs.new_folder_title') || 'New folder', @@ -156,7 +155,7 @@ const Modal = { * @returns {Promise} */ promptRename(currentName, isFolder = false) { - const t = i18n ? i18n.t.bind(i18n) : (k) => k; + const t = i18n.t.bind(i18n); // For files, we want to select only the name part (without extension) this._selectNameOnly = !isFolder; diff --git a/static/js/core/notifications.js b/static/js/core/notifications.js index 59878ee6..4986653e 100644 --- a/static/js/core/notifications.js +++ b/static/js/core/notifications.js @@ -149,7 +149,7 @@ const notifications = (() => { item.className = 'notif-item'; item.id = batchId; - const t = i18n?.t || ((k) => k); + const t = i18n.t.bind(i18n); const uploadingText = folderName ? `📁 ${t('upload.uploading')} ${_esc(folderName)}…` : t('upload.uploading'); const filesLabel = t('upload.files'); @@ -254,7 +254,7 @@ const notifications = (() => { const pctEl = $(`${batchId}-pct`); const statsEl = $(`${batchId}-stats`); - const t = i18n?.t || ((k) => k); + const t = i18n.t.bind(i18n); const filesLabel = t('upload.files'); if (fillEl) fillEl.style.width = `${pctVal}%`; @@ -282,7 +282,7 @@ const notifications = (() => { const curEl = $(`${batchId}-current`); if (curEl) curEl.textContent = ''; - const t = i18n?.t || ((k) => k); + const t = i18n.t.bind(i18n); const completeText = t('upload.complete', { count: successCount, total: totalFiles diff --git a/static/js/features/auth/auth.js b/static/js/features/auth/auth.js index 30c424f8..122ace65 100644 --- a/static/js/features/auth/auth.js +++ b/static/js/features/auth/auth.js @@ -511,10 +511,7 @@ function initLanguageSelector() { localStorage.setItem(LOCALE_KEY, selectedLanguage); localStorage.setItem(FIRST_RUN_KEY, 'true'); - // Update i18n if available - if (i18n?.setLocale) { - await i18n.setLocale(selectedLanguage); - } + await i18n.setLocale(selectedLanguage); // Hide language panel hidePanel(languagePanel); @@ -635,7 +632,7 @@ async function configureOidcLoginUI() { // Update button text with provider name const btnTextEl = oidcBtn.querySelector('span'); if (btnTextEl && oidcInfo.provider_name) { - const template = i18n?.t ? i18n.t('auth.sso_login_provider') : 'Sign in with {{provider}}'; + const template = i18n.t('auth.sso_login_provider'); btnTextEl.textContent = template.replace('{{provider}}', oidcInfo.provider_name); } @@ -942,8 +939,7 @@ if (isLoginPage && registerForm) { // Validate passwords match if (password !== confirmPassword) { - const errorMsg = i18n ? i18n.t('auth.passwords_mismatch') : 'Passwords do not match'; - registerError.textContent = errorMsg; + registerError.textContent = i18n.t('auth.passwords_mismatch'); registerError.style.display = 'block'; return; } @@ -951,9 +947,7 @@ if (isLoginPage && registerForm) { try { await register(username, email, password); - // Show success message - const successMsg = i18n ? i18n.t('auth.account_success') : 'Account created successfully! You can now log in.'; - registerSuccess.textContent = successMsg; + registerSuccess.textContent = i18n.t('auth.account_success'); registerSuccess.style.display = 'block'; // Clear form @@ -965,8 +959,7 @@ if (isLoginPage && registerForm) { hidePanel(registerPanel); }, 2000); } catch (error) { - const errorMsg = i18n ? i18n.t('auth.admin_create_error') : 'Error registering account'; - registerError.textContent = error.message || errorMsg; + registerError.textContent = error.message || i18n.t('auth.admin_create_error'); registerError.style.display = 'block'; } }); @@ -988,8 +981,7 @@ if (isLoginPage && adminSetupForm) { // Validate passwords match if (password !== confirmPassword) { - const errorMsg = i18n ? i18n.t('auth.passwords_mismatch') : 'Passwords do not match'; - adminSetupError.textContent = errorMsg; + adminSetupError.textContent = i18n.t('auth.passwords_mismatch'); adminSetupError.style.display = 'block'; return; } @@ -1012,11 +1004,8 @@ if (isLoginPage && adminSetupForm) { } await response.json(); - // Show success message in the GUI instead of alert - const successMsg = i18n ? i18n.t('auth.admin_success') : 'Admin account created successfully! You can now log in.'; - if (adminSetupSuccess) { - adminSetupSuccess.textContent = successMsg; + adminSetupSuccess.textContent = i18n.t('auth.admin_success'); adminSetupSuccess.style.display = 'block'; } @@ -1027,8 +1016,7 @@ if (isLoginPage && adminSetupForm) { if (adminSetupSuccess) adminSetupSuccess.style.display = 'none'; }, 2000); } catch (error) { - const errorMsg = i18n ? i18n.t('auth.admin_create_error') : 'Error creating admin account'; - adminSetupError.textContent = error.message || errorMsg; + adminSetupError.textContent = error.message || i18n.t('auth.admin_create_error'); adminSetupError.style.display = 'block'; } }); diff --git a/static/js/features/files/contextMenus.js b/static/js/features/files/contextMenus.js index d95218b6..d0eae153 100644 --- a/static/js/features/files/contextMenus.js +++ b/static/js/features/files/contextMenus.js @@ -27,7 +27,7 @@ const contextMenus = { if (!option) return; const label = option.querySelector('span'); if (!label) return; - label.textContent = i18n ? i18n.t(isFavorite ? 'actions.unfavorite' : 'actions.favorite') : isFavorite ? 'Remove from favorites' : 'Add to favorites'; + label.textContent = i18n.t(isFavorite ? 'actions.unfavorite' : 'actions.favorite'); }, /** @@ -382,7 +382,7 @@ const contextMenus = { renameInput.value = folder.name; // Update header text const headerSpan = renameDialog.querySelector('.rename-dialog-header span'); - if (headerSpan) headerSpan.textContent = i18n ? i18n.t('dialogs.rename_folder') : 'Rename folder'; + if (headerSpan) headerSpan.textContent = i18n.t('dialogs.rename_folder'); renameDialog?.classList.remove('hidden'); renameInput.focus(); renameInput.select(); @@ -402,7 +402,7 @@ const contextMenus = { renameInput.value = file.name; // Update header text const headerSpan = renameDialog.querySelector('.rename-dialog-header span'); - if (headerSpan) headerSpan.textContent = i18n ? i18n.t('dialogs.rename_file') : 'Rename file'; + if (headerSpan) headerSpan.textContent = i18n.t('dialogs.rename_file'); renameDialog?.classList.remove('hidden'); renameInput.focus(); renameInput.select(); @@ -471,7 +471,7 @@ const contextMenus = { // Update dialog title (preserve icon) const dialogHeader = document.getElementById('move-file-dialog').querySelector('.rename-dialog-header'); - const titleText = mode === 'file' ? (i18n ? i18n.t('dialogs.move_file') : 'Move file') : i18n ? i18n.t('dialogs.move_folder') : 'Move folder'; + const titleText = mode === 'file' ? i18n.t('dialogs.move_file') : i18n.t('dialogs.move_folder'); dialogHeader.innerHTML = ` ${titleText}`; // Load folders for the starting location @@ -496,7 +496,7 @@ const contextMenus = { async renameItem() { const newName = document.getElementById('rename-input').value.trim(); if (!newName) { - alert(i18n ? i18n.t('errors.empty_name') : 'Name cannot be empty'); + alert(i18n.t('errors.empty_name')); return; } @@ -587,7 +587,7 @@ const contextMenus = { currentFolderOption.className = 'folder-select-item folder-select-current'; currentFolderOption.innerHTML = ` - ${i18n ? i18n.t('dialogs.select_this_folder') : 'Select this folder'} + ${i18n.t('dialogs.select_this_folder')} `; currentFolderOption.addEventListener('click', () => { document.querySelectorAll('.folder-select-item').forEach((item) => { @@ -606,7 +606,7 @@ const contextMenus = { parentOption.className = 'folder-select-item folder-navigate-up'; parentOption.innerHTML = ` - ${i18n ? i18n.t('dialogs.go_to_parent') : '.. (parent folder)'} + ${i18n.t('dialogs.go_to_parent')} `; parentOption.addEventListener('click', () => { // Navigate to parent folder @@ -664,7 +664,7 @@ const contextMenus = { homeOption.className = 'folder-select-item folder-select-current'; homeOption.innerHTML = ` - ${i18n ? i18n.t('dialogs.move_to_home') : 'Move to Home folder'} + ${i18n.t('dialogs.move_to_home')} `; homeOption.addEventListener('click', () => { document.querySelectorAll('.folder-select-item').forEach((item) => { @@ -678,7 +678,7 @@ const contextMenus = { // Inside a subfolder with no children - show empty message const emptyMsg = document.createElement('div'); emptyMsg.className = 'folder-select-empty'; - emptyMsg.innerHTML = ` ${i18n ? i18n.t('dialogs.no_subfolders') : 'No subfolders to navigate'}`; + emptyMsg.innerHTML = ` ${i18n.t('dialogs.no_subfolders')}`; folderSelectContainer.appendChild(emptyMsg); } @@ -686,9 +686,7 @@ const contextMenus = { app.selectedTargetFolderId = parentFolderId || ''; // Translate new elements - if (i18n?.translateElement) { - i18n.translateElement(folderSelectContainer); - } + i18n.translateElement(folderSelectContainer); } catch (error) { console.error('Error loading folders:', error); } @@ -799,7 +797,7 @@ const contextMenus = { if (dialogHeader) { const headerSpan = dialogHeader.querySelector('span'); const titleText = - itemType === 'file' ? (i18n ? i18n.t('dialogs.share_file') : 'Share file') : i18n ? i18n.t('dialogs.share_folder') : 'Share folder'; + itemType === 'file' ? i18n.t('dialogs.share_file') : i18n.t('dialogs.share_folder'); if (headerSpan) { headerSpan.textContent = titleText; } else { @@ -900,9 +898,9 @@ const contextMenus = { const shareId = btn.getAttribute('data-share-id'); showConfirmDialog({ - title: i18n ? i18n.t('dialogs.confirm_delete_share') : 'Delete link', - message: i18n ? i18n.t('dialogs.confirm_delete_share_msg') : 'Are you sure you want to delete this shared link?', - confirmText: i18n ? i18n.t('actions.delete') : 'Delete' + title: i18n.t('dialogs.confirm_delete_share'), + message: i18n.t('dialogs.confirm_delete_share_msg'), + confirmText: i18n.t('actions.delete') }).then(async (confirmed) => { if (confirmed) { await fileSharing.removeSharedLink(shareId); @@ -998,8 +996,8 @@ const contextMenus = { // Show success message ui.showNotification( - i18n ? i18n.t('notifications.link_created') : 'Link created', - i18n ? i18n.t('notifications.share_success') : 'Shared link created successfully' + i18n.t('notifications.link_created'), + i18n.t('notifications.share_success') ); } catch (error) { console.error('Error creating shared link:', error); @@ -1088,7 +1086,7 @@ const contextMenus = { // Update files info if (filesInfo) { - filesInfo.innerHTML = `${i18n ? i18n.t('music.selected_files', 'Selected:') : 'Selected:'} ${file.name}`; + filesInfo.innerHTML = `${i18n.t('music.selected_files')} ${file.name}`; } // Reset selection @@ -1112,12 +1110,12 @@ const contextMenus = { this._renderPlaylistSelect(container, playlists); } catch (err) { console.error('Error loading playlists:', err); - container.innerHTML = `
${i18n ? i18n.t('music.load_error', 'Error loading playlists') : 'Error loading playlists'}
`; + container.innerHTML = `
${i18n.t('music.load_error')}
`; } }, _renderPlaylistSelect(container, playlists) { - const t = (key, fallback) => (i18n ? i18n.t(key, fallback) : fallback); + const t = i18n.t.bind(i18n); container.innerHTML = ''; @@ -1177,8 +1175,8 @@ const contextMenus = { await resp.json(); ui.showNotification( - i18n ? i18n.t('music.added', 'Added!') : 'Added!', - `${files.length} ${files.length === 1 ? 'track' : 'tracks'} ${i18n ? i18n.t('music.added_to_playlist', 'added to playlist') : 'added to playlist'}` + i18n.t('music.added'), + `${files.length} ${files.length === 1 ? 'track' : 'tracks'} ${i18n.t('music.added_to_playlist')}` ); this.closePlaylistDialog(); @@ -1189,7 +1187,7 @@ const contextMenus = { } } catch (err) { console.error('Error adding to playlist:', err); - ui.showNotification(i18n ? i18n.t('music.error', 'Error') : 'Error', err.message || i18n.t('music.add_error', 'Could not add tracks to playlist')); + ui.showNotification(i18n.t('music.error'), err.message || i18n.t('music.add_error')); if (addBtn) addBtn.disabled = false; } }, diff --git a/static/js/features/files/fileOperations.js b/static/js/features/files/fileOperations.js index 9f85102a..9c8530d9 100644 --- a/static/js/features/files/fileOperations.js +++ b/static/js/features/files/fileOperations.js @@ -30,12 +30,12 @@ const fileOps = { /** Start a new upload batch in the notification bell */ _initUploadToast(totalFiles, folderName) { - this._currentBatchId = notifications ? notifications.addUploadBatch(totalFiles, folderName) : null; + this._currentBatchId = notifications.addUploadBatch(totalFiles, folderName); }, /** Finalise the batch in the notification bell */ _finishUploadToast(successCount, totalFiles) { - if (notifications && this._currentBatchId) { + if (this._currentBatchId) { notifications.finishBatch(this._currentBatchId, successCount, totalFiles); } }, @@ -361,7 +361,7 @@ const fileOps = { progressBar.style.width = `${(uploadedCount / totalFiles) * 100}%`; } // Notify bell of per-file completion - if (notifications && batchId) { + if (batchId) { try { notifications.fileCompleted(batchId, result.ok); } catch (e) { @@ -383,7 +383,7 @@ const fileOps = { }); } if (result.isQuotaError) { - const msg = result.errorMsg || i18n?.t('storage_quota_exceeded') || 'Storage quota exceeded'; + const msg = result.errorMsg || i18n.t('storage_quota_exceeded'); if (notifications) { notifications.addNotification({ icon: 'fa-exclamation-triangle', @@ -591,7 +591,7 @@ const fileOps = { console.warn(`[SKIP] #${idx} ${rel} — cannot read 0-byte file (FIFO/pipe?), skipping`); uploadedCount++; successCount++; - if (notifications && batchId) { + if (batchId) { try { notifications.fileCompleted(batchId, true); } catch (_) {} @@ -623,7 +623,7 @@ const fileOps = { uploadedCount++; - if (notifications && batchId) { + if (batchId) { try { notifications.fileCompleted(batchId, result.ok); } catch (_) {} @@ -995,8 +995,8 @@ const fileOps = { if (response.ok) { ui.showNotification( - i18n ? i18n.t('notifications.file_renamed') : 'File renamed', - i18n ? i18n.t('notifications.file_renamed_to', { name: newName }) : `File renamed to "${newName}"` + i18n.t('notifications.file_renamed'), + i18n.t('notifications.file_renamed_to', { name: newName }) ); return true; } else { @@ -1075,9 +1075,9 @@ const fileOps = { */ async deleteFile(fileId, fileName) { const confirmed = await showConfirmDialog({ - title: i18n ? i18n.t('dialogs.confirm_delete') : 'Move to trash', - message: i18n ? i18n.t('dialogs.confirm_delete_file', { name: fileName }) : `Are you sure you want to move the file "${fileName}" to trash?`, - confirmText: i18n ? i18n.t('actions.delete') : 'Delete' + title: i18n.t('dialogs.confirm_delete'), + message: i18n.t('dialogs.confirm_delete_file', { name: fileName }), + confirmText: i18n.t('actions.delete') }); if (!confirmed) return false; @@ -1123,11 +1123,9 @@ const fileOps = { */ async deleteFolder(folderId, folderName) { const confirmed = await showConfirmDialog({ - title: i18n ? i18n.t('dialogs.confirm_delete') : 'Move to trash', - message: i18n - ? i18n.t('dialogs.confirm_delete_folder', { name: folderName }) - : `Are you sure you want to move the folder "${folderName}" and all its contents to trash?`, - confirmText: i18n ? i18n.t('actions.delete') : 'Delete' + title: i18n.t('dialogs.confirm_delete'), + message: i18n.t('dialogs.confirm_delete_folder', { name: folderName }), + confirmText: i18n.t('actions.delete') }); if (!confirmed) return false; @@ -1234,11 +1232,9 @@ const fileOps = { */ async deletePermanently(trashId) { const confirmed = await showConfirmDialog({ - title: i18n ? i18n.t('dialogs.confirm_permanent_delete') : 'Delete permanently', - message: i18n - ? i18n.t('dialogs.confirm_permanent_delete_msg') - : 'Are you sure you want to permanently delete this item? This action cannot be undone.', - confirmText: i18n ? i18n.t('actions.delete_permanently') : 'Delete permanently' + title: i18n.t('dialogs.confirm_permanent_delete'), + message: i18n.t('dialogs.confirm_permanent_delete_msg'), + confirmText: i18n.t('actions.delete_permanently') }); if (!confirmed) return false; @@ -1268,9 +1264,9 @@ const fileOps = { */ async emptyTrash() { const confirmed = await showConfirmDialog({ - title: i18n ? i18n.t('dialogs.confirm_empty_trash') : 'Empty trash', - message: i18n ? i18n.t('trash.empty_confirm') : 'Are you sure you want to empty the trash? This action will permanently delete all items.', - confirmText: i18n ? i18n.t('actions.empty_trash') : 'Empty trash' + title: i18n.t('dialogs.confirm_empty_trash'), + message: i18n.t('trash.empty_confirm'), + confirmText: i18n.t('actions.empty_trash') }); if (!confirmed) return false; diff --git a/static/js/features/files/multiSelect.js b/static/js/features/files/multiSelect.js index a3a4a56a..f77194cd 100644 --- a/static/js/features/files/multiSelect.js +++ b/static/js/features/files/multiSelect.js @@ -50,12 +50,8 @@ const multiSelect = { // ── Helpers for i18n ──────────────────────────────────── _t(key, vars) { - if (i18n && typeof i18n.t === 'function') { - const val = i18n.t(key, vars); - // If i18n returned the key itself, it's missing → fall back - if (val && val !== key) return val; - } - return null; + const val = i18n.t(key, vars); + return val !== key ? val : null; }, // ── Selection state management ────────────────────────── diff --git a/static/js/features/library/favorites.js b/static/js/features/library/favorites.js index 43fe53be..7ce2a490 100644 --- a/static/js/features/library/favorites.js +++ b/static/js/features/library/favorites.js @@ -111,8 +111,8 @@ const favorites = { // Notify user if (ui?.showNotification) { ui.showNotification( - i18n ? i18n.t('favorites.added_title') : 'Added to favorites', - `"${name}" ${i18n ? i18n.t('favorites.added_msg') : 'added to favorites'}` + i18n.t('favorites.added_title'), + `"${name}" ${i18n.t('favorites.added_msg')}` ); } @@ -146,8 +146,8 @@ const favorites = { if (ui?.showNotification) { ui.showNotification( - i18n ? i18n.t('favorites.removed_title') : 'Removed from favorites', - `"${itemName}" ${i18n ? i18n.t('favorites.removed_msg') : 'removed from favorites'}` + i18n.t('favorites.removed_title'), + `"${itemName}" ${i18n.t('favorites.removed_msg')}` ); } @@ -182,8 +182,8 @@ const favorites = { if (this._cache.size === 0) { ui.showError(` -

${i18n ? i18n.t('favorites.empty_state') : 'No favorite items'}

-

${i18n ? i18n.t('favorites.empty_hint') : 'To mark as favorite, right-click on any file or folder'}

+

${i18n.t('favorites.empty_state')}

+

${i18n.t('favorites.empty_hint')}

`); return; } diff --git a/static/js/features/library/music.js b/static/js/features/library/music.js index 456ec605..f002efac 100644 --- a/static/js/features/library/music.js +++ b/static/js/features/library/music.js @@ -186,9 +186,7 @@ const musicView = { const listEl = document.getElementById('music-playlist-list'); if (!listEl) return; - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); if (this.playlists.length === 0) { listEl.innerHTML = ` @@ -291,9 +289,7 @@ const musicView = { if (detailEl) detailEl.classList.remove('hidden'); if (nameEl) nameEl.textContent = playlist.name; if (metaEl) { - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); metaEl.textContent = `${playlist.track_count || 0} ${t('music.tracks', 'tracks')}`; } @@ -314,7 +310,7 @@ const musicView = { } const togglePublicBtn = document.getElementById('music-toggle-public-btn'); if (togglePublicBtn) { - const t2 = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t2 = (key) => i18n.t(key); togglePublicBtn.title = playlist.is_public ? t2('music.make_private', 'Make private') : t2('music.make_public', 'Make public'); togglePublicBtn.classList.toggle('active', playlist.is_public); } @@ -352,9 +348,7 @@ const musicView = { const trackListEl = document.getElementById('music-track-list'); if (!trackListEl) return; - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); if (this.currentTracks.length === 0) { trackListEl.innerHTML = ` @@ -467,9 +461,7 @@ const musicView = { _playTrack(idx) { if (!this.currentTracks[idx]) return; - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); musicPlayer.setQueue(this.currentTracks, this.currentPlaylist?.name || t('music.playlists', 'Playlist')); musicPlayer.playTrack(idx); }, @@ -487,18 +479,14 @@ const musicView = { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); musicPlayer.setQueue(shuffled, this.currentPlaylist?.name || t('music.shuffle', 'Shuffle')); musicPlayer.playTrack(0); } }, async _showCreatePlaylistDialog() { - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); const name = await Modal.prompt({ title: t('music.create_playlist', 'Create Playlist'), @@ -513,9 +501,7 @@ const musicView = { }, async _createPlaylist(name) { - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); const createBtn = document.getElementById('music-create-playlist-btn'); if (createBtn) createBtn.disabled = true; try { @@ -556,9 +542,7 @@ const musicView = { }, async _deletePlaylist() { - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); if (!this.currentPlaylist) return; @@ -644,9 +628,7 @@ const musicView = { async _showEditPlaylistDialog() { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); const newName = await Modal.prompt({ title: t('music.edit', 'Edit'), @@ -690,9 +672,7 @@ const musicView = { async _showSharePlaylistDialog() { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); const userId = await Modal.prompt({ title: t('music.share', 'Share'), @@ -736,9 +716,7 @@ const musicView = { async _showAddTracksDialog() { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => { - return i18n?.t ? i18n.t(key) : fallback || key; - }; + const t = (key) => i18n.t(key); // ── Build modal overlay ── const overlay = document.createElement('div'); @@ -900,7 +878,7 @@ const musicView = { async _removeTrackFromPlaylist(_trackId, fileId) { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); try { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, { @@ -942,7 +920,7 @@ const musicView = { async _reorderTrack(fromIdx, toIdx) { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); const tracks = [...this.currentTracks]; const [moved] = tracks.splice(fromIdx, 1); @@ -975,7 +953,7 @@ const musicView = { async _showManageSharesDialog() { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); const existing = document.getElementById('music-shares-dialog'); if (existing) existing.remove(); @@ -1052,7 +1030,7 @@ const musicView = { async _loadSharesList(dialog) { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); const body = dialog.querySelector('.music-shares-body'); if (!body) return; @@ -1097,7 +1075,7 @@ const musicView = { async _removeShare(userId, dialog) { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); try { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, { @@ -1121,7 +1099,7 @@ const musicView = { async _togglePublic() { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); const newValue = !this.currentPlaylist.is_public; try { @@ -1170,7 +1148,7 @@ const musicView = { async _showCoverPicker() { if (!this.currentPlaylist) return; - const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); + const t = (key) => i18n.t(key); const input = document.createElement('input'); input.type = 'file'; @@ -1289,25 +1267,25 @@ const musicPlayer = {
- ${i18n?.t('music.not_playing', 'Not playing') || 'Not playing'} + ${i18n.t('music.not_playing')}
- - - - -
@@ -1321,22 +1299,22 @@ const musicPlayer = {
- -
-
`; @@ -118,8 +115,8 @@ const musicView = {
-

${t('music.playlists', 'Playlists')}

-
@@ -128,44 +125,44 @@ const musicView = {
-

${t('music.select_playlist', 'Select a playlist')}

-

${t('music.select_hint', 'Choose a playlist from the sidebar or create a new one')}

+

${i18n.t('music.select_playlist')}

+

${i18n.t('music.select_hint')}

` @@ -461,8 +454,7 @@ const musicView = { _playTrack(idx) { if (!this.currentTracks[idx]) return; - const t = (key) => i18n.t(key); - musicPlayer.setQueue(this.currentTracks, this.currentPlaylist?.name || t('music.playlists', 'Playlist')); + musicPlayer.setQueue(this.currentTracks, this.currentPlaylist?.name || i18n.t('music.playlists')); musicPlayer.playTrack(idx); }, @@ -479,21 +471,19 @@ const musicView = { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } - const t = (key) => i18n.t(key); - musicPlayer.setQueue(shuffled, this.currentPlaylist?.name || t('music.shuffle', 'Shuffle')); + musicPlayer.setQueue(shuffled, this.currentPlaylist?.name || i18n.t('music.shuffle')); musicPlayer.playTrack(0); } }, async _showCreatePlaylistDialog() { - const t = (key) => i18n.t(key); const name = await Modal.prompt({ - title: t('music.create_playlist', 'Create Playlist'), - label: t('music.playlist_name', 'Playlist name'), - placeholder: t('music.playlist_name', 'Playlist name'), + title: i18n.t('music.create_playlist'), + label: i18n.t('music.playlist_name'), + placeholder: i18n.t('music.playlist_name'), icon: 'fa-music', - confirmText: t('music.create', 'Create') + confirmText: i18n.t('music.create') }); if (!name?.trim()) return; @@ -501,7 +491,6 @@ const musicView = { }, async _createPlaylist(name) { - const t = (key) => i18n.t(key); const createBtn = document.getElementById('music-create-playlist-btn'); if (createBtn) createBtn.disabled = true; try { @@ -522,7 +511,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.create_playlist', 'Create Playlist'), + title: i18n.t('music.create_playlist'), text: name }); } @@ -532,7 +521,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -542,18 +531,17 @@ const musicView = { }, async _deletePlaylist() { - const t = (key) => i18n.t(key); if (!this.currentPlaylist) return; const confirmed = await new Promise((resolve) => { Modal.prompt({ - title: t('music.delete', 'Delete'), - label: t('music.confirm_delete', 'Delete this playlist?'), + title: i18n.t('music.delete'), + label: i18n.t('music.confirm_delete'), placeholder: '', value: this.currentPlaylist.name, icon: 'fa-trash', - confirmText: t('music.delete', 'Delete') + confirmText: i18n.t('music.delete') }).then((val) => resolve(val !== null)); }); if (!confirmed) return; @@ -575,7 +563,7 @@ const musicView = { this.currentTracks = []; this._renderPlaylists(); if (notifications) { - notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: t('music.delete', 'Delete'), text: deletedName }); + notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', title: i18n.t('music.delete'), text: deletedName }); } } catch (err) { console.error('Delete playlist error:', err); @@ -583,7 +571,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -628,15 +616,14 @@ const musicView = { async _showEditPlaylistDialog() { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const newName = await Modal.prompt({ - title: t('music.edit', 'Edit'), - label: t('music.playlist_name', 'Playlist name'), - placeholder: t('music.playlist_name', 'Playlist name'), + title: i18n.t('music.edit'), + label: i18n.t('music.playlist_name'), + placeholder: i18n.t('music.playlist_name'), value: this.currentPlaylist.name, icon: 'fa-pen', - confirmText: t('actions.confirm', 'Save') + confirmText: i18n.t('actions.confirm') }); if (!newName?.trim() || newName.trim() === this.currentPlaylist.name) return; @@ -663,7 +650,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -672,14 +659,13 @@ const musicView = { async _showSharePlaylistDialog() { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const userId = await Modal.prompt({ - title: t('music.share', 'Share'), - label: t('music.share_with_user', 'User ID or email'), - placeholder: t('music.share_with_user', 'User ID or email'), + title: i18n.t('music.share'), + label: i18n.t('music.share_with_user'), + placeholder: i18n.t('music.share_with_user'), icon: 'fa-share-alt', - confirmText: t('music.share', 'Share') + confirmText: i18n.t('music.share') }); if (!userId?.trim()) return; @@ -697,8 +683,8 @@ const musicView = { notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.share', 'Share'), - text: t('music.added', 'Added!') + title: i18n.t('music.share'), + text: i18n.t('music.added') }); } } catch (err) { @@ -707,7 +693,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -716,7 +702,6 @@ const musicView = { async _showAddTracksDialog() { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); // ── Build modal overlay ── const overlay = document.createElement('div'); @@ -724,23 +709,23 @@ const musicView = { overlay.innerHTML = `
-

${t('music.add_tracks', 'Add Tracks')}

- +

${i18n.t('music.add_tracks')}

+
-
${t('music.loading', 'Loading…')}
+
${i18n.t('music.loading')}
@@ -770,7 +755,7 @@ const musicView = { const AUDIO_EXTENSIONS = 'mp3,ogg,flac,wav,aac,m4a,wma,opus,webm'; const fetchAudioFiles = async (query = '') => { - listEl.innerHTML = `
${t('music.loading', 'Loading…')}
`; + listEl.innerHTML = `
${i18n.t('music.loading')}
`; try { const params = new URLSearchParams({ type_filter: AUDIO_EXTENSIONS, limit: '200', recursive: 'true' }); if (query.trim()) params.set('query', query.trim()); @@ -780,13 +765,13 @@ const musicView = { renderFiles(data.files || []); } catch (err) { console.error('Audio search error:', err); - listEl.innerHTML = `
${t('music.search_error', 'Could not load audio files')}
`; + listEl.innerHTML = `
${i18n.t('music.search_error')}
`; } }; const renderFiles = (files) => { if (files.length === 0) { - listEl.innerHTML = `
${t('music.no_audio_files', 'No audio files found')}
`; + listEl.innerHTML = `
${i18n.t('music.no_audio_files')}
`; return; } listEl.innerHTML = ''; @@ -809,7 +794,7 @@ const musicView = { selectedIds.delete(file.id); row.classList.remove('selected'); } - countEl.textContent = `${selectedIds.size} ${t('music.selected', 'selected')}`; + countEl.textContent = `${selectedIds.size} ${i18n.t('music.selected')}`; addBtn.disabled = selectedIds.size === 0; }); listEl.appendChild(row); @@ -827,7 +812,7 @@ const musicView = { addBtn.addEventListener('click', async () => { if (selectedIds.size === 0) return; addBtn.disabled = true; - addBtn.innerHTML = ` ${t('music.adding', 'Adding…')}`; + addBtn.innerHTML = ` ${i18n.t('music.adding')}`; try { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks`, { @@ -842,8 +827,8 @@ const musicView = { notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.add_tracks', 'Add Tracks'), - text: `${selectedIds.size} ${t('music.added_to_playlist', 'added to playlist')}` + title: i18n.t('music.add_tracks'), + text: `${selectedIds.size} ${i18n.t('music.added_to_playlist')}` }); } close(); @@ -854,7 +839,7 @@ const musicView = { this.currentPlaylist.track_count = playlist.track_count; this._renderPlaylistList(); const metaEl = document.getElementById('music-playlist-meta'); - if (metaEl) metaEl.textContent = `${playlist.track_count} ${t('music.tracks', 'tracks')}`; + if (metaEl) metaEl.textContent = `${playlist.track_count} ${i18n.t('music.tracks')}`; } } catch (err) { console.error('Add tracks error:', err); @@ -862,12 +847,12 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), - text: t('music.add_error', 'Could not add tracks to playlist') + title: i18n.t('music.error'), + text: i18n.t('music.add_error') }); } addBtn.disabled = false; - addBtn.innerHTML = ` ${t('music.add', 'Add')}`; + addBtn.innerHTML = ` ${i18n.t('music.add')}`; } }); @@ -878,7 +863,6 @@ const musicView = { async _removeTrackFromPlaylist(_trackId, fileId) { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); try { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, { @@ -892,8 +876,8 @@ const musicView = { notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.remove', 'Remove'), - text: t('music.track_removed', 'Track removed') + title: i18n.t('music.remove'), + text: i18n.t('music.track_removed') }); } await this._loadPlaylistTracks(this.currentPlaylist.id); @@ -903,7 +887,7 @@ const musicView = { this.currentPlaylist.track_count = playlist.track_count; this._renderPlaylistList(); const metaEl = document.getElementById('music-playlist-meta'); - if (metaEl) metaEl.textContent = `${playlist.track_count} ${t('music.tracks', 'tracks')}`; + if (metaEl) metaEl.textContent = `${playlist.track_count} ${i18n.t('music.tracks')}`; } } catch (err) { console.error('Remove track error:', err); @@ -911,7 +895,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -920,7 +904,6 @@ const musicView = { async _reorderTrack(fromIdx, toIdx) { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const tracks = [...this.currentTracks]; const [moved] = tracks.splice(fromIdx, 1); @@ -943,7 +926,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -953,7 +936,6 @@ const musicView = { async _showManageSharesDialog() { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const existing = document.getElementById('music-shares-dialog'); if (existing) existing.remove(); @@ -964,19 +946,19 @@ const musicView = { dialog.innerHTML = `
-

${t('music.manage_shares', 'Manage Shares')}

+

${i18n.t('music.manage_shares')}

- +
@@ -1009,8 +991,8 @@ const musicView = { notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.share', 'Share'), - text: t('music.added', 'Added!') + title: i18n.t('music.share'), + text: i18n.t('music.added') }); } } catch (err) { @@ -1018,7 +1000,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -1030,7 +1012,6 @@ const musicView = { async _loadSharesList(dialog) { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const body = dialog.querySelector('.music-shares-body'); if (!body) return; @@ -1045,7 +1026,7 @@ const musicView = { const shares = await resp.json(); if (shares.length === 0) { - body.innerHTML = `

${t('music.no_shares', 'No shares yet')}

`; + body.innerHTML = `

${i18n.t('music.no_shares')}

`; return; } @@ -1054,8 +1035,8 @@ const musicView = { (s) => ` ` ) @@ -1075,7 +1056,6 @@ const musicView = { async _removeShare(userId, dialog) { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); try { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, { @@ -1090,7 +1070,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -1099,7 +1079,6 @@ const musicView = { async _togglePublic() { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const newValue = !this.currentPlaylist.is_public; try { @@ -1120,16 +1099,16 @@ const musicView = { const btn = document.getElementById('music-toggle-public-btn'); if (btn) { - btn.title = newValue ? t('music.make_private', 'Make private') : t('music.make_public', 'Make public'); + btn.title = newValue ? i18n.t('music.make_private') : i18n.t('music.make_public'); btn.classList.toggle('active', newValue); } if (notifications) { - const status = newValue ? t('music.public', 'Public') : t('music.private', 'Private'); + const status = newValue ? i18n.t('music.public') : i18n.t('music.private'); notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.toggle_public', 'Visibility'), + title: i18n.t('music.toggle_public'), text: status }); } @@ -1139,7 +1118,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -1148,7 +1127,6 @@ const musicView = { async _showCoverPicker() { if (!this.currentPlaylist) return; - const t = (key) => i18n.t(key); const input = document.createElement('input'); input.type = 'file'; @@ -1198,8 +1176,8 @@ const musicView = { notifications.addNotification({ icon: 'fa-check-circle', iconClass: 'upload', - title: t('music.set_cover', 'Set cover'), - text: t('music.cover_updated', 'Cover updated') + title: i18n.t('music.set_cover'), + text: i18n.t('music.cover_updated') }); } } catch (err) { @@ -1208,7 +1186,7 @@ const musicView = { notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), + title: i18n.t('music.error'), text: err.message }); } @@ -1626,14 +1604,13 @@ const musicPlayer = { console.error('Audio error:', e); this.isPlaying = false; this._updateUI(); - const t = (key) => i18n.t(key); if (notifications) { - const trackName = this.currentTrack?.title || this.currentTrack?.file_name || t('music.unknown_title', 'Unknown'); + const trackName = this.currentTrack?.title || this.currentTrack?.file_name || i18n.t('music.unknown_title'); notifications.addNotification({ icon: 'fa-exclamation-circle', iconClass: 'error', - title: t('music.error', 'Error'), - text: `${t('music.playback_error', 'Playback failed')}: ${trackName}` + title: i18n.t('music.error'), + text: `${i18n.t('music.playback_error')}: ${trackName}` }); } }, @@ -1657,10 +1634,9 @@ const musicPlayer = { } if (trackName) { - const t = (key) => i18n.t(key); trackName.textContent = this.currentTrack - ? this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown') - : t('music.not_playing', 'Not playing'); + ? this.currentTrack.title || this.currentTrack.file_name || i18n.t('music.unknown_title') + : i18n.t('music.not_playing'); } if (trackArtist) { @@ -1710,13 +1686,12 @@ const musicPlayer = { const queueList = document.getElementById('player-queue-list'); if (!queueList) return; - const t = (key) => i18n.t(key); if (this.queue.length === 0) { queueList.innerHTML = `
-

${t('music.queue_empty', 'Queue is empty')}

+

${i18n.t('music.queue_empty')}

`; return; @@ -1728,8 +1703,8 @@ const musicPlayer = {
${idx + 1} - ${this._escapeHtml(track.title || track.file_name || t('music.unknown_title', 'Unknown'))} - ${this._escapeHtml(track.artist || t('music.unknown_artist', 'Unknown Artist'))} + ${this._escapeHtml(track.title || track.file_name || i18n.t('music.unknown_title'))} + ${this._escapeHtml(track.artist || i18n.t('music.unknown_artist'))} ${this._formatDuration(track.duration_secs)} diff --git a/static/js/views/admin/admin.js b/static/js/views/admin/admin.js index e06499ba..739927e5 100644 --- a/static/js/views/admin/admin.js +++ b/static/js/views/admin/admin.js @@ -8,11 +8,6 @@ let usersPage = 0; const PAGE_SIZE = 50; let totalUsers = 0; -/* ── i18n helper ── */ -function t(key, params) { - return i18n.t(key, params); -} - /** Escape a string for safe embedding inside a JS string literal within an HTML attribute. */ function _escJs(s) { if (typeof s !== 'string') return ''; @@ -52,14 +47,14 @@ function formatBytes(bytes) { } function timeAgo(dateStr) { - if (!dateStr) return t('admin.never'); + if (!dateStr) return i18n.t('admin.never'); const d = new Date(dateStr); const now = new Date(); const secs = Math.floor((now - d) / 1000); - if (secs < 60) return t('admin.just_now'); - if (secs < 3600) return t('admin.minutes_ago', { n: Math.floor(secs / 60) }); - if (secs < 86400) return t('admin.hours_ago', { n: Math.floor(secs / 3600) }); - if (secs < 2592000) return t('admin.days_ago', { n: Math.floor(secs / 86400) }); + if (secs < 60) return i18n.t('admin.just_now'); + if (secs < 3600) return i18n.t('admin.minutes_ago', { n: Math.floor(secs / 60) }); + if (secs < 86400) return i18n.t('admin.hours_ago', { n: Math.floor(secs / 3600) }); + if (secs < 2592000) return i18n.t('admin.days_ago', { n: Math.floor(secs / 86400) }); return d.toLocaleDateString(); } @@ -157,9 +152,9 @@ async function loadDashboard() { const bar = document.getElementById('ds-bar'); bar.style.width = `${Math.min(d.storage_usage_percent, 100)}%`; bar.className = `progress-fill ${d.storage_usage_percent > 90 ? 'red' : d.storage_usage_percent > 70 ? 'orange' : 'green'}`; - document.getElementById('ds-auth').textContent = d.auth_enabled ? t('admin.enabled') : t('admin.disabled'); - document.getElementById('ds-oidc').textContent = d.oidc_configured ? t('admin.active') : t('admin.off'); - document.getElementById('ds-quotas-flag').textContent = d.quotas_enabled ? t('admin.enabled') : t('admin.disabled'); + document.getElementById('ds-auth').textContent = d.auth_enabled ? i18n.t('admin.enabled') : i18n.t('admin.disabled'); + document.getElementById('ds-oidc').textContent = d.oidc_configured ? i18n.t('admin.active') : i18n.t('admin.off'); + document.getElementById('ds-quotas-flag').textContent = d.quotas_enabled ? i18n.t('admin.enabled') : i18n.t('admin.disabled'); if (typeof d.registration_enabled !== 'undefined') { document.getElementById('ds-registration').checked = d.registration_enabled; @@ -182,7 +177,7 @@ async function loadDashboard() { async function loadUsers() { const tbody = document.getElementById('users-tbody'); - tbody.innerHTML = ` ${escapeHtml(t('admin.loading_users'))}`; + tbody.innerHTML = ` ${escapeHtml(i18n.t('admin.loading_users'))}`; try { const resp = await fetch(`${API}/admin/users?limit=${PAGE_SIZE}&offset=${usersPage * PAGE_SIZE}`, { headers: headers(), @@ -191,7 +186,7 @@ async function loadUsers() { if (!resp.ok) { tbody.innerHTML = ' ' + - escapeHtml(t('admin.failed_load_users')) + + escapeHtml(i18n.t('admin.failed_load_users')) + ''; return; } @@ -199,7 +194,7 @@ async function loadUsers() { totalUsers = data.total; const users = data.users; if (users.length === 0) { - tbody.innerHTML = `${escapeHtml(t('admin.no_users_found'))}`; + tbody.innerHTML = `${escapeHtml(i18n.t('admin.no_users_found'))}`; return; } @@ -219,12 +214,12 @@ async function loadUsers() { '"> ' + escapeHtml(u.auth_provider) + '' - : `${escapeHtml(t('admin.local'))}`; + : `${escapeHtml(i18n.t('admin.local'))}`; return ( '' + '' + @@ -240,7 +235,7 @@ async function loadUsers() { '' + - (u.active ? escapeHtml(t('admin.active')) : escapeHtml(t('admin.inactive'))) + + (u.active ? escapeHtml(i18n.t('admin.active')) : escapeHtml(i18n.t('admin.inactive'))) + '' + '
' + (isOidc ? '' @@ -269,14 +264,14 @@ async function loadUsers() { '" data-uname="' + _escJs(u.username) + '" title="' + - escapeHtml(t('admin.reset_password_title')) + + escapeHtml(i18n.t('admin.reset_password_title')) + '">') + '' + @@ -329,13 +324,13 @@ async function loadUsers() { const from = usersPage * PAGE_SIZE + 1; const to = Math.min((usersPage + 1) * PAGE_SIZE, totalUsers); - document.getElementById('users-info').textContent = t('admin.showing_users', { from: from, to: to, total: totalUsers }); + document.getElementById('users-info').textContent = i18n.t('admin.showing_users', { from: from, to: to, total: totalUsers }); document.getElementById('prev-btn').disabled = usersPage === 0; document.getElementById('next-btn').disabled = (usersPage + 1) * PAGE_SIZE >= totalUsers; } catch (e) { tbody.innerHTML = ' ' + - escapeHtml(t('admin.error_network', { message: e.message })) + + escapeHtml(i18n.t('admin.error_network', { message: e.message })) + ''; } } @@ -355,7 +350,7 @@ function nextPage() { async function toggleRole(userId, currentRole) { const newRole = currentRole === 'admin' ? 'user' : 'admin'; - const ok = await showConfirm(t('admin.confirm_role_change', { role: newRole })); + const ok = await showConfirm(i18n.t('admin.confirm_role_change', { role: newRole })); if (!ok) return; try { const resp = await fetch(`${API}/admin/users/${userId}/role`, { @@ -367,15 +362,15 @@ async function toggleRole(userId, currentRole) { if (resp.ok) loadUsers(); else { const e = await resp.json(); - alert(e.message || t('admin.error_generic')); + alert(e.message || i18n.t('admin.error_generic')); } } catch (e) { - alert(t('admin.error_network', { message: e.message })); + alert(i18n.t('admin.error_network', { message: e.message })); } } async function toggleActive(userId, currentActive) { - const msg = currentActive ? t('admin.confirm_deactivate') : t('admin.confirm_activate'); + const msg = currentActive ? i18n.t('admin.confirm_deactivate') : i18n.t('admin.confirm_activate'); const ok = await showConfirm(msg); if (!ok) return; try { @@ -388,15 +383,15 @@ async function toggleActive(userId, currentActive) { if (resp.ok) loadUsers(); else { const e = await resp.json(); - alert(e.message || t('admin.error_generic')); + alert(e.message || i18n.t('admin.error_generic')); } } catch (e) { - alert(t('admin.error_network', { message: e.message })); + alert(i18n.t('admin.error_network', { message: e.message })); } } async function deleteUser(userId, username) { - const ok = await showConfirm(t('admin.confirm_delete_user', { name: username })); + const ok = await showConfirm(i18n.t('admin.confirm_delete_user', { name: username })); if (!ok) return; try { const resp = await fetch(`${API}/admin/users/${userId}`, { @@ -409,10 +404,10 @@ async function deleteUser(userId, username) { loadDashboard(); } else { const e = await resp.json(); - alert(e.message || t('admin.error_generic')); + alert(e.message || i18n.t('admin.error_generic')); } } catch (e) { - alert(t('admin.error_network', { message: e.message })); + alert(i18n.t('admin.error_network', { message: e.message })); } } @@ -446,10 +441,10 @@ async function saveQuota() { loadDashboard(); } else { const e = await resp.json(); - alert(e.message || t('admin.error_generic')); + alert(e.message || i18n.t('admin.error_generic')); } } catch (e) { - alert(t('admin.error_network', { message: e.message })); + alert(i18n.t('admin.error_network', { message: e.message })); } } @@ -480,19 +475,19 @@ async function submitCreateUser() { const errorEl = document.getElementById('cu-error'); if (username.length < 3) { - errorEl.textContent = t('admin.error_username_short'); + errorEl.textContent = i18n.t('admin.error_username_short'); errorEl.className = 'alert alert-error'; return; } if (password.length < 8) { - errorEl.textContent = t('admin.error_password_short'); + errorEl.textContent = i18n.t('admin.error_password_short'); errorEl.className = 'alert alert-error'; return; } const btn = document.getElementById('cu-submit'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.creating'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.creating'))}`; try { const resp = await fetch(`${API}/admin/users`, { method: 'POST', @@ -512,15 +507,15 @@ async function submitCreateUser() { loadDashboard(); } else { const e = await resp.json().catch(() => ({})); - errorEl.textContent = e.message || t('admin.error_create_user'); + errorEl.textContent = e.message || i18n.t('admin.error_create_user'); errorEl.className = 'alert alert-error'; } } catch (e) { - errorEl.textContent = t('admin.error_network', { message: e.message }); + errorEl.textContent = i18n.t('admin.error_network', { message: e.message }); errorEl.className = 'alert alert-error'; } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.create_user'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.create_user'))}`; } let resetPwUserId = ''; @@ -541,14 +536,14 @@ async function submitResetPassword() { const password = document.getElementById('rp-password').value; const errorEl = document.getElementById('rp-error'); if (password.length < 8) { - errorEl.textContent = t('admin.error_password_short'); + errorEl.textContent = i18n.t('admin.error_password_short'); errorEl.className = 'alert alert-error'; return; } const btn = document.getElementById('rp-submit'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.resetting'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.resetting'))}`; try { const resp = await fetch(`${API}/admin/users/${resetPwUserId}/password`, { method: 'PUT', @@ -560,15 +555,15 @@ async function submitResetPassword() { closeResetPasswordModal(); } else { const e = await resp.json().catch(() => ({})); - errorEl.textContent = e.message || t('admin.error_generic'); + errorEl.textContent = e.message || i18n.t('admin.error_generic'); errorEl.className = 'alert alert-error'; } } catch (e) { - errorEl.textContent = t('admin.error_network', { message: e.message }); + errorEl.textContent = i18n.t('admin.error_network', { message: e.message }); errorEl.className = 'alert alert-error'; } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.reset_btn'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.reset_btn'))}`; } async function toggleRegistration(enabled) { @@ -586,13 +581,13 @@ async function toggleRegistration(enabled) { if (!enabled) showElement('registration-warning', 'flex'); else hideElement('registration-warning'); const e = await resp.json().catch(() => ({})); - alert(e.message || t('admin.error_generic')); + alert(e.message || i18n.t('admin.error_generic')); } } catch (e) { document.getElementById('ds-registration').checked = !enabled; if (!enabled) showElement('registration-warning', 'flex'); else hideElement('registration-warning'); - alert(t('admin.error_network', { message: e.message })); + alert(i18n.t('admin.error_network', { message: e.message })); } } @@ -624,7 +619,7 @@ async function testConnection() { } const btn = document.getElementById('discover-btn'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.discovering'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.discovering'))}`; const resultDiv = document.getElementById('discovery-result'); try { const resp = await fetch(`${API}/admin/settings/oidc/test`, { @@ -652,13 +647,13 @@ async function testConnection() { resultDiv.innerHTML = `
Error: ${escapeHtml(e.message)}
`; } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.auto_discover'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.auto_discover'))}`; } async function saveOidcSettings() { const btn = document.getElementById('save-btn'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.saving'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.saving'))}`; const body = { enabled: document.getElementById('oidc-enabled').checked, issuer_url: document.getElementById('issuer-url').value.trim(), @@ -678,18 +673,18 @@ async function saveOidcSettings() { body: JSON.stringify(body) }); if (resp.ok) { - const status = body.enabled ? t('admin.active').toLowerCase() : t('admin.disabled').toLowerCase(); - showOidcStatus(t('admin.settings_saved', { status: status }), 'success'); + const status = body.enabled ? i18n.t('admin.active').toLowerCase() : i18n.t('admin.disabled').toLowerCase(); + showOidcStatus(i18n.t('admin.settings_saved', { status: status }), 'success'); loadDashboard(); } else { const e = await resp.json().catch(() => ({})); showOidcStatus(`Error: ${e.message || resp.statusText}`, 'error'); } } catch (e) { - showOidcStatus(t('admin.error_network', { message: e.message }), 'error'); + showOidcStatus(i18n.t('admin.error_network', { message: e.message }), 'error'); } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.save_btn'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.save_btn'))}`; } /* ── Storage tab ── */ @@ -750,7 +745,7 @@ async function loadStorage() { // Secret hints if (s.s3_access_key_set) { - document.getElementById('storage-access-key').placeholder = t('admin.storage_key_placeholder') || 'Leave empty to keep current value'; + document.getElementById('storage-access-key').placeholder = i18n.t('admin.storage_key_placeholder') || 'Leave empty to keep current value'; } if (s.s3_secret_key_set) { showElement('storage-secret-hint'); @@ -770,7 +765,7 @@ async function loadStorage() { document.getElementById('storage-total-size').textContent = s.total_bytes_stored != null ? formatBytes(s.total_bytes_stored) : '—'; document.getElementById('storage-dedup-ratio').textContent = s.dedup_ratio != null ? `${s.dedup_ratio.toFixed(2)}x` : '—'; } catch (e) { - showStorageStatus(t('admin.error_network', { message: e.message }), 'error'); + showStorageStatus(i18n.t('admin.error_network', { message: e.message }), 'error'); } // Also load migration status @@ -780,7 +775,7 @@ async function loadStorage() { async function saveStorageSettings() { const btn = document.getElementById('btn-save-storage'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.saving'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.saving'))}`; const backend = document.querySelector('input[name="storage-backend"]:checked').value; const body = { @@ -801,23 +796,23 @@ async function saveStorageSettings() { body: JSON.stringify(body) }); if (resp.ok) { - showStorageStatus(t('admin.storage_saved') || 'Storage settings saved successfully', 'success'); + showStorageStatus(i18n.t('admin.storage_saved') || 'Storage settings saved successfully', 'success'); loadStorage(); } else { const e = await resp.json().catch(() => ({})); showStorageStatus(`Error: ${e.message || resp.statusText}`, 'error'); } } catch (e) { - showStorageStatus(t('admin.error_network', { message: e.message }), 'error'); + showStorageStatus(i18n.t('admin.error_network', { message: e.message }), 'error'); } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.storage_save') || 'Save')}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.storage_save') || 'Save')}`; } async function testStorageConnection() { const btn = document.getElementById('btn-test-storage'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.testing') || 'Testing...')}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.testing') || 'Testing...')}`; const backend = document.querySelector('input[name="storage-backend"]:checked').value; const body = { @@ -839,17 +834,17 @@ async function testStorageConnection() { }); const r = await resp.json(); if (r.connected) { - let msg = `${t('admin.storage_test_success') || 'Connection successful'} (${escapeHtml(r.backend_type)})`; + let msg = `${i18n.t('admin.storage_test_success') || 'Connection successful'} (${escapeHtml(r.backend_type)})`; if (r.available_bytes != null) msg += ` — ${formatBytes(r.available_bytes)} available`; showStorageStatus(msg, 'success'); } else { - showStorageStatus(`${t('admin.storage_test_failure') || 'Connection failed'}: ${escapeHtml(r.message)}`, 'error'); + showStorageStatus(`${i18n.t('admin.storage_test_failure') || 'Connection failed'}: ${escapeHtml(r.message)}`, 'error'); } } catch (e) { - showStorageStatus(t('admin.error_network', { message: e.message }), 'error'); + showStorageStatus(i18n.t('admin.error_network', { message: e.message }), 'error'); } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.storage_test_connection') || 'Test Connection')}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.storage_test_connection') || 'Test Connection')}`; } /* ── Migration ── */ @@ -951,14 +946,14 @@ async function startMigration() { body: JSON.stringify({ concurrency: 4 }) }); if (resp.ok) { - showMigrationMsg(t('admin.migration_started') || 'Migration started', 'success'); + showMigrationMsg(i18n.t('admin.migration_started') || 'Migration started', 'success'); loadMigrationStatus(); } else { const e = await resp.json().catch(() => ({})); showMigrationMsg(`Error: ${e.message || resp.statusText}`, 'error'); } } catch (e) { - showMigrationMsg(t('admin.error_network', { message: e.message }), 'error'); + showMigrationMsg(i18n.t('admin.error_network', { message: e.message }), 'error'); } btn.disabled = false; } @@ -971,7 +966,7 @@ async function pauseMigration() { credentials: 'same-origin' }); if (resp.ok) { - showMigrationMsg(t('admin.migration_paused_msg') || 'Migration paused', 'success'); + showMigrationMsg(i18n.t('admin.migration_paused_msg') || 'Migration paused', 'success'); loadMigrationStatus(); } } catch (_e) { @@ -987,7 +982,7 @@ async function resumeMigration() { credentials: 'same-origin' }); if (resp.ok) { - showMigrationMsg(t('admin.migration_resumed_msg') || 'Migration resumed', 'success'); + showMigrationMsg(i18n.t('admin.migration_resumed_msg') || 'Migration resumed', 'success'); loadMigrationStatus(); } } catch (_e) { @@ -998,7 +993,7 @@ async function resumeMigration() { async function verifyMigration() { const btn = document.getElementById('btn-verify-migration'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('admin.migration_verifying') || 'Verifying...')}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.migration_verifying') || 'Verifying...')}`; const resultDiv = document.getElementById('migration-verify-result'); try { const resp = await fetch(`${API}/admin/storage/migration/verify`, { @@ -1010,19 +1005,19 @@ async function verifyMigration() { const r = await resp.json(); resultDiv.style.display = ''; if (r.passed) { - resultDiv.innerHTML = `
${escapeHtml(t('admin.migration_verify_passed') || 'Verification passed')}

${r.sample_checked} blobs checked, ${r.pg_blob_count} total in database

`; + resultDiv.innerHTML = `
${escapeHtml(i18n.t('admin.migration_verify_passed') || 'Verification passed')}

${r.sample_checked} blobs checked, ${r.pg_blob_count} total in database

`; } else { const issues = []; if (r.missing_in_target.length) issues.push(`${r.missing_in_target.length} missing`); if (r.size_mismatches.length) issues.push(`${r.size_mismatches.length} size mismatches`); - resultDiv.innerHTML = `
${escapeHtml(t('admin.migration_verify_failed') || 'Verification failed')}

${issues.join(', ')}

`; + resultDiv.innerHTML = `
${escapeHtml(i18n.t('admin.migration_verify_failed') || 'Verification failed')}

${issues.join(', ')}

`; } } catch (e) { resultDiv.style.display = ''; resultDiv.innerHTML = `
Error: ${escapeHtml(e.message)}
`; } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('admin.migration_verify') || 'Verify Integrity')}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('admin.migration_verify') || 'Verify Integrity')}`; } async function completeMigration() { @@ -1033,14 +1028,14 @@ async function completeMigration() { credentials: 'same-origin' }); if (resp.ok) { - showMigrationMsg(t('admin.migration_completed_msg') || 'Migration finalized. Restart the server to use the new backend.', 'success'); + showMigrationMsg(i18n.t('admin.migration_completed_msg') || 'Migration finalized. Restart the server to use the new backend.', 'success'); loadMigrationStatus(); } else { const e = await resp.json().catch(() => ({})); showMigrationMsg(`Error: ${e.message || resp.statusText}`, 'error'); } } catch (e) { - showMigrationMsg(t('admin.error_network', { message: e.message }), 'error'); + showMigrationMsg(i18n.t('admin.error_network', { message: e.message }), 'error'); } } @@ -1104,7 +1099,7 @@ function showAccessDenied() { /* ── Apply i18n when translations load / change ── */ document.addEventListener('translationsLoaded', () => { i18n.translatePage(); - // Re-render dynamic content that uses t() + // Re-render dynamic content that uses i18n.t() loadDashboard(); if (activeTabName === 'users') loadUsers(); }); diff --git a/static/js/views/profile/profile.js b/static/js/views/profile/profile.js index 1743d101..a73f4ced 100644 --- a/static/js/views/profile/profile.js +++ b/static/js/views/profile/profile.js @@ -3,11 +3,6 @@ import { i18n } from '../../core/i18n.js'; const API = '/api'; -/* ── i18n helper ── */ -function t(key, params) { - return i18n.t(key, params); -} - function headers() { return { 'Content-Type': 'application/json', ...getCsrfHeaders() }; } @@ -21,14 +16,14 @@ function formatBytes(bytes) { } function timeAgo(dateStr) { - if (!dateStr) return t('profile.never'); + if (!dateStr) return i18n.t('profile.never'); const d = new Date(dateStr); const now = new Date(); const secs = Math.floor((now - d) / 1000); - if (secs < 60) return t('profile.just_now'); - if (secs < 3600) return t('profile.minutes_ago', { n: Math.floor(secs / 60) }); - if (secs < 86400) return t('profile.hours_ago', { n: Math.floor(secs / 3600) }); - if (secs < 2592000) return t('profile.days_ago', { n: Math.floor(secs / 86400) }); + if (secs < 60) return i18n.t('profile.just_now'); + if (secs < 3600) return i18n.t('profile.minutes_ago', { n: Math.floor(secs / 60) }); + if (secs < 86400) return i18n.t('profile.hours_ago', { n: Math.floor(secs / 3600) }); + if (secs < 2592000) return i18n.t('profile.days_ago', { n: Math.floor(secs / 86400) }); return d.toLocaleDateString(); } @@ -52,15 +47,15 @@ async function init() { const badge = document.getElementById('p-role-badge'); if (user.role === 'admin') { badge.className = 'role-badge role-badge-admin'; - badge.innerHTML = ` ${t('profile.role_admin')}`; + badge.innerHTML = ` ${i18n.t('profile.role_admin')}`; } else { badge.className = 'role-badge role-badge-user'; - badge.innerHTML = ` ${t('profile.role_user')}`; + badge.innerHTML = ` ${i18n.t('profile.role_user')}`; } document.getElementById('p-detail-username').textContent = user.username; document.getElementById('p-detail-email').textContent = user.email || '—'; - document.getElementById('p-detail-role').textContent = user.role === 'admin' ? t('profile.role_admin') : t('profile.role_user'); + document.getElementById('p-detail-role').textContent = user.role === 'admin' ? i18n.t('profile.role_admin') : i18n.t('profile.role_user'); document.getElementById('p-detail-login').textContent = timeAgo(user.last_login_at); const used = user.storage_used_bytes || 0; @@ -74,7 +69,7 @@ async function init() { const bar = document.getElementById('p-storage-bar'); bar.style.width = `${pct}%`; bar.className = `storage-fill ${pct > 90 ? 'red' : pct > 70 ? 'orange' : 'green'}`; - document.getElementById('p-storage-text').textContent = `${formatBytes(used)} / ${quota > 0 ? formatBytes(quota) : t('profile.unlimited')}`; + document.getElementById('p-storage-text').textContent = `${formatBytes(used)} / ${quota > 0 ? formatBytes(quota) : i18n.t('profile.unlimited')}`; if (user.auth_provider && user.auth_provider !== 'local') { document.getElementById('password-section').classList.add('hidden'); @@ -115,18 +110,18 @@ async function changePassword(e) { const statusEl = document.getElementById('pw-status'); if (newPw !== confirmPw) { - statusEl.innerHTML = `
${escapeHtml(t('profile.passwords_no_match'))}
`; + statusEl.innerHTML = `
${escapeHtml(i18n.t('profile.passwords_no_match'))}
`; return false; } if (newPw.length < 8) { - statusEl.innerHTML = `
${escapeHtml(t('profile.password_too_short'))}
`; + statusEl.innerHTML = `
${escapeHtml(i18n.t('profile.password_too_short'))}
`; return false; } const btn = document.getElementById('pw-submit'); btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('profile.updating'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('profile.updating'))}`; try { const resp = await fetch(`${API}/auth/change-password`, { @@ -140,24 +135,24 @@ async function changePassword(e) { }); if (resp.ok) { - statusEl.innerHTML = `
${escapeHtml(t('profile.password_updated'))}
`; + statusEl.innerHTML = `
${escapeHtml(i18n.t('profile.password_updated'))}
`; document.getElementById('password-form').reset(); } else { const err = await resp.json().catch(() => ({})); statusEl.innerHTML = '
' + - escapeHtml(err.message || t('profile.password_change_failed')) + + escapeHtml(err.message || i18n.t('profile.password_change_failed')) + '
'; } } catch (err) { statusEl.innerHTML = '
' + - escapeHtml(t('profile.error_network', { message: err.message })) + + escapeHtml(i18n.t('profile.error_network', { message: err.message })) + '
'; } btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('profile.update_password'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('profile.update_password'))}`; return false; } @@ -176,15 +171,15 @@ function renderPwRow(pw) { const created = document.createElement('td'); created.textContent = new Date(pw.created_at).toLocaleDateString(); const lastUsed = document.createElement('td'); - lastUsed.textContent = pw.last_used_at ? timeAgo(pw.last_used_at) : t('profile.never'); + lastUsed.textContent = pw.last_used_at ? timeAgo(pw.last_used_at) : i18n.t('profile.never'); const status = document.createElement('td'); const badge = document.createElement('span'); if (pw.active !== false) { badge.className = 'badge badge-active'; - badge.textContent = t('profile.active'); + badge.textContent = i18n.t('profile.active'); } else { badge.className = 'badge badge-expired'; - badge.textContent = t('profile.revoked'); + badge.textContent = i18n.t('profile.revoked'); } status.appendChild(badge); const actions = document.createElement('td'); @@ -192,7 +187,7 @@ function renderPwRow(pw) { const btn = document.createElement('button'); btn.className = 'btn btn-danger-sm'; btn.innerHTML = ''; - btn.title = t('profile.revoke_title'); + btn.title = i18n.t('profile.revoke_title'); btn.addEventListener('click', () => { revokeAppPassword(pw.id, pw.label); }); @@ -264,12 +259,12 @@ async function createAppPassword() { const btn = document.getElementById('app-pw-generate'); if (!label) { - statusEl.innerHTML = `
${escapeHtml(t('profile.error_label_required'))}
`; + statusEl.innerHTML = `
${escapeHtml(i18n.t('profile.error_label_required'))}
`; return; } btn.disabled = true; - btn.innerHTML = ` ${escapeHtml(t('profile.generating'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('profile.generating'))}`; statusEl.innerHTML = ''; try { @@ -283,7 +278,7 @@ async function createAppPassword() { const err = await resp.json().catch(() => ({})); statusEl.innerHTML = '
' + - escapeHtml(err.message || t('profile.error_create_pw')) + + escapeHtml(err.message || i18n.t('profile.error_create_pw')) + '
'; return; } @@ -297,7 +292,7 @@ async function createAppPassword() { statusEl.innerHTML = `
${err.message}
`; } finally { btn.disabled = false; - btn.innerHTML = ` ${escapeHtml(t('profile.generate'))}`; + btn.innerHTML = ` ${escapeHtml(i18n.t('profile.generate'))}`; } } @@ -313,7 +308,7 @@ function copyAppPassword() { } async function revokeAppPassword(id, label) { - if (!confirm(t('profile.confirm_revoke', { label: label }))) return; + if (!confirm(i18n.t('profile.confirm_revoke', { label: label }))) return; try { const resp = await fetch(`${API}/auth/app-passwords/${encodeURIComponent(id)}`, { method: 'DELETE', @@ -325,10 +320,10 @@ async function revokeAppPassword(id, label) { loadAppPasswords(); } else { const err = await resp.json().catch(() => ({})); - alert(err.message || t('profile.error_revoke')); + alert(err.message || i18n.t('profile.error_revoke')); } } catch (err) { - alert(t('profile.error_network', { message: err.message })); + alert(i18n.t('profile.error_network', { message: err.message })); } } From 5c19564102d58f49427a796d37f05c220c46ceb5 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sat, 25 Apr 2026 23:51:08 +0200 Subject: [PATCH 3/6] refactor(ui): i18n: add missing + correct keys --- static/js/features/library/music.js | 4 ++-- static/js/views/shared/sharedView.js | 8 ++++---- static/locales/en.json | 17 +++++++++++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/static/js/features/library/music.js b/static/js/features/library/music.js index 9274a067..53ffced6 100644 --- a/static/js/features/library/music.js +++ b/static/js/features/library/music.js @@ -710,7 +710,7 @@ const musicView = {

${i18n.t('music.add_tracks')}

- +