refactor(ui): remove unnecessary checks (i18n is always defined)

This commit is contained in:
Edouard Vanbelle
2026-04-25 22:55:42 +02:00
parent 156f3bc7ba
commit 4e2029969e
20 changed files with 142 additions and 215 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ async function loadFiles(options = { insertHistory: true }) {
ui.showError(` ui.showError(`
<div class="files-loading-spinner"> <div class="files-loading-spinner">
<div class="spinner"></div> <div class="spinner"></div>
<span>${i18n ? i18n.t('files.loading') : 'Loading files…'}</span> <span>${i18n.t('files.loading')}</span>
</div> </div>
`); `);
}, 100); }, 100);
+8 -15
View File
@@ -166,9 +166,7 @@ function setActionsBarMode(mode, force = false) {
elements.gridViewBtn = document.getElementById('grid-view-btn'); elements.gridViewBtn = document.getElementById('grid-view-btn');
elements.listViewBtn = document.getElementById('list-view-btn'); elements.listViewBtn = document.getElementById('list-view-btn');
if (i18n?.translateElement) { i18n.translateElement(elements.actionsBar);
i18n.translateElement(elements.actionsBar);
}
if (mode === 'files') { if (mode === 'files') {
setupUploadDropdown(); setupUploadDropdown();
@@ -395,7 +393,7 @@ function initApp() {
}); });
// Wait for translations to load before checking authentication // Wait for translations to load before checking authentication
if (i18n?.isLoaded?.()) { if (i18n.isLoaded()) {
// Translations already loaded, proceed with authentication // Translations already loaded, proceed with authentication
checkAuthentication(); checkAuthentication();
} else { } else {
@@ -408,7 +406,7 @@ function initApp() {
// Set a timeout as a fallback in case translations take too long // Set a timeout as a fallback in case translations take too long
setTimeout(() => { setTimeout(() => {
if (!i18n?.isLoaded?.()) { if (!i18n.isLoaded()) {
console.warn('Translations loading timeout, proceeding with authentication anyway'); console.warn('Translations loading timeout, proceeding with authentication anyway');
checkAuthentication(); checkAuthentication();
} }
@@ -727,16 +725,11 @@ function updateStorageUsageDisplay(userData) {
// Remove data-i18n attribute to prevent i18n from overwriting our value // Remove data-i18n attribute to prevent i18n from overwriting our value
storageInfo.removeAttribute('data-i18n'); storageInfo.removeAttribute('data-i18n');
// Use i18n if available storageInfo.textContent = i18n.t('storage.used', {
if (i18n?.t) { percentage: usagePercentage,
storageInfo.textContent = i18n.t('storage.used', { used: usedFormatted,
percentage: usagePercentage, total: quotaFormatted
used: usedFormatted, });
total: quotaFormatted
});
} else {
storageInfo.textContent = `${usagePercentage}% used (${usedFormatted} / ${quotaFormatted})`;
}
} }
console.log(`Updated storage display: ${usagePercentage}% (${usedFormatted} / ${quotaFormatted})`); console.log(`Updated storage display: ${usagePercentage}% (${usedFormatted} / ${quotaFormatted})`);
+1 -1
View File
@@ -152,7 +152,7 @@ function setCurrentSection(section) {
// Update page title // Update page title
const titleKey = `nav.${section}`; const titleKey = `nav.${section}`;
const defaultTitle = section.charAt(0).toUpperCase() + section.slice(1); 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); appElements.pageTitle.setAttribute('data-i18n', titleKey);
// Hide sharedView when switching to any other section // Hide sharedView when switching to any other section
+10 -11
View File
@@ -15,14 +15,13 @@ async function loadTrashItems() {
try { try {
if (multiSelect) multiSelect.clear(); if (multiSelect) multiSelect.clear();
ui.resetFilesList(); // ensure also list visible & error hidden ui.resetFilesList(); // ensure also list visible & error hidden
const _tt = i18n?.t ? i18n.t : (k) => k.split('.').pop();
elements.filesList.innerHTML = ` elements.filesList.innerHTML = `
<div class="list-header trash-header"> <div class="list-header trash-header">
<div data-i18n="files.name">${_tt('files.name')}</div> <div data-i18n="files.name">${i18n.t('files.name')}</div>
<div data-i18n="files.type">${_tt('files.type')}</div> <div data-i18n="files.type">${i18n.t('files.type')}</div>
<div data-i18n="trash.original_location">${_tt('trash.original_location')}</div> <div data-i18n="trash.original_location">${i18n.t('trash.original_location')}</div>
<div data-i18n="trash.deleted_date">${_tt('trash.deleted_date')}</div> <div data-i18n="trash.deleted_date">${i18n.t('trash.deleted_date')}</div>
<div data-i18n="trash.actions">${_tt('trash.actions')}</div> <div data-i18n="trash.actions">${i18n.t('trash.actions')}</div>
</div> </div>
`; `;
@@ -33,7 +32,7 @@ async function loadTrashItems() {
if (trashItems.length === 0) { if (trashItems.length === 0) {
ui.showError(` ui.showError(`
<i class="fas fa-trash empty-state-icon"></i> <i class="fas fa-trash empty-state-icon"></i>
<p>${i18n ? i18n.t('trash.empty_state') : 'The trash is empty'}</p> <p>${i18n.t('trash.empty_state')}</p>
`); `);
return; return;
} }
@@ -58,12 +57,12 @@ function addTrashItemToView(item) {
let iconSpecialClass = ''; let iconSpecialClass = '';
if (!isFile) { if (!isFile) {
iconClass = item.icon_class || 'fas fa-folder'; iconClass = item.icon_class || 'fas fa-folder';
typeLabel = i18n ? i18n.t('files.file_types.folder') : 'Folder'; typeLabel = i18n.t('files.file_types.folder');
} else { } else {
iconClass = item.icon_class || (ui?.getIconClass ? ui.getIconClass(item.name) : 'fas fa-file'); iconClass = item.icon_class || (ui?.getIconClass ? ui.getIconClass(item.name) : 'fas fa-file');
iconSpecialClass = ui?.getIconSpecialClass ? ui.getIconSpecialClass(item.name) : ''; iconSpecialClass = ui?.getIconSpecialClass ? ui.getIconSpecialClass(item.name) : '';
const cat = item.category || ''; 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; const isFolder = !isFile;
@@ -86,10 +85,10 @@ function addTrashItemToView(item) {
<div class="path-cell">${escapeHtml(item.original_path || '--')}</div> <div class="path-cell">${escapeHtml(item.original_path || '--')}</div>
<div class="date-cell">${escapeHtml(formattedDate)}</div> <div class="date-cell">${escapeHtml(formattedDate)}</div>
<div class="actions-cell"> <div class="actions-cell">
<button class="btn-restore" title="${i18n ? i18n.t('trash.restore') : 'Restore'}"> <button class="btn-restore" title="${i18n.t('trash.restore')}">
<i class="fas fa-undo"></i> <i class="fas fa-undo"></i>
</button> </button>
<button class="btn-delete" title="${i18n ? i18n.t('trash.delete_permanently') : 'Delete permanently'}"> <button class="btn-delete" title="${i18n.t('trash.delete_permanently')}">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
</div> </div>
+8 -14
View File
@@ -542,17 +542,11 @@ const ui = {
breadcrumb.innerHTML = ''; breadcrumb.innerHTML = '';
const path = app.breadcrumbPath; // [{id, name}, ...] 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) -- // -- Home icon (always present, clickable to go to root) --
const homeIcon = document.createElement('span'); const homeIcon = document.createElement('span');
homeIcon.className = 'breadcrumb-item breadcrumb-home'; homeIcon.className = 'breadcrumb-item breadcrumb-home';
homeIcon.innerHTML = '<i class="fas fa-home"></i>'; homeIcon.innerHTML = '<i class="fas fa-home"></i>';
homeIcon.title = getTranslatedText('breadcrumb.home', 'Home'); homeIcon.title = i18n.t('breadcrumb.home');
// Home is always clickable if we have a home folder // Home is always clickable if we have a home folder
if (app.userHomeFolderId) { if (app.userHomeFolderId) {
@@ -1265,7 +1259,7 @@ const ui = {
<div class="file-badge file-badge-favorite ${isFav ? '' : 'hidden'}"><i class="fas fa-star favorite-star-inline"></i></div> <div class="file-badge file-badge-favorite ${isFav ? '' : 'hidden'}"><i class="fas fa-star favorite-star-inline"></i></div>
<div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-share-alt"></i></div> <div class="file-badge file-badge-shared ${isShared ? '' : 'hidden'}"><i class="fas fa-share-alt"></i></div>
</div> </div>
<div class="type-cell">${i18n ? i18n.t('files.file_types.folder') : 'Folder'}</div> <div class="type-cell">${i18n.t('files.file_types.folder')}</div>
<div class="size-cell">--</div> <div class="size-cell">--</div>
<div class="date-cell">${formattedDate}</div> <div class="date-cell">${formattedDate}</div>
<div class="action-cell"> <div class="action-cell">
@@ -1288,7 +1282,7 @@ const ui = {
const iconClass = file.icon_class || this.getIconClass(file.name); const iconClass = file.icon_class || this.getIconClass(file.name);
const iconSpecialClass = file.icon_special_class || this.getIconSpecialClass(file.name); const iconSpecialClass = file.icon_special_class || this.getIconSpecialClass(file.name);
const cat = file.category || ''; 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 fileSize = file.size_formatted || formatFileSize(file.size);
const formattedDate = formatDateTime(file.modified_at); const formattedDate = formatDateTime(file.modified_at);
const isFav = favorites?.isFavorite(file.id, 'file'); const isFav = favorites?.isFavorite(file.id, 'file');
@@ -1352,7 +1346,7 @@ const ui = {
<div></div><!-- actions --> <div></div><!-- actions -->
</div>`; </div>`;
if (i18n?.translateElement) i18n.translateElement(filesList); i18n.translateElement(filesList);
filesList.classList.remove('hidden'); filesList.classList.remove('hidden');
filesContainerError?.classList.add('hidden'); filesContainerError?.classList.add('hidden');
@@ -1376,7 +1370,7 @@ const ui = {
const filesList = document.getElementById('files-list'); const filesList = document.getElementById('files-list');
if (filesContainerError) filesContainerError.innerHTML = content; if (filesContainerError) filesContainerError.innerHTML = content;
if (i18n?.translateElement) i18n.translateElement(filesContainerError); i18n.translateElement(filesContainerError);
filesContainerError?.classList.remove('hidden'); filesContainerError?.classList.remove('hidden');
filesList?.classList.add('hidden'); filesList?.classList.add('hidden');
@@ -1642,9 +1636,9 @@ if (document.readyState === 'loading') {
* @returns {Promise<boolean>} true if confirmed, false if cancelled * @returns {Promise<boolean>} true if confirmed, false if cancelled
*/ */
function showConfirmDialog({ title, message, confirmText, cancelText, danger = true } = {}) { function showConfirmDialog({ title, message, confirmText, cancelText, danger = true } = {}) {
const ct = confirmText || (i18n ? i18n.t('actions.delete') : 'Delete'); const ct = confirmText || i18n.t('actions.delete');
const cc = cancelText || (i18n ? i18n.t('actions.cancel') : 'Cancel'); const cc = cancelText || i18n.t('actions.cancel');
const t = title || (i18n ? i18n.t('dialogs.confirm_title') : 'Confirm action'); const t = title || i18n.t('dialogs.confirm_title');
return new Promise((resolve) => { return new Promise((resolve) => {
// Remove any previous confirm dialog // Remove any previous confirm dialog
+2 -1
View File
@@ -209,9 +209,10 @@ function showUserProfileModal() {
const usedBytes = userData.storage_used_bytes || 0; const usedBytes = userData.storage_used_bytes || 0;
const quotaBytes = userData.storage_quota_bytes == null ? 10 * 1024 * 1024 * 1024 : userData.storage_quota_bytes; 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; 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 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'); const existing = document.getElementById('profile-modal-overlay');
if (existing) existing.remove(); if (existing) existing.remove();
+2 -5
View File
@@ -66,7 +66,7 @@ function createLanguageSelector(containerId = 'language-selector') {
// Get current language // Get current language
const languages = getAvailableLanguages(); const languages = getAvailableLanguages();
const currentLocale = i18n ? i18n.getCurrentLocale() : 'en'; const currentLocale = i18n.getCurrentLocale();
const currentLang = languages.find((l) => l.code === currentLocale) || languages[0]; const currentLang = languages.find((l) => l.code === currentLocale) || languages[0];
// Set initial HTML attributes // Set initial HTML attributes
@@ -185,10 +185,7 @@ function closeDropdown(container) {
* Select a language * Select a language
*/ */
async function selectLanguage(langCode, container) { async function selectLanguage(langCode, container) {
// Update i18n if available await i18n.setLocale(langCode);
if (i18n) {
await i18n.setLocale(langCode);
}
// Update HTML lang attribute and dir for RTL languages // Update HTML lang attribute and dir for RTL languages
updateHtmlAttributes(langCode); updateHtmlAttributes(langCode);
+4 -5
View File
@@ -108,16 +108,15 @@ const Modal = {
this.input.placeholder = placeholder; this.input.placeholder = placeholder;
this.input.value = value; this.input.value = value;
// Set button text (use i18n if available)
if (confirmText) { if (confirmText) {
this.confirmBtn.textContent = confirmText; this.confirmBtn.textContent = confirmText;
} else if (i18n) { } else {
this.confirmBtn.textContent = i18n.t('actions.confirm'); this.confirmBtn.textContent = i18n.t('actions.confirm');
} }
if (cancelText) { if (cancelText) {
this.cancelBtn.textContent = cancelText; this.cancelBtn.textContent = cancelText;
} else if (i18n) { } else {
this.cancelBtn.textContent = i18n.t('actions.cancel'); this.cancelBtn.textContent = i18n.t('actions.cancel');
} }
@@ -138,7 +137,7 @@ const Modal = {
* @returns {Promise<string|null>} * @returns {Promise<string|null>}
*/ */
promptNewFolder() { promptNewFolder() {
const t = i18n ? i18n.t.bind(i18n) : (k) => k; const t = i18n.t.bind(i18n);
return this.prompt({ return this.prompt({
title: t('dialogs.new_folder_title') || 'New folder', title: t('dialogs.new_folder_title') || 'New folder',
@@ -156,7 +155,7 @@ const Modal = {
* @returns {Promise<string|null>} * @returns {Promise<string|null>}
*/ */
promptRename(currentName, isFolder = false) { 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) // For files, we want to select only the name part (without extension)
this._selectNameOnly = !isFolder; this._selectNameOnly = !isFolder;
+3 -3
View File
@@ -149,7 +149,7 @@ const notifications = (() => {
item.className = 'notif-item'; item.className = 'notif-item';
item.id = batchId; 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 uploadingText = folderName ? `📁 ${t('upload.uploading')} ${_esc(folderName)}…` : t('upload.uploading');
const filesLabel = t('upload.files'); const filesLabel = t('upload.files');
@@ -254,7 +254,7 @@ const notifications = (() => {
const pctEl = $(`${batchId}-pct`); const pctEl = $(`${batchId}-pct`);
const statsEl = $(`${batchId}-stats`); const statsEl = $(`${batchId}-stats`);
const t = i18n?.t || ((k) => k); const t = i18n.t.bind(i18n);
const filesLabel = t('upload.files'); const filesLabel = t('upload.files');
if (fillEl) fillEl.style.width = `${pctVal}%`; if (fillEl) fillEl.style.width = `${pctVal}%`;
@@ -282,7 +282,7 @@ const notifications = (() => {
const curEl = $(`${batchId}-current`); const curEl = $(`${batchId}-current`);
if (curEl) curEl.textContent = ''; if (curEl) curEl.textContent = '';
const t = i18n?.t || ((k) => k); const t = i18n.t.bind(i18n);
const completeText = t('upload.complete', { const completeText = t('upload.complete', {
count: successCount, count: successCount,
total: totalFiles total: totalFiles
+8 -20
View File
@@ -511,10 +511,7 @@ function initLanguageSelector() {
localStorage.setItem(LOCALE_KEY, selectedLanguage); localStorage.setItem(LOCALE_KEY, selectedLanguage);
localStorage.setItem(FIRST_RUN_KEY, 'true'); localStorage.setItem(FIRST_RUN_KEY, 'true');
// Update i18n if available await i18n.setLocale(selectedLanguage);
if (i18n?.setLocale) {
await i18n.setLocale(selectedLanguage);
}
// Hide language panel // Hide language panel
hidePanel(languagePanel); hidePanel(languagePanel);
@@ -635,7 +632,7 @@ async function configureOidcLoginUI() {
// Update button text with provider name // Update button text with provider name
const btnTextEl = oidcBtn.querySelector('span'); const btnTextEl = oidcBtn.querySelector('span');
if (btnTextEl && oidcInfo.provider_name) { 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); btnTextEl.textContent = template.replace('{{provider}}', oidcInfo.provider_name);
} }
@@ -942,8 +939,7 @@ if (isLoginPage && registerForm) {
// Validate passwords match // Validate passwords match
if (password !== confirmPassword) { if (password !== confirmPassword) {
const errorMsg = i18n ? i18n.t('auth.passwords_mismatch') : 'Passwords do not match'; registerError.textContent = i18n.t('auth.passwords_mismatch');
registerError.textContent = errorMsg;
registerError.style.display = 'block'; registerError.style.display = 'block';
return; return;
} }
@@ -951,9 +947,7 @@ if (isLoginPage && registerForm) {
try { try {
await register(username, email, password); await register(username, email, password);
// Show success message registerSuccess.textContent = i18n.t('auth.account_success');
const successMsg = i18n ? i18n.t('auth.account_success') : 'Account created successfully! You can now log in.';
registerSuccess.textContent = successMsg;
registerSuccess.style.display = 'block'; registerSuccess.style.display = 'block';
// Clear form // Clear form
@@ -965,8 +959,7 @@ if (isLoginPage && registerForm) {
hidePanel(registerPanel); hidePanel(registerPanel);
}, 2000); }, 2000);
} catch (error) { } catch (error) {
const errorMsg = i18n ? i18n.t('auth.admin_create_error') : 'Error registering account'; registerError.textContent = error.message || i18n.t('auth.admin_create_error');
registerError.textContent = error.message || errorMsg;
registerError.style.display = 'block'; registerError.style.display = 'block';
} }
}); });
@@ -988,8 +981,7 @@ if (isLoginPage && adminSetupForm) {
// Validate passwords match // Validate passwords match
if (password !== confirmPassword) { if (password !== confirmPassword) {
const errorMsg = i18n ? i18n.t('auth.passwords_mismatch') : 'Passwords do not match'; adminSetupError.textContent = i18n.t('auth.passwords_mismatch');
adminSetupError.textContent = errorMsg;
adminSetupError.style.display = 'block'; adminSetupError.style.display = 'block';
return; return;
} }
@@ -1012,11 +1004,8 @@ if (isLoginPage && adminSetupForm) {
} }
await response.json(); 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) { if (adminSetupSuccess) {
adminSetupSuccess.textContent = successMsg; adminSetupSuccess.textContent = i18n.t('auth.admin_success');
adminSetupSuccess.style.display = 'block'; adminSetupSuccess.style.display = 'block';
} }
@@ -1027,8 +1016,7 @@ if (isLoginPage && adminSetupForm) {
if (adminSetupSuccess) adminSetupSuccess.style.display = 'none'; if (adminSetupSuccess) adminSetupSuccess.style.display = 'none';
}, 2000); }, 2000);
} catch (error) { } catch (error) {
const errorMsg = i18n ? i18n.t('auth.admin_create_error') : 'Error creating admin account'; adminSetupError.textContent = error.message || i18n.t('auth.admin_create_error');
adminSetupError.textContent = error.message || errorMsg;
adminSetupError.style.display = 'block'; adminSetupError.style.display = 'block';
} }
}); });
+22 -24
View File
@@ -27,7 +27,7 @@ const contextMenus = {
if (!option) return; if (!option) return;
const label = option.querySelector('span'); const label = option.querySelector('span');
if (!label) return; 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; renameInput.value = folder.name;
// Update header text // Update header text
const headerSpan = renameDialog.querySelector('.rename-dialog-header span'); 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'); renameDialog?.classList.remove('hidden');
renameInput.focus(); renameInput.focus();
renameInput.select(); renameInput.select();
@@ -402,7 +402,7 @@ const contextMenus = {
renameInput.value = file.name; renameInput.value = file.name;
// Update header text // Update header text
const headerSpan = renameDialog.querySelector('.rename-dialog-header span'); 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'); renameDialog?.classList.remove('hidden');
renameInput.focus(); renameInput.focus();
renameInput.select(); renameInput.select();
@@ -471,7 +471,7 @@ const contextMenus = {
// Update dialog title (preserve icon) // Update dialog title (preserve icon)
const dialogHeader = document.getElementById('move-file-dialog').querySelector('.rename-dialog-header'); 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 = `<i class="fas fa-arrows-alt dialog-header-icon"></i> <span>${titleText}</span>`; dialogHeader.innerHTML = `<i class="fas fa-arrows-alt dialog-header-icon"></i> <span>${titleText}</span>`;
// Load folders for the starting location // Load folders for the starting location
@@ -496,7 +496,7 @@ const contextMenus = {
async renameItem() { async renameItem() {
const newName = document.getElementById('rename-input').value.trim(); const newName = document.getElementById('rename-input').value.trim();
if (!newName) { if (!newName) {
alert(i18n ? i18n.t('errors.empty_name') : 'Name cannot be empty'); alert(i18n.t('errors.empty_name'));
return; return;
} }
@@ -587,7 +587,7 @@ const contextMenus = {
currentFolderOption.className = 'folder-select-item folder-select-current'; currentFolderOption.className = 'folder-select-item folder-select-current';
currentFolderOption.innerHTML = ` currentFolderOption.innerHTML = `
<i class="fas fa-check-circle check-icon"></i> <i class="fas fa-check-circle check-icon"></i>
<span>${i18n ? i18n.t('dialogs.select_this_folder') : 'Select this folder'}</span> <span>${i18n.t('dialogs.select_this_folder')}</span>
`; `;
currentFolderOption.addEventListener('click', () => { currentFolderOption.addEventListener('click', () => {
document.querySelectorAll('.folder-select-item').forEach((item) => { document.querySelectorAll('.folder-select-item').forEach((item) => {
@@ -606,7 +606,7 @@ const contextMenus = {
parentOption.className = 'folder-select-item folder-navigate-up'; parentOption.className = 'folder-select-item folder-navigate-up';
parentOption.innerHTML = ` parentOption.innerHTML = `
<i class="fas fa-level-up-alt"></i> <i class="fas fa-level-up-alt"></i>
<span>${i18n ? i18n.t('dialogs.go_to_parent') : '.. (parent folder)'}</span> <span>${i18n.t('dialogs.go_to_parent')}</span>
`; `;
parentOption.addEventListener('click', () => { parentOption.addEventListener('click', () => {
// Navigate to parent folder // Navigate to parent folder
@@ -664,7 +664,7 @@ const contextMenus = {
homeOption.className = 'folder-select-item folder-select-current'; homeOption.className = 'folder-select-item folder-select-current';
homeOption.innerHTML = ` homeOption.innerHTML = `
<i class="fas fa-check-circle check-icon"></i> <i class="fas fa-check-circle check-icon"></i>
<span>${i18n ? i18n.t('dialogs.move_to_home') : 'Move to Home folder'}</span> <span>${i18n.t('dialogs.move_to_home')}</span>
`; `;
homeOption.addEventListener('click', () => { homeOption.addEventListener('click', () => {
document.querySelectorAll('.folder-select-item').forEach((item) => { document.querySelectorAll('.folder-select-item').forEach((item) => {
@@ -678,7 +678,7 @@ const contextMenus = {
// Inside a subfolder with no children - show empty message // Inside a subfolder with no children - show empty message
const emptyMsg = document.createElement('div'); const emptyMsg = document.createElement('div');
emptyMsg.className = 'folder-select-empty'; emptyMsg.className = 'folder-select-empty';
emptyMsg.innerHTML = `<i class="fas fa-folder-open"></i> <span>${i18n ? i18n.t('dialogs.no_subfolders') : 'No subfolders to navigate'}</span>`; emptyMsg.innerHTML = `<i class="fas fa-folder-open"></i> <span>${i18n.t('dialogs.no_subfolders')}</span>`;
folderSelectContainer.appendChild(emptyMsg); folderSelectContainer.appendChild(emptyMsg);
} }
@@ -686,9 +686,7 @@ const contextMenus = {
app.selectedTargetFolderId = parentFolderId || ''; app.selectedTargetFolderId = parentFolderId || '';
// Translate new elements // Translate new elements
if (i18n?.translateElement) { i18n.translateElement(folderSelectContainer);
i18n.translateElement(folderSelectContainer);
}
} catch (error) { } catch (error) {
console.error('Error loading folders:', error); console.error('Error loading folders:', error);
} }
@@ -799,7 +797,7 @@ const contextMenus = {
if (dialogHeader) { if (dialogHeader) {
const headerSpan = dialogHeader.querySelector('span'); const headerSpan = dialogHeader.querySelector('span');
const titleText = 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) { if (headerSpan) {
headerSpan.textContent = titleText; headerSpan.textContent = titleText;
} else { } else {
@@ -900,9 +898,9 @@ const contextMenus = {
const shareId = btn.getAttribute('data-share-id'); const shareId = btn.getAttribute('data-share-id');
showConfirmDialog({ showConfirmDialog({
title: i18n ? i18n.t('dialogs.confirm_delete_share') : 'Delete link', title: i18n.t('dialogs.confirm_delete_share'),
message: i18n ? i18n.t('dialogs.confirm_delete_share_msg') : 'Are you sure you want to delete this shared link?', message: i18n.t('dialogs.confirm_delete_share_msg'),
confirmText: i18n ? i18n.t('actions.delete') : 'Delete' confirmText: i18n.t('actions.delete')
}).then(async (confirmed) => { }).then(async (confirmed) => {
if (confirmed) { if (confirmed) {
await fileSharing.removeSharedLink(shareId); await fileSharing.removeSharedLink(shareId);
@@ -998,8 +996,8 @@ const contextMenus = {
// Show success message // Show success message
ui.showNotification( ui.showNotification(
i18n ? i18n.t('notifications.link_created') : 'Link created', i18n.t('notifications.link_created'),
i18n ? i18n.t('notifications.share_success') : 'Shared link created successfully' i18n.t('notifications.share_success')
); );
} catch (error) { } catch (error) {
console.error('Error creating shared link:', error); console.error('Error creating shared link:', error);
@@ -1088,7 +1086,7 @@ const contextMenus = {
// Update files info // Update files info
if (filesInfo) { if (filesInfo) {
filesInfo.innerHTML = `<strong>${i18n ? i18n.t('music.selected_files', 'Selected:') : 'Selected:'} </strong>${file.name}`; filesInfo.innerHTML = `<strong>${i18n.t('music.selected_files')} </strong>${file.name}`;
} }
// Reset selection // Reset selection
@@ -1112,12 +1110,12 @@ const contextMenus = {
this._renderPlaylistSelect(container, playlists); this._renderPlaylistSelect(container, playlists);
} catch (err) { } catch (err) {
console.error('Error loading playlists:', err); console.error('Error loading playlists:', err);
container.innerHTML = `<div class="folder-select-empty">${i18n ? i18n.t('music.load_error', 'Error loading playlists') : 'Error loading playlists'}</div>`; container.innerHTML = `<div class="folder-select-empty">${i18n.t('music.load_error')}</div>`;
} }
}, },
_renderPlaylistSelect(container, playlists) { _renderPlaylistSelect(container, playlists) {
const t = (key, fallback) => (i18n ? i18n.t(key, fallback) : fallback); const t = i18n.t.bind(i18n);
container.innerHTML = ''; container.innerHTML = '';
@@ -1177,8 +1175,8 @@ const contextMenus = {
await resp.json(); await resp.json();
ui.showNotification( ui.showNotification(
i18n ? i18n.t('music.added', 'Added!') : 'Added!', i18n.t('music.added'),
`${files.length} ${files.length === 1 ? 'track' : 'tracks'} ${i18n ? i18n.t('music.added_to_playlist', 'added to playlist') : 'added to playlist'}` `${files.length} ${files.length === 1 ? 'track' : 'tracks'} ${i18n.t('music.added_to_playlist')}`
); );
this.closePlaylistDialog(); this.closePlaylistDialog();
@@ -1189,7 +1187,7 @@ const contextMenus = {
} }
} catch (err) { } catch (err) {
console.error('Error adding to playlist:', 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; if (addBtn) addBtn.disabled = false;
} }
}, },
+20 -24
View File
@@ -30,12 +30,12 @@ const fileOps = {
/** Start a new upload batch in the notification bell */ /** Start a new upload batch in the notification bell */
_initUploadToast(totalFiles, folderName) { _initUploadToast(totalFiles, folderName) {
this._currentBatchId = notifications ? notifications.addUploadBatch(totalFiles, folderName) : null; this._currentBatchId = notifications.addUploadBatch(totalFiles, folderName);
}, },
/** Finalise the batch in the notification bell */ /** Finalise the batch in the notification bell */
_finishUploadToast(successCount, totalFiles) { _finishUploadToast(successCount, totalFiles) {
if (notifications && this._currentBatchId) { if (this._currentBatchId) {
notifications.finishBatch(this._currentBatchId, successCount, totalFiles); notifications.finishBatch(this._currentBatchId, successCount, totalFiles);
} }
}, },
@@ -361,7 +361,7 @@ const fileOps = {
progressBar.style.width = `${(uploadedCount / totalFiles) * 100}%`; progressBar.style.width = `${(uploadedCount / totalFiles) * 100}%`;
} }
// Notify bell of per-file completion // Notify bell of per-file completion
if (notifications && batchId) { if (batchId) {
try { try {
notifications.fileCompleted(batchId, result.ok); notifications.fileCompleted(batchId, result.ok);
} catch (e) { } catch (e) {
@@ -383,7 +383,7 @@ const fileOps = {
}); });
} }
if (result.isQuotaError) { 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) { if (notifications) {
notifications.addNotification({ notifications.addNotification({
icon: 'fa-exclamation-triangle', icon: 'fa-exclamation-triangle',
@@ -591,7 +591,7 @@ const fileOps = {
console.warn(`[SKIP] #${idx} ${rel} — cannot read 0-byte file (FIFO/pipe?), skipping`); console.warn(`[SKIP] #${idx} ${rel} — cannot read 0-byte file (FIFO/pipe?), skipping`);
uploadedCount++; uploadedCount++;
successCount++; successCount++;
if (notifications && batchId) { if (batchId) {
try { try {
notifications.fileCompleted(batchId, true); notifications.fileCompleted(batchId, true);
} catch (_) {} } catch (_) {}
@@ -623,7 +623,7 @@ const fileOps = {
uploadedCount++; uploadedCount++;
if (notifications && batchId) { if (batchId) {
try { try {
notifications.fileCompleted(batchId, result.ok); notifications.fileCompleted(batchId, result.ok);
} catch (_) {} } catch (_) {}
@@ -995,8 +995,8 @@ const fileOps = {
if (response.ok) { if (response.ok) {
ui.showNotification( ui.showNotification(
i18n ? i18n.t('notifications.file_renamed') : 'File renamed', i18n.t('notifications.file_renamed'),
i18n ? i18n.t('notifications.file_renamed_to', { name: newName }) : `File renamed to "${newName}"` i18n.t('notifications.file_renamed_to', { name: newName })
); );
return true; return true;
} else { } else {
@@ -1075,9 +1075,9 @@ const fileOps = {
*/ */
async deleteFile(fileId, fileName) { async deleteFile(fileId, fileName) {
const confirmed = await showConfirmDialog({ const confirmed = await showConfirmDialog({
title: i18n ? i18n.t('dialogs.confirm_delete') : 'Move to trash', title: i18n.t('dialogs.confirm_delete'),
message: i18n ? i18n.t('dialogs.confirm_delete_file', { name: fileName }) : `Are you sure you want to move the file "${fileName}" to trash?`, message: i18n.t('dialogs.confirm_delete_file', { name: fileName }),
confirmText: i18n ? i18n.t('actions.delete') : 'Delete' confirmText: i18n.t('actions.delete')
}); });
if (!confirmed) return false; if (!confirmed) return false;
@@ -1123,11 +1123,9 @@ const fileOps = {
*/ */
async deleteFolder(folderId, folderName) { async deleteFolder(folderId, folderName) {
const confirmed = await showConfirmDialog({ const confirmed = await showConfirmDialog({
title: i18n ? i18n.t('dialogs.confirm_delete') : 'Move to trash', title: i18n.t('dialogs.confirm_delete'),
message: i18n message: i18n.t('dialogs.confirm_delete_folder', { name: folderName }),
? i18n.t('dialogs.confirm_delete_folder', { name: folderName }) confirmText: i18n.t('actions.delete')
: `Are you sure you want to move the folder "${folderName}" and all its contents to trash?`,
confirmText: i18n ? i18n.t('actions.delete') : 'Delete'
}); });
if (!confirmed) return false; if (!confirmed) return false;
@@ -1234,11 +1232,9 @@ const fileOps = {
*/ */
async deletePermanently(trashId) { async deletePermanently(trashId) {
const confirmed = await showConfirmDialog({ const confirmed = await showConfirmDialog({
title: i18n ? i18n.t('dialogs.confirm_permanent_delete') : 'Delete permanently', title: i18n.t('dialogs.confirm_permanent_delete'),
message: i18n message: i18n.t('dialogs.confirm_permanent_delete_msg'),
? i18n.t('dialogs.confirm_permanent_delete_msg') confirmText: i18n.t('actions.delete_permanently')
: 'Are you sure you want to permanently delete this item? This action cannot be undone.',
confirmText: i18n ? i18n.t('actions.delete_permanently') : 'Delete permanently'
}); });
if (!confirmed) return false; if (!confirmed) return false;
@@ -1268,9 +1264,9 @@ const fileOps = {
*/ */
async emptyTrash() { async emptyTrash() {
const confirmed = await showConfirmDialog({ const confirmed = await showConfirmDialog({
title: i18n ? i18n.t('dialogs.confirm_empty_trash') : 'Empty trash', title: i18n.t('dialogs.confirm_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.', message: i18n.t('trash.empty_confirm'),
confirmText: i18n ? i18n.t('actions.empty_trash') : 'Empty trash' confirmText: i18n.t('actions.empty_trash')
}); });
if (!confirmed) return false; if (!confirmed) return false;
+2 -6
View File
@@ -50,12 +50,8 @@ const multiSelect = {
// ── Helpers for i18n ──────────────────────────────────── // ── Helpers for i18n ────────────────────────────────────
_t(key, vars) { _t(key, vars) {
if (i18n && typeof i18n.t === 'function') { const val = i18n.t(key, vars);
const val = i18n.t(key, vars); return val !== key ? val : null;
// If i18n returned the key itself, it's missing → fall back
if (val && val !== key) return val;
}
return null;
}, },
// ── Selection state management ────────────────────────── // ── Selection state management ──────────────────────────
+6 -6
View File
@@ -111,8 +111,8 @@ const favorites = {
// Notify user // Notify user
if (ui?.showNotification) { if (ui?.showNotification) {
ui.showNotification( ui.showNotification(
i18n ? i18n.t('favorites.added_title') : 'Added to favorites', i18n.t('favorites.added_title'),
`"${name}" ${i18n ? i18n.t('favorites.added_msg') : 'added to favorites'}` `"${name}" ${i18n.t('favorites.added_msg')}`
); );
} }
@@ -146,8 +146,8 @@ const favorites = {
if (ui?.showNotification) { if (ui?.showNotification) {
ui.showNotification( ui.showNotification(
i18n ? i18n.t('favorites.removed_title') : 'Removed from favorites', i18n.t('favorites.removed_title'),
`"${itemName}" ${i18n ? i18n.t('favorites.removed_msg') : 'removed from favorites'}` `"${itemName}" ${i18n.t('favorites.removed_msg')}`
); );
} }
@@ -182,8 +182,8 @@ const favorites = {
if (this._cache.size === 0) { if (this._cache.size === 0) {
ui.showError(` ui.showError(`
<i class="fas fa-star empty-state-icon"></i> <i class="fas fa-star empty-state-icon"></i>
<p>${i18n ? i18n.t('favorites.empty_state') : 'No favorite items'}</p> <p>${i18n.t('favorites.empty_state')}</p>
<p>${i18n ? i18n.t('favorites.empty_hint') : 'To mark as favorite, right-click on any file or folder'}</p> <p>${i18n.t('favorites.empty_hint')}</p>
`); `);
return; return;
} }
+32 -60
View File
@@ -186,9 +186,7 @@ const musicView = {
const listEl = document.getElementById('music-playlist-list'); const listEl = document.getElementById('music-playlist-list');
if (!listEl) return; if (!listEl) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
if (this.playlists.length === 0) { if (this.playlists.length === 0) {
listEl.innerHTML = ` listEl.innerHTML = `
@@ -291,9 +289,7 @@ const musicView = {
if (detailEl) detailEl.classList.remove('hidden'); if (detailEl) detailEl.classList.remove('hidden');
if (nameEl) nameEl.textContent = playlist.name; if (nameEl) nameEl.textContent = playlist.name;
if (metaEl) { if (metaEl) {
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
metaEl.textContent = `${playlist.track_count || 0} ${t('music.tracks', 'tracks')}`; metaEl.textContent = `${playlist.track_count || 0} ${t('music.tracks', 'tracks')}`;
} }
@@ -314,7 +310,7 @@ const musicView = {
} }
const togglePublicBtn = document.getElementById('music-toggle-public-btn'); const togglePublicBtn = document.getElementById('music-toggle-public-btn');
if (togglePublicBtn) { 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.title = playlist.is_public ? t2('music.make_private', 'Make private') : t2('music.make_public', 'Make public');
togglePublicBtn.classList.toggle('active', playlist.is_public); togglePublicBtn.classList.toggle('active', playlist.is_public);
} }
@@ -352,9 +348,7 @@ const musicView = {
const trackListEl = document.getElementById('music-track-list'); const trackListEl = document.getElementById('music-track-list');
if (!trackListEl) return; if (!trackListEl) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
if (this.currentTracks.length === 0) { if (this.currentTracks.length === 0) {
trackListEl.innerHTML = ` trackListEl.innerHTML = `
@@ -467,9 +461,7 @@ const musicView = {
_playTrack(idx) { _playTrack(idx) {
if (!this.currentTracks[idx]) return; if (!this.currentTracks[idx]) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
musicPlayer.setQueue(this.currentTracks, this.currentPlaylist?.name || t('music.playlists', 'Playlist')); musicPlayer.setQueue(this.currentTracks, this.currentPlaylist?.name || t('music.playlists', 'Playlist'));
musicPlayer.playTrack(idx); musicPlayer.playTrack(idx);
}, },
@@ -487,18 +479,14 @@ const musicView = {
const j = Math.floor(Math.random() * (i + 1)); const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
} }
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
musicPlayer.setQueue(shuffled, this.currentPlaylist?.name || t('music.shuffle', 'Shuffle')); musicPlayer.setQueue(shuffled, this.currentPlaylist?.name || t('music.shuffle', 'Shuffle'));
musicPlayer.playTrack(0); musicPlayer.playTrack(0);
} }
}, },
async _showCreatePlaylistDialog() { async _showCreatePlaylistDialog() {
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
const name = await Modal.prompt({ const name = await Modal.prompt({
title: t('music.create_playlist', 'Create Playlist'), title: t('music.create_playlist', 'Create Playlist'),
@@ -513,9 +501,7 @@ const musicView = {
}, },
async _createPlaylist(name) { async _createPlaylist(name) {
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
const createBtn = document.getElementById('music-create-playlist-btn'); const createBtn = document.getElementById('music-create-playlist-btn');
if (createBtn) createBtn.disabled = true; if (createBtn) createBtn.disabled = true;
try { try {
@@ -556,9 +542,7 @@ const musicView = {
}, },
async _deletePlaylist() { async _deletePlaylist() {
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
@@ -644,9 +628,7 @@ const musicView = {
async _showEditPlaylistDialog() { async _showEditPlaylistDialog() {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
const newName = await Modal.prompt({ const newName = await Modal.prompt({
title: t('music.edit', 'Edit'), title: t('music.edit', 'Edit'),
@@ -690,9 +672,7 @@ const musicView = {
async _showSharePlaylistDialog() { async _showSharePlaylistDialog() {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
const userId = await Modal.prompt({ const userId = await Modal.prompt({
title: t('music.share', 'Share'), title: t('music.share', 'Share'),
@@ -736,9 +716,7 @@ const musicView = {
async _showAddTracksDialog() { async _showAddTracksDialog() {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
// ── Build modal overlay ── // ── Build modal overlay ──
const overlay = document.createElement('div'); const overlay = document.createElement('div');
@@ -900,7 +878,7 @@ const musicView = {
async _removeTrackFromPlaylist(_trackId, fileId) { async _removeTrackFromPlaylist(_trackId, fileId) {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); const t = (key) => i18n.t(key);
try { try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/tracks/${encodeURIComponent(fileId)}`, {
@@ -942,7 +920,7 @@ const musicView = {
async _reorderTrack(fromIdx, toIdx) { async _reorderTrack(fromIdx, toIdx) {
if (!this.currentPlaylist) return; 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 tracks = [...this.currentTracks];
const [moved] = tracks.splice(fromIdx, 1); const [moved] = tracks.splice(fromIdx, 1);
@@ -975,7 +953,7 @@ const musicView = {
async _showManageSharesDialog() { async _showManageSharesDialog() {
if (!this.currentPlaylist) return; 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'); const existing = document.getElementById('music-shares-dialog');
if (existing) existing.remove(); if (existing) existing.remove();
@@ -1052,7 +1030,7 @@ const musicView = {
async _loadSharesList(dialog) { async _loadSharesList(dialog) {
if (!this.currentPlaylist) return; 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'); const body = dialog.querySelector('.music-shares-body');
if (!body) return; if (!body) return;
@@ -1097,7 +1075,7 @@ const musicView = {
async _removeShare(userId, dialog) { async _removeShare(userId, dialog) {
if (!this.currentPlaylist) return; if (!this.currentPlaylist) return;
const t = (key, fallback = '') => (i18n?.t ? i18n.t(key) : fallback || key); const t = (key) => i18n.t(key);
try { try {
const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, { const resp = await fetch(`/api/playlists/${this.currentPlaylist.id}/share/${encodeURIComponent(userId)}`, {
@@ -1121,7 +1099,7 @@ const musicView = {
async _togglePublic() { async _togglePublic() {
if (!this.currentPlaylist) return; 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; const newValue = !this.currentPlaylist.is_public;
try { try {
@@ -1170,7 +1148,7 @@ const musicView = {
async _showCoverPicker() { async _showCoverPicker() {
if (!this.currentPlaylist) return; 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'); const input = document.createElement('input');
input.type = 'file'; input.type = 'file';
@@ -1289,25 +1267,25 @@ const musicPlayer = {
<i class="fas fa-music"></i> <i class="fas fa-music"></i>
</div> </div>
<div class="player-track-details"> <div class="player-track-details">
<span class="player-track-name">${i18n?.t('music.not_playing', 'Not playing') || 'Not playing'}</span> <span class="player-track-name">${i18n.t('music.not_playing')}</span>
<span class="player-track-artist"></span> <span class="player-track-artist"></span>
</div> </div>
</div> </div>
<div class="player-controls"> <div class="player-controls">
<div class="player-buttons"> <div class="player-buttons">
<button class="player-btn" id="player-shuffle-btn" title="${i18n?.t('music.shuffle', 'Shuffle') || 'Shuffle'}"> <button class="player-btn" id="player-shuffle-btn" title="${i18n.t('music.shuffle')}">
<i class="fas fa-shuffle"></i> <i class="fas fa-shuffle"></i>
</button> </button>
<button class="player-btn" id="player-prev-btn" title="${i18n?.t('music.previous', 'Previous') || 'Previous'}"> <button class="player-btn" id="player-prev-btn" title="${i18n.t('music.previous')}">
<i class="fas fa-backward"></i> <i class="fas fa-backward"></i>
</button> </button>
<button class="player-btn player-btn-main" id="player-play-btn" title="${i18n?.t('music.play', 'Play') || 'Play'}"> <button class="player-btn player-btn-main" id="player-play-btn" title="${i18n.t('music.play')}">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button class="player-btn" id="player-next-btn" title="${i18n?.t('music.next', 'Next') || 'Next'}"> <button class="player-btn" id="player-next-btn" title="${i18n.t('music.next')}">
<i class="fas fa-forward"></i> <i class="fas fa-forward"></i>
</button> </button>
<button class="player-btn" id="player-repeat-btn" title="${i18n?.t('music.repeat', 'Repeat') || 'Repeat'}"> <button class="player-btn" id="player-repeat-btn" title="${i18n.t('music.repeat')}">
<i class="fas fa-repeat"></i> <i class="fas fa-repeat"></i>
</button> </button>
</div> </div>
@@ -1321,22 +1299,22 @@ const musicPlayer = {
</div> </div>
</div> </div>
<div class="player-extra"> <div class="player-extra">
<button class="player-btn player-btn-small" id="player-playlist-btn" title="${i18n?.t('music.queue', 'Queue') || 'Queue'}"> <button class="player-btn player-btn-small" id="player-playlist-btn" title="${i18n.t('music.queue')}">
<i class="fas fa-list"></i> <i class="fas fa-list"></i>
</button> </button>
<button class="player-btn player-btn-small" id="player-vol-btn" title="${i18n?.t('music.volume', 'Volume') || 'Volume'}"> <button class="player-btn player-btn-small" id="player-vol-btn" title="${i18n.t('music.volume')}">
<i class="fas fa-volume-up"></i> <i class="fas fa-volume-up"></i>
</button> </button>
<div class="player-volume-slider" id="player-volume-slider"> <div class="player-volume-slider" id="player-volume-slider">
<input type="range" min="0" max="100" value="70" id="player-volume-input"> <input type="range" min="0" max="100" value="70" id="player-volume-input">
</div> </div>
<button class="player-btn player-btn-small player-close-btn" id="player-close-btn" title="${i18n?.t('actions.close', 'Close') || 'Close'}"> <button class="player-btn player-btn-small player-close-btn" id="player-close-btn" title="${i18n.t('actions.close')}">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
<div class="player-queue hidden" id="player-queue"> <div class="player-queue hidden" id="player-queue">
<div class="player-queue-header"> <div class="player-queue-header">
<h3>${i18n?.t('music.queue', 'Queue') || 'Queue'}</h3> <h3>${i18n.t('music.queue')}</h3>
<button class="player-btn player-btn-small" id="player-close-queue-btn"> <button class="player-btn player-btn-small" id="player-close-queue-btn">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
@@ -1648,9 +1626,7 @@ const musicPlayer = {
console.error('Audio error:', e); console.error('Audio error:', e);
this.isPlaying = false; this.isPlaying = false;
this._updateUI(); this._updateUI();
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
if (notifications) { 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 || t('music.unknown_title', 'Unknown');
notifications.addNotification({ notifications.addNotification({
@@ -1681,9 +1657,7 @@ const musicPlayer = {
} }
if (trackName) { if (trackName) {
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
trackName.textContent = this.currentTrack trackName.textContent = this.currentTrack
? this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown') ? this.currentTrack.title || this.currentTrack.file_name || t('music.unknown_title', 'Unknown')
: t('music.not_playing', 'Not playing'); : t('music.not_playing', 'Not playing');
@@ -1736,9 +1710,7 @@ const musicPlayer = {
const queueList = document.getElementById('player-queue-list'); const queueList = document.getElementById('player-queue-list');
if (!queueList) return; if (!queueList) return;
const t = (key, fallback = '') => { const t = (key) => i18n.t(key);
return i18n?.t ? i18n.t(key) : fallback || key;
};
if (this.queue.length === 0) { if (this.queue.length === 0) {
queueList.innerHTML = ` queueList.innerHTML = `
+3 -3
View File
@@ -410,7 +410,7 @@ const photosView = {
/** Render the group mode toolbar */ /** Render the group mode toolbar */
_renderToolbar() { _renderToolbar() {
const t = (k, d) => (i18n ? i18n.t(k) : d); const t = (k) => i18n.t(k);
const modes = [ const modes = [
['daily', t('photos.view_daily', 'Day')], ['daily', t('photos.view_daily', 'Day')],
['monthly', t('photos.view_monthly', 'Month')], ['monthly', t('photos.view_monthly', 'Month')],
@@ -427,7 +427,7 @@ const photosView = {
/** Render empty state */ /** Render empty state */
_renderEmpty() { _renderEmpty() {
const t = (k, d) => (i18n ? i18n.t(k) : d); const t = (k) => i18n.t(k);
this._container.innerHTML = ` this._container.innerHTML = `
<div class="photos-empty"> <div class="photos-empty">
<i class="fas fa-images"></i> <i class="fas fa-images"></i>
@@ -520,7 +520,7 @@ const photosView = {
document.body.appendChild(bar); document.body.appendChild(bar);
} }
const t = (k, d) => (i18n ? i18n.t(k) : d); const t = (k) => i18n.t(k);
const count = this.selected.size; const count = this.selected.size;
bar.innerHTML = ` bar.innerHTML = `
<span class="selection-count">${count} ${t('photos.items_selected', 'selected')}</span> <span class="selection-count">${count} ${t('photos.items_selected', 'selected')}</span>
+2 -2
View File
@@ -114,8 +114,8 @@ const recent = {
if (recentItems.length === 0) { if (recentItems.length === 0) {
ui.showError(` ui.showError(`
<i class="fas fa-clock empty-state-icon"></i> <i class="fas fa-clock empty-state-icon"></i>
<p>${i18n ? i18n.t('recent.empty_state') : 'No recent files'}</p> <p>${i18n.t('recent.empty_state')}</p>
<p>${i18n ? i18n.t('recent.empty_hint') : 'Files you open will appear here'}</p> <p>${i18n.t('recent.empty_hint')}</p>
`); `);
} }
+4 -6
View File
@@ -8,11 +8,9 @@ let usersPage = 0;
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
let totalUsers = 0; let totalUsers = 0;
/* ── i18n helper — falls back to key if i18n not ready ── */ /* ── i18n helper ── */
function t(key, params) { function t(key, params) {
if (i18n && typeof i18n.t === 'function') return i18n.t(key, params); return i18n.t(key, params);
// fallback: strip prefix and humanise
return key.split('.').pop().replace(/_/g, ' ');
} }
/** Escape a string for safe embedding inside a JS string literal within an HTML attribute. */ /** Escape a string for safe embedding inside a JS string literal within an HTML attribute. */
@@ -1105,13 +1103,13 @@ function showAccessDenied() {
/* ── Apply i18n when translations load / change ── */ /* ── Apply i18n when translations load / change ── */
document.addEventListener('translationsLoaded', () => { document.addEventListener('translationsLoaded', () => {
if (i18n?.translatePage) i18n.translatePage(); i18n.translatePage();
// Re-render dynamic content that uses t() // Re-render dynamic content that uses t()
loadDashboard(); loadDashboard();
if (activeTabName === 'users') loadUsers(); if (activeTabName === 'users') loadUsers();
}); });
document.addEventListener('localeChanged', () => { document.addEventListener('localeChanged', () => {
if (i18n?.translatePage) i18n.translatePage(); i18n.translatePage();
loadDashboard(); loadDashboard();
if (activeTabName === 'users') loadUsers(); if (activeTabName === 'users') loadUsers();
}); });
+2 -3
View File
@@ -3,10 +3,9 @@ import { i18n } from '../../core/i18n.js';
const API = '/api'; const API = '/api';
/* ── i18n helper — falls back to key if i18n not ready ── */ /* ── i18n helper ── */
function t(key, params) { function t(key, params) {
if (i18n && typeof i18n.t === 'function') return i18n.t(key, params); return i18n.t(key, params);
return key.split('.').pop().replace(/_/g, ' ');
} }
function headers() { function headers() {
+2 -5
View File
@@ -220,9 +220,7 @@ const sharedView = {
</div> </div>
`; `;
if (i18n?.translateElement) { i18n.translateElement(container);
i18n.translateElement(container);
}
}, },
// Attach event listeners // Attach event listeners
@@ -662,8 +660,7 @@ const sharedView = {
}, },
translate(key, defaultText) { translate(key, defaultText) {
if (i18n?.t) return i18n.t(key, defaultText); return i18n.t(key, defaultText);
return defaultText;
} }
}; };