Start i18n Integration

This commit is contained in:
Ryan
2025-04-08 18:40:01 -04:00
committed by GitHub
parent 7aa4fe142a
commit a6c4c1d39c
17 changed files with 662 additions and 144 deletions

View File

@@ -1,4 +1,5 @@
import { sendRequest } from './networkUtils.js';
import { t } from './i18n.js';
import {
toggleVisibility,
showToast as originalShowToast,
@@ -34,8 +35,9 @@ window.currentOIDCConfig = currentOIDCConfig;
window.pendingTOTP = new URLSearchParams(window.location.search).get('totp_required') === '1';
// override showToast to suppress the "Please log in to continue." toast during TOTP
function showToast(msg) {
if (window.pendingTOTP && msg === "Please log in to continue.") {
function showToast(msgKey) {
const msg = t(msgKey);
if (window.pendingTOTP && msgKey === "please_log_in_to_continue") {
return;
}
originalShowToast(msg);

View File

@@ -1,5 +1,6 @@
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
import { sendRequest } from './networkUtils.js';
import { t, applyTranslations, setLocale } from './i18n.js';
const version = "v1.0.9";
const adminTitle = `Admin Panel <small style="font-size: 12px; color: gray;">${version}</small>`;
@@ -32,14 +33,14 @@ export function openTOTPLoginModal() {
<div style="background: ${modalBg}; padding:20px; border-radius:8px; text-align:center; position:relative; color:${textColor};">
<span id="closeTOTPLoginModal" style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">&times;</span>
<div id="totpSection">
<h3>Enter TOTP Code</h3>
<h3>${t("enter_totp_code")}</h3>
<input type="text" id="totpLoginInput" maxlength="6"
style="font-size:24px; text-align:center; width:100%; padding:10px;"
placeholder="6-digit code" />
</div>
<a href="#" id="toggleRecovery" style="display:block; margin-top:10px; font-size:14px;">Use Recovery Code instead</a>
<a href="#" id="toggleRecovery" style="display:block; margin-top:10px; font-size:14px;">${t("use_recovery_code_instead")}</a>
<div id="recoverySection" style="display:none; margin-top:10px;">
<h3>Enter Recovery Code</h3>
<h3>${t("enter_recovery_code")}</h3>
<input type="text" id="recoveryInput"
style="font-size:24px; text-align:center; width:100%; padding:10px;"
placeholder="Recovery code" />
@@ -168,6 +169,8 @@ export function openUserPanel() {
transform: none;
transition: none;
`;
// Retrieve the language setting from local storage, default to English ("en")
const savedLanguage = localStorage.getItem("language") || "en";
if (!userPanelModal) {
userPanelModal = document.createElement("div");
userPanelModal.id = "userPanelModal";
@@ -195,15 +198,29 @@ export function openUserPanel() {
<input type="checkbox" id="userTOTPEnabled" style="vertical-align: middle;" />
</div>
</fieldset>
<fieldset style="margin-bottom: 15px;">
<legend>Language</legend>
<div class="form-group">
<label for="languageSelector">Select Language:</label>
<select id="languageSelector">
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
</select>
</div>
</fieldset>
</div>
`;
document.body.appendChild(userPanelModal);
// Close button handler
document.getElementById("closeUserPanel").addEventListener("click", () => {
userPanelModal.style.display = "none";
});
// Change Password button
document.getElementById("openChangePasswordModalBtn").addEventListener("click", () => {
document.getElementById("changePasswordModal").style.display = "block";
});
// TOTP checkbox behavior
const totpCheckbox = document.getElementById("userTOTPEnabled");
totpCheckbox.checked = localStorage.getItem("userTOTPEnabled") === "true";
totpCheckbox.addEventListener("change", function () {
@@ -228,7 +245,17 @@ export function openUserPanel() {
})
.catch(() => { showToast("Error updating TOTP setting."); });
});
// Language dropdown initialization
const languageSelector = document.getElementById("languageSelector");
languageSelector.value = savedLanguage;
languageSelector.addEventListener("change", function () {
const selectedLanguage = this.value;
localStorage.setItem("language", selectedLanguage);
setLocale(selectedLanguage);
applyTranslations();
});
} else {
// If the modal already exists, update its colors
userPanelModal.style.backgroundColor = overlayBackground;
const modalContent = userPanelModal.querySelector(".modal-content");
modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff";

View File

@@ -1,4 +1,5 @@
// domUtils.js
import { t } from './i18n.js';
// Basic DOM Helpers
export function toggleVisibility(elementId, shouldShow) {
@@ -6,7 +7,7 @@ export function toggleVisibility(elementId, shouldShow) {
if (element) {
element.style.display = shouldShow ? "block" : "none";
} else {
console.error(`Element with id "${elementId}" not found.`);
console.error(t("element_not_found", { id: elementId }));
}
}
@@ -97,7 +98,7 @@ export function buildSearchAndPaginationControls({ currentPage, totalPages, sear
<i class="material-icons">search</i>
</span>
</div>
<input type="text" id="searchInput" class="form-control" placeholder="Search files or tag..." value="${safeSearchTerm}" aria-describedby="searchIcon">
<input type="text" id="searchInput" class="form-control" placeholder="${t("search_placeholder")}" value="${safeSearchTerm}" aria-describedby="searchIcon">
</div>
</div>
<div class="col-12 col-md-4 text-left">
@@ -117,11 +118,11 @@ export function buildFileTableHeader(sortOrder) {
<thead>
<tr>
<th class="checkbox-col"><input type="checkbox" id="selectAll" onclick="toggleAllCheckboxes(this)"></th>
<th data-column="name" class="sortable-col">File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="modified" class="hide-small sortable-col">Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="uploaded" class="hide-small hide-medium sortable-col">Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="size" class="hide-small sortable-col">File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="uploader" class="hide-small hide-medium sortable-col">Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="name" class="sortable-col">${t("file_name")} ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="modified" class="hide-small sortable-col">${t("date_modified")} ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="uploaded" class="hide-small hide-medium sortable-col">${t("upload_date")} ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="size" class="hide-small sortable-col">${t("file_size")} ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th data-column="uploader" class="hide-small hide-medium sortable-col">${t("uploader")} ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
<th>Actions</th>
</tr>
</thead>

View File

@@ -2,18 +2,20 @@
import { showToast, attachEnterKeyListener } from './domUtils.js';
import { loadFileList } from './fileListView.js';
import { formatFolderName } from './fileListView.js';
import { t } from './i18n.js';
export function handleDeleteSelected(e) {
e.preventDefault();
e.stopImmediatePropagation();
const checkboxes = document.querySelectorAll(".file-checkbox:checked");
if (checkboxes.length === 0) {
showToast("No files selected.");
showToast("no_files_selected");
return;
}
window.filesToDelete = Array.from(checkboxes).map(chk => chk.value);
document.getElementById("deleteFilesMessage").textContent =
"Are you sure you want to delete " + window.filesToDelete.length + " selected file(s)?";
const count = window.filesToDelete.length;
document.getElementById("deleteFilesMessage").textContent = t("confirm_delete_files", { count: count });
document.getElementById("deleteFilesModal").style.display = "block";
attachEnterKeyListener("deleteFilesModal", "confirmDeleteFiles");
}

View File

@@ -1,6 +1,7 @@
// editor.js
import { escapeHTML, showToast } from './domUtils.js';
import { loadFileList } from './fileListView.js';
import { t } from './i18n.js';
function getModeForFile(fileName) {
const ext = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase();
@@ -73,17 +74,17 @@ export function editFile(fileName, folder) {
modal.classList.add("modal", "editor-modal");
modal.innerHTML = `
<div class="editor-header">
<h3 class="editor-title">Editing: ${escapeHTML(fileName)}</h3>
<h3 class="editor-title">${t("editing")}: ${escapeHTML(fileName)}</h3>
<div class="editor-controls">
<button id="decreaseFont" class="btn btn-sm btn-secondary">A-</button>
<button id="increaseFont" class="btn btn-sm btn-secondary">A+</button>
<button id="decreaseFont" class="btn btn-sm btn-secondary">${t("decrease_font")}</button>
<button id="increaseFont" class="btn btn-sm btn-secondary">${t("increase_font")}</button>
</div>
<button id="closeEditorX" class="editor-close-btn">&times;</button>
</div>
<textarea id="fileEditor" class="editor-textarea">${escapeHTML(content)}</textarea>
<div class="editor-footer">
<button id="saveBtn" class="btn btn-primary">Save</button>
<button id="closeBtn" class="btn btn-secondary">Close</button>
<button id="saveBtn" class="btn btn-primary">${t("save")}</button>
<button id="closeBtn" class="btn btn-secondary">${t("close")}</button>
</div>
`;
document.body.appendChild(modal);

View File

@@ -12,7 +12,7 @@ import {
toggleRowSelection,
attachEnterKeyListener
} from './domUtils.js';
import { t } from './i18n.js';
import { bindFileListContextMenu } from './fileMenu.js';
export let fileData = [];
@@ -36,12 +36,12 @@ export function createViewToggleButton() {
titleElem.parentNode.insertBefore(toggleBtn, titleElem.nextSibling);
}
}
toggleBtn.textContent = window.viewMode === "gallery" ? "Switch to Table View" : "Switch to Gallery View";
toggleBtn.textContent = window.viewMode === "gallery" ? t("switch_to_table_view") : t("switch_to_gallery_view");
toggleBtn.onclick = () => {
window.viewMode = window.viewMode === "gallery" ? "table" : "gallery";
localStorage.setItem("viewMode", window.viewMode);
loadFileList(window.currentFolder);
toggleBtn.textContent = window.viewMode === "gallery" ? "Switch to Table View" : "Switch to Gallery View";
toggleBtn.textContent = window.viewMode === "gallery" ? t("switch_to_table_view") : t("switch_to_gallery_view");
};
return toggleBtn;
}
@@ -97,7 +97,7 @@ export function loadFileList(folderParam) {
renderFileTable(folder);
}
} else {
fileListContainer.textContent = "No files found.";
fileListContainer.textContent = t("no_files_found");
updateFileActionButtons();
}
return data.files || [];

View File

@@ -5,6 +5,7 @@ import { previewFile } from './filePreview.js';
import { editFile } from './fileEditor.js';
import { canEditFile, fileData } from './fileListView.js';
import { openTagModal, openMultiTagModal } from './fileTags.js';
import { t } from './i18n.js';
export function showFileContextMenu(x, y, menuItems) {
let menu = document.getElementById("fileContextMenu");
@@ -74,22 +75,22 @@ export function fileListContextMenuHandler(e) {
const selected = Array.from(document.querySelectorAll("#fileList .file-checkbox:checked")).map(chk => chk.value);
let menuItems = [
{ label: "Delete Selected", action: () => { handleDeleteSelected(new Event("click")); } },
{ label: "Copy Selected", action: () => { handleCopySelected(new Event("click")); } },
{ label: "Move Selected", action: () => { handleMoveSelected(new Event("click")); } },
{ label: "Download Zip", action: () => { handleDownloadZipSelected(new Event("click")); } }
{ label: t("delete_selected"), action: () => { handleDeleteSelected(new Event("click")); } },
{ label: t("copy_selected"), action: () => { handleCopySelected(new Event("click")); } },
{ label: t("move_selected"), action: () => { handleMoveSelected(new Event("click")); } },
{ label: t("download_zip"), action: () => { handleDownloadZipSelected(new Event("click")); } }
];
if (selected.some(name => name.toLowerCase().endsWith(".zip"))) {
menuItems.push({
label: "Extract Zip",
label: t("extract_zip"),
action: () => { handleExtractZipSelected(new Event("click")); }
});
}
if (selected.length > 1) {
menuItems.push({
label: "Tag Selected",
label: t("tag_selected"),
action: () => {
const files = fileData.filter(f => selected.includes(f.name));
openMultiTagModal(files);
@@ -100,7 +101,7 @@ export function fileListContextMenuHandler(e) {
const file = fileData.find(f => f.name === selected[0]);
menuItems.push({
label: "Preview",
label: t("preview"),
action: () => {
const folder = window.currentFolder || "root";
const folderPath = folder === "root"
@@ -112,18 +113,18 @@ export function fileListContextMenuHandler(e) {
if (canEditFile(file.name)) {
menuItems.push({
label: "Edit",
label: t("edit"),
action: () => { editFile(selected[0], window.currentFolder); }
});
}
menuItems.push({
label: "Rename",
label: t("rename"),
action: () => { renameFile(selected[0], window.currentFolder); }
});
menuItems.push({
label: "Tag File",
label: t("tag_file"),
action: () => { openTagModal(file); }
});
}

View File

@@ -1,6 +1,7 @@
// filePreview.js
import { escapeHTML, showToast } from './domUtils.js';
import { fileData } from './fileListView.js';
import { t } from './i18n.js';
export function openShareModal(file, folder) {
const existing = document.getElementById("shareModal");
@@ -12,11 +13,11 @@ export function openShareModal(file, folder) {
modal.innerHTML = `
<div class="modal-content share-modal-content" style="width: 600px; max-width:90vw;">
<div class="modal-header">
<h3>Share File: ${escapeHTML(file.name)}</h3>
<h3>${t("share_file")}: ${escapeHTML(file.name)}</h3>
<span class="close-image-modal" id="closeShareModal" title="Close">&times;</span>
</div>
<div class="modal-body">
<p>Set Expiration:</p>
<p>${t("set_expiration")}</p>
<select id="shareExpiration">
<option value="30">30 minutes</option>
<option value="60" selected>60 minutes</option>
@@ -26,13 +27,13 @@ export function openShareModal(file, folder) {
<option value="1440">1 Day</option>
</select>
<p>Password (optional):</p>
<input type="text" id="sharePassword" placeholder="No password by default" style="width: 100%;"/>
<input type="text" id="sharePassword" placeholder=${t("password_optional")} style="width: 100%;"/>
<br>
<button id="generateShareLinkBtn" class="btn btn-primary" style="margin-top:10px;">Generate Share Link</button>
<button id="generateShareLinkBtn" class="btn btn-primary" style="margin-top:10px;">${t("generate_share_link")}</button>
<div id="shareLinkDisplay" style="margin-top: 10px; display:none;">
<p>Shareable Link:</p>
<p>${t("shareable_link")}</p>
<input type="text" id="shareLinkInput" readonly style="width:100%;"/>
<button id="copyShareLinkBtn" class="btn btn-primary" style="margin-top:5px;">Copy Link</button>
<button id="copyShareLinkBtn" class="btn btn-primary" style="margin-top:5px;">${t("copy_link")}</button>
</div>
</div>
</div>

View File

@@ -4,6 +4,7 @@
// updating the file row display with tag badges,
// filtering the file list by tag, and persisting tag data.
import { escapeHTML } from './domUtils.js';
import { t } from './i18n.js';
export function openTagModal(file) {
// Create the modal element.
@@ -13,14 +14,14 @@ export function openTagModal(file) {
modal.innerHTML = `
<div class="modal-content" style="width: 400px; max-width:90vw;">
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0;">Tag File: ${file.name}</h3>
<h3 style="margin:0;">${t("tag_file")}: ${file.name}</h3>
<span id="closeTagModal" style="cursor:pointer; font-size:24px;">&times;</span>
</div>
<div class="modal-body" style="margin-top:10px;">
<label for="tagNameInput">Tag Name:</label>
<label for="tagNameInput">${t("tag_name")}</label>
<input type="text" id="tagNameInput" placeholder="Enter tag name" style="width:100%; padding:5px;"/>
<br><br>
<label for="tagColorInput">Tag Color:</label>
<label for="tagColorInput">${t("tag_name")}</label>
<input type="color" id="tagColorInput" value="#ff0000" style="width:100%; padding:5px;"/>
<br><br>
<div id="customTagDropdown" style="max-height:150px; overflow-y:auto; border:1px solid #ccc; margin-top:5px; padding:5px;">
@@ -28,7 +29,7 @@ export function openTagModal(file) {
</div>
<br>
<div style="text-align:right;">
<button id="saveTagBtn" class="btn btn-primary">Save Tag</button>
<button id="saveTagBtn" class="btn btn-primary">${t("save_tag")}</button>
</div>
<div id="currentTags" style="margin-top:10px; font-size:0.9em;">
<!-- Existing tags will be listed here -->

View File

@@ -2,6 +2,7 @@
import { loadFileList } from './fileListView.js';
import { showToast, escapeHTML, attachEnterKeyListener } from './domUtils.js';
import { t } from './i18n.js';
/* ----------------------
Helper Functions (Data/State)
@@ -112,7 +113,7 @@ function breadcrumbClickHandler(e) {
// Update the container with sanitized breadcrumbs.
const container = document.getElementById("fileListTitle");
const sanitizedBreadcrumb = DOMPurify.sanitize(renderBreadcrumb(folder));
container.innerHTML = "Files in (" + sanitizedBreadcrumb + ")";
container.innerHTML = t("files_in") + " (" + sanitizedBreadcrumb + ")";
expandTreePath(folder);
document.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));

449
js/i18n.js Normal file
View File

@@ -0,0 +1,449 @@
/* i18n.js */
const translations = {
en: { /* English translations */
"please_log_in_to_continue": "Please log in to continue.",
"no_files_selected": "No files selected.",
"confirm_delete_files": "Are you sure you want to delete {count} selected file(s)?",
"element_not_found": "Element with id \"{id}\" not found.",
"search_placeholder": "Search files or tag...",
"file_name": "File Name",
"date_modified": "Date Modified",
"upload_date": "Upload Date",
"file_size": "File Size",
"uploader": "Uploader",
"enter_totp_code": "Enter TOTP Code",
"use_recovery_code_instead": "Use Recovery Code instead",
"enter_recovery_code": "Enter Recovery Code",
"editing": "Editing",
"decrease_font": "A-",
"increase_font": "A+",
"save": "Save",
"close": "Close",
"no_files_found": "No files found.",
"switch_to_table_view": "Switch to Table View",
"switch_to_gallery_view": "Switch to Gallery View",
"share_file": "Share File",
"set_expiration": "Set Expiration:",
"password_optional": "Password (optional):",
"generate_share_link": "Generate Share Link",
"shareable_link": "Shareable Link:",
"copy_link": "Copy Link",
"tag_file": "Tag File",
"tag_name": "Tag Name:",
"tag_color": "Tag Color:",
"save_tag": "Save Tag",
"files_in": "Files in",
"light_mode": "Light Mode",
"dark_mode": "Dark Mode",
"upload_instruction": "Drop files/folders here or click 'Choose files'",
"no_files_selected_default": "No files selected",
"choose_files": "Choose files",
"delete_selected": "Delete Selected",
"copy_selected": "Copy Selected",
"move_selected": "Move Selected",
"tag_selected": "Tag Selected",
"download_zip": "Download Zip",
"extract_zip": "Extract Zip",
"preview": "Preview",
"edit": "Edit",
"rename": "Rename",
"trash_empty": "Trash is empty.",
"no_trash_selected": "No trash items selected for restore.",
// Additional keys for HTML translations:
"title": "FileRise",
"header_title": "FileRise",
"logout": "Logout",
"change_password": "Change Password",
"restore_text": "Restore or",
"delete_text": "Delete Trash Items",
"restore_selected": "Restore Selected",
"restore_all": "Restore All",
"delete_selected_trash": "Delete Selected",
"delete_all": "Delete All",
"upload_header": "Upload Files/Folders",
// Folder Management keys:
"folder_navigation": "Folder Navigation & Management",
"create_folder": "Create Folder",
"create_folder_title": "Create Folder",
"enter_folder_name": "Enter folder name",
"cancel": "Cancel",
"create": "Create",
"rename_folder": "Rename Folder",
"rename_folder_title": "Rename Folder",
"rename_folder_placeholder": "Enter new folder name",
"delete_folder": "Delete Folder",
"delete_folder_title": "Delete Folder",
"delete_folder_message": "Are you sure you want to delete this folder?",
"folder_help": "Folder Help",
"folder_help_item_1": "Click on a folder in the tree to view its files.",
"folder_help_item_2": "Use [-] to collapse and [+] to expand folders.",
"folder_help_item_3": "Select a folder and click \"Create Folder\" to add a subfolder.",
"folder_help_item_4": "To rename or delete a folder, select it and then click the appropriate button.",
// File List keys:
"file_list_title": "Files in (Root)",
"delete_files": "Delete Files",
"delete_selected_files_title": "Delete Selected Files",
"delete_files_message": "Are you sure you want to delete the selected files?",
"copy_files": "Copy Files",
"copy_files_title": "Copy Selected Files",
"copy_files_message": "Select a target folder for copying the selected files:",
"move_files": "Move Files",
"move_files_title": "Move Selected Files",
"move_files_message": "Select a target folder for moving the selected files:",
"move": "Move",
"extract_zip_button": "Extract Zip",
"download_zip_title": "Download Selected Files as Zip",
"download_zip_prompt": "Enter a name for the zip file:",
"zip_placeholder": "files.zip",
// Login Form keys:
"login": "Login",
"remember_me": "Remember me",
"login_oidc": "Login with OIDC",
"basic_http_login": "Use Basic HTTP Login",
// Change Password keys:
"change_password_title": "Change Password",
"old_password": "Old Password",
"new_password": "New Password",
"confirm_new_password": "Confirm New Password",
// Add User keys:
"create_new_user_title": "Create New User",
"username": "Username:",
"password": "Password:",
"grant_admin": "Grant Admin Access",
"save_user": "Save User",
// Remove User keys:
"remove_user_title": "Remove User",
"select_user_remove": "Select a user to remove:",
"delete_user": "Delete User",
// Rename File keys:
"rename_file_title": "Rename File",
"rename_file_placeholder": "Enter new file name",
// Custom Confirm Modal keys:
"yes": "Yes",
"no": "No",
"delete": "Delete",
"download": "Download",
"upload": "Upload",
"copy": "Copy",
"extract": "Extract",
// Dark Mode Toggle
"dark_mode_toggle": "Dark Mode"
},
es: { /* Spanish translations */
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
"no_files_selected": "No se seleccionaron archivos.",
"confirm_delete_files": "¿Está seguro de que desea eliminar {count} archivo(s) seleccionado(s)?",
"element_not_found": "Elemento con id \"{id}\" no encontrado.",
"search_placeholder": "Buscar archivos o etiqueta...",
"file_name": "Nombre del archivo",
"date_modified": "Fecha de modificación",
"upload_date": "Fecha de carga",
"file_size": "Tamaño del archivo",
"uploader": "Cargado por",
"enter_totp_code": "Ingrese el código TOTP",
"use_recovery_code_instead": "Usar código de recuperación en su lugar",
"enter_recovery_code": "Ingrese el código de recuperación",
"editing": "Editando",
"decrease_font": "A-",
"increase_font": "A+",
"save": "Guardar",
"close": "Cerrar",
"no_files_found": "No se encontraron archivos.",
"switch_to_table_view": "Cambiar a vista de tabla",
"switch_to_gallery_view": "Cambiar a vista de galería",
"share_file": "Compartir archivo",
"set_expiration": "Establecer vencimiento:",
"password_optional": "Contraseña (opcional):",
"generate_share_link": "Generar enlace para compartir",
"shareable_link": "Enlace para compartir:",
"copy_link": "Copiar enlace",
"tag_file": "Etiquetar archivo",
"tag_name": "Nombre de la etiqueta:",
"tag_color": "Color de la etiqueta:",
"save_tag": "Guardar etiqueta",
"files_in": "Archivos en",
"light_mode": "Modo claro",
"dark_mode": "Modo oscuro",
"upload_instruction": "Suelte archivos/carpetas o haga clic en 'Elegir archivos'",
"no_files_selected_default": "No se seleccionaron archivos",
"choose_files": "Elegir archivos",
"delete_selected": "Eliminar seleccionados",
"copy_selected": "Copiar seleccionados",
"move_selected": "Mover seleccionados",
"tag_selected": "Etiquetar seleccionados",
"download_zip": "Descargar Zip",
"extract_zip": "Extraer Zip",
"preview": "Vista previa",
"edit": "Editar",
"rename": "Renombrar",
"trash_empty": "La papelera está vacía.",
"no_trash_selected": "No se seleccionaron elementos de la papelera para restaurar.",
// Additional keys for HTML translations:
"title": "FileRise",
"header_title": "FileRise",
"logout": "Cerrar sesión",
"change_password": "Cambiar contraseña",
"restore_text": "Restaurar o",
"delete_text": "Eliminar elementos de la papelera",
"restore_selected": "Restaurar seleccionados",
"restore_all": "Restaurar todo",
"delete_selected_trash": "Eliminar seleccionados",
"delete_all": "Eliminar todo",
"upload_header": "Cargar archivos/carpetas",
// Folder Management keys:
"folder_navigation": "Navegación y gestión de carpetas",
"create_folder": "Crear carpeta",
"create_folder_title": "Crear carpeta",
"enter_folder_name": "Ingrese el nombre de la carpeta",
"cancel": "Cancelar",
"create": "Crear",
"rename_folder": "Renombrar carpeta",
"rename_folder_title": "Renombrar carpeta",
"rename_folder_placeholder": "Ingrese el nuevo nombre de la carpeta",
"delete_folder": "Eliminar carpeta",
"delete_folder_title": "Eliminar carpeta",
"delete_folder_message": "¿Está seguro de que desea eliminar esta carpeta?",
"folder_help": "Ayuda de carpetas",
"folder_help_item_1": "Haga clic en una carpeta en el árbol para ver sus archivos.",
"folder_help_item_2": "Utilice [-] para colapsar y [+] para expandir carpetas.",
"folder_help_item_3": "Seleccione una carpeta y haga clic en \"Crear carpeta\" para agregar una subcarpeta.",
"folder_help_item_4": "Para renombrar o eliminar una carpeta, selecciónela y luego haga clic en el botón correspondiente.",
// File List keys:
"file_list_title": "Archivos en (Raíz)",
"delete_files": "Eliminar archivos",
"delete_selected_files_title": "Eliminar archivos seleccionados",
"delete_files_message": "¿Está seguro de que desea eliminar los archivos seleccionados?",
"copy_files": "Copiar archivos",
"copy_files_title": "Copiar archivos seleccionados",
"copy_files_message": "Seleccione una carpeta destino para copiar los archivos seleccionados:",
"move_files": "Mover archivos",
"move_files_title": "Mover archivos seleccionados",
"move_files_message": "Seleccione una carpeta destino para mover los archivos seleccionados:",
"move": "Mover",
"extract_zip_button": "Extraer Zip",
"download_zip_title": "Descargar archivos seleccionados en Zip",
"download_zip_prompt": "Ingrese un nombre para el archivo Zip:",
"zip_placeholder": "archivos.zip",
// Login Form keys:
"login": "Iniciar sesión",
"remember_me": "Recuérdame",
"login_oidc": "Iniciar sesión con OIDC",
"basic_http_login": "Usar autenticación HTTP básica",
// Change Password keys:
"change_password_title": "Cambiar contraseña",
"old_password": "Contraseña antigua",
"new_password": "Nueva contraseña",
"confirm_new_password": "Confirmar nueva contraseña",
// Add User keys:
"create_new_user_title": "Crear nuevo usuario",
"username": "Usuario:",
"password": "Contraseña:",
"grant_admin": "Otorgar acceso de administrador",
"save_user": "Guardar usuario",
// Remove User keys:
"remove_user_title": "Eliminar usuario",
"select_user_remove": "Seleccione un usuario para eliminar:",
"delete_user": "Eliminar usuario",
// Rename File keys:
"rename_file_title": "Renombrar archivo",
"rename_file_placeholder": "Ingrese el nuevo nombre del archivo",
// Custom Confirm Modal keys:
"yes": "Sí",
"no": "No",
"delete": "Eliminar",
"download": "Descargar",
"upload": "Cargar",
"copy": "Copiar",
"extract": "Extraer",
// Dark Mode Toggle
"dark_mode_toggle": "Modo oscuro"
},
fr: { /* French translations */
"please_log_in_to_continue": "Veuillez vous connecter pour continuer.",
"no_files_selected": "Aucun fichier sélectionné.",
"confirm_delete_files": "Êtes-vous sûr de vouloir supprimer {count} fichier(s) sélectionné(s) ?",
"element_not_found": "Élément avec l'id \"{id}\" non trouvé.",
"search_placeholder": "Rechercher des fichiers ou un tag...",
"file_name": "Nom du fichier",
"date_modified": "Date de modification",
"upload_date": "Date de téléchargement",
"file_size": "Taille du fichier",
"uploader": "Téléversé par",
"enter_totp_code": "Entrez le code TOTP",
"use_recovery_code_instead": "Utilisez le code de récupération à la place",
"enter_recovery_code": "Entrez le code de récupération",
"editing": "Modification",
"decrease_font": "A-",
"increase_font": "A+",
"save": "Enregistrer",
"close": "Fermer",
"no_files_found": "Aucun fichier trouvé.",
"switch_to_table_view": "Passer à la vue tableau",
"switch_to_gallery_view": "Passer à la vue galerie",
"share_file": "Partager le fichier",
"set_expiration": "Définir l'expiration :",
"password_optional": "Mot de passe (facultatif) :",
"generate_share_link": "Générer un lien de partage",
"shareable_link": "Lien partageable :",
"copy_link": "Copier le lien",
"tag_file": "Marquer le fichier",
"tag_name": "Nom du tag :",
"tag_color": "Couleur du tag :",
"save_tag": "Enregistrer le tag",
"files_in": "Fichiers dans",
"light_mode": "Mode clair",
"dark_mode": "Mode sombre",
"upload_instruction": "Déposez vos fichiers/dossiers ici ou cliquez sur 'Choisir des fichiers'",
"no_files_selected_default": "Aucun fichier sélectionné",
"choose_files": "Choisir des fichiers",
"delete_selected": "Supprimer la sélection",
"copy_selected": "Copier la sélection",
"move_selected": "Déplacer la sélection",
"tag_selected": "Marquer la sélection",
"download_zip": "Télécharger en Zip",
"extract_zip": "Extraire le Zip",
"preview": "Aperçu",
"edit": "Modifier",
"rename": "Renommer",
"trash_empty": "La corbeille est vide.",
"no_trash_selected": "Aucun élément de la corbeille sélectionné pour restauration.",
// Additional keys for HTML translations:
"title": "FileRise",
"header_title": "FileRise",
"logout": "Se déconnecter",
"change_password": "Changer le mot de passe",
"restore_text": "Restaurer ou",
"delete_text": "Supprimer les éléments de la corbeille",
"restore_selected": "Restaurer la sélection",
"restore_all": "Restaurer tout",
"delete_selected_trash": "Supprimer la sélection",
"delete_all": "Supprimer tout",
"upload_header": "Téléverser des fichiers/dossiers",
// Folder Management keys:
"folder_navigation": "Navigation et gestion des dossiers",
"create_folder": "Créer un dossier",
"create_folder_title": "Créer un dossier",
"enter_folder_name": "Entrez le nom du dossier",
"cancel": "Annuler",
"create": "Créer",
"rename_folder": "Renommer le dossier",
"rename_folder_title": "Renommer le dossier",
"rename_folder_placeholder": "Entrez le nouveau nom du dossier",
"delete_folder": "Supprimer le dossier",
"delete_folder_title": "Supprimer le dossier",
"delete_folder_message": "Êtes-vous sûr de vouloir supprimer ce dossier ?",
"folder_help": "Aide des dossiers",
"folder_help_item_1": "Cliquez sur un dossier dans l'arborescence pour voir ses fichiers.",
"folder_help_item_2": "Utilisez [-] pour réduire et [+] pour développer les dossiers.",
"folder_help_item_3": "Sélectionnez un dossier et cliquez sur \"Créer un dossier\" pour ajouter un sous-dossier.",
"folder_help_item_4": "Pour renommer ou supprimer un dossier, sélectionnez-le puis cliquez sur le bouton approprié.",
// File List keys:
"file_list_title": "Fichiers dans (Racine)",
"delete_files": "Supprimer les fichiers",
"delete_selected_files_title": "Supprimer les fichiers sélectionnés",
"delete_files_message": "Êtes-vous sûr de vouloir supprimer les fichiers sélectionnés ?",
"copy_files": "Copier les fichiers",
"copy_files_title": "Copier les fichiers sélectionnés",
"copy_files_message": "Sélectionnez un dossier de destination pour copier les fichiers sélectionnés :",
"move_files": "Déplacer les fichiers",
"move_files_title": "Déplacer les fichiers sélectionnés",
"move_files_message": "Sélectionnez un dossier de destination pour déplacer les fichiers sélectionnés :",
"move": "Déplacer",
"extract_zip_button": "Extraire le Zip",
"download_zip_title": "Télécharger les fichiers sélectionnés en Zip",
"download_zip_prompt": "Entrez un nom pour le fichier Zip :",
"zip_placeholder": "fichiers.zip",
// Login Form keys:
"login": "Connexion",
"remember_me": "Se souvenir de moi",
"login_oidc": "Connexion avec OIDC",
"basic_http_login": "Utiliser l'authentification HTTP basique",
// Change Password keys:
"change_password_title": "Changer le mot de passe",
"old_password": "Ancien mot de passe",
"new_password": "Nouveau mot de passe",
"confirm_new_password": "Confirmer le nouveau mot de passe",
// Add User keys:
"create_new_user_title": "Créer un nouvel utilisateur",
"username": "Nom d'utilisateur :",
"password": "Mot de passe :",
"grant_admin": "Accorder les droits d'administrateur",
"save_user": "Enregistrer l'utilisateur",
// Remove User keys:
"remove_user_title": "Supprimer l'utilisateur",
"select_user_remove": "Sélectionnez un utilisateur à supprimer :",
"delete_user": "Supprimer l'utilisateur",
// Rename File keys:
"rename_file_title": "Renommer le fichier",
"rename_file_placeholder": "Entrez le nouveau nom du fichier",
// Custom Confirm Modal keys:
"yes": "Oui",
"no": "Non",
"delete": "Supprimer",
"download": "Télécharger",
"upload": "Téléverser",
"copy": "Copier",
"extract": "Extraire",
// Dark Mode Toggle
"dark_mode_toggle": "Mode sombre"
}
};
let currentLocale = 'en';
export function setLocale(locale) {
currentLocale = locale;
}
export function t(key, placeholders) {
const localeTranslations = translations[currentLocale] || {};
let translation = localeTranslations[key] || key;
if (placeholders) {
Object.keys(placeholders).forEach(ph => {
translation = translation.replace(`{${ph}}`, placeholders[ph]);
});
}
return translation;
}
export function applyTranslations() {
document.querySelectorAll('[data-i18n-key]').forEach(el => {
el.innerText = t(el.getAttribute('data-i18n-key'));
});
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
el.setAttribute('placeholder', t(el.getAttribute('data-i18n-placeholder')));
});
document.querySelectorAll('[data-i18n-title]').forEach(el => {
el.setAttribute('title', t(el.getAttribute('data-i18n-title')));
});
}

View File

@@ -10,6 +10,7 @@ import { displayFilePreview } from './filePreview.js';
import { loadFileList } from './fileListView.js';
import { initFileActions, renameFile } from './fileActions.js';
import { editFile, saveFile } from './fileEditor.js';
import { t, applyTranslations, setLocale } from './i18n.js';
function loadCsrfTokenWithRetry(retries = 3, delay = 1000) {
return fetch('token.php', { credentials: 'include' })
@@ -55,6 +56,7 @@ function loadCsrfTokenWithRetry(retries = 3, delay = 1000) {
});
}
// Expose functions for inline handlers.
window.sendRequest = sendRequest;
window.toggleVisibility = toggleVisibility;
@@ -67,6 +69,12 @@ window.renameFile = renameFile;
window.currentFolder = "root";
document.addEventListener("DOMContentLoaded", function () {
// Retrieve the saved language from localStorage; default to "en"
const savedLanguage = localStorage.getItem("language") || "en";
// Set the locale based on the saved language
setLocale(savedLanguage);
// Apply the translations to update the UI
applyTranslations();
// First, load the CSRF token (with retry).
loadCsrfTokenWithRetry().then(() => {
// Once CSRF token is loaded, initialize authentication.
@@ -104,7 +112,7 @@ document.addEventListener("DOMContentLoaded", function () {
// Other DOM initialization that can happen after CSRF is ready.
const newPasswordInput = document.getElementById("newPassword");
if (newPasswordInput) {
newPasswordInput.addEventListener("input", function() {
newPasswordInput.addEventListener("input", function () {
console.log("newPassword input event:", this.value);
});
} else {
@@ -149,10 +157,10 @@ document.addEventListener("DOMContentLoaded", function () {
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => {
if (event.matches) {
document.body.classList.add("dark-mode");
if (darkModeToggle) darkModeToggle.textContent = "Light Mode";
if (darkModeToggle) darkModeToggle.textContent = t("light_mode");
} else {
document.body.classList.remove("dark-mode");
if (darkModeToggle) darkModeToggle.textContent = "Dark Mode";
if (darkModeToggle) darkModeToggle.textContent = t("dark_mode");
}
});
}

View File

@@ -3,6 +3,7 @@ import { sendRequest } from './networkUtils.js';
import { toggleVisibility, showToast } from './domUtils.js';
import { loadFileList } from './fileListView.js';
import { loadFolderTree } from './folderManager.js';
import { t } from './i18n.js';
function showConfirm(message, onConfirm) {
const modal = document.getElementById("customConfirmModal");
@@ -65,7 +66,7 @@ export function setupTrashRestoreDelete() {
const files = Array.from(selected).map(chk => chk.value);
console.log("Restore Selected clicked, files:", files);
if (files.length === 0) {
showToast("No trash items selected for restore.");
showToast(t("no_trash_selected"));
return;
}
fetch("restoreFiles.php", {
@@ -105,7 +106,7 @@ export function setupTrashRestoreDelete() {
const files = Array.from(allChk).map(chk => chk.value);
console.log("Restore All clicked, files:", files);
if (files.length === 0) {
showToast("Trash is empty.");
showToast(t("trash_empty"));
return;
}
fetch("restoreFiles.php", {

View File

@@ -3,6 +3,7 @@ import { displayFilePreview } from './filePreview.js';
import { showToast, escapeHTML } from './domUtils.js';
import { loadFolderTree } from './folderManager.js';
import { loadFileList } from './fileListView.js';
import { t } from './i18n.js';
/* -----------------------------------------------------
Helpers for DragandDrop Folder Uploads (Original Code)
@@ -55,14 +56,14 @@ function setDropAreaDefault() {
if (dropArea) {
dropArea.innerHTML = `
<div id="uploadInstruction" class="upload-instruction">
Drop files/folders here or click 'Choose files'
${t("upload_instruction")}
</div>
<div id="uploadFileRow" class="upload-file-row">
<button id="customChooseBtn" type="button">Choose files</button>
<button id="customChooseBtn" type="button">${t("choose_files")}</button>
</div>
<div id="fileInfoWrapper" class="file-info-wrapper">
<div id="fileInfoContainer" class="file-info-container">
<span id="fileInfoDefault">No files selected</span>
<span id="fileInfoDefault"> ${t("no_files_selected_default")}</span>
</div>
</div>
<!-- File input for file picker (files only) -->