Start i18n Integration
This commit is contained in:
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## Changes 4/8/2025
|
||||
|
||||
**May have missed some stuff or could have bugs. Please report any issue you may encounter.**
|
||||
|
||||
- **i18n Integration:**
|
||||
- Implemented a complete internationalization (i18n) system for all user-facing texts in FileRise.
|
||||
- Created an `i18n.js` module containing a translations object with full keys for English (en), Spanish (es), and French (fr).
|
||||
- Updated JavaScript code to replace hard-coded strings with the `t()` translation function.
|
||||
- Enhanced HTML and modal templates to support dynamic language translations using data attributes (data-i18n-key, data-i18n-placeholder, etc.).
|
||||
|
||||
- **Language Dropdown & Persistence:**
|
||||
- Added a language dropdown to the user panel modal allowing users to select their preferred language.
|
||||
- Persisted the selected language in localStorage, ensuring that the preferred language is automatically applied on page refresh.
|
||||
- Updated main.js to load and set the user’s language preference on DOMContentLoaded by calling `setLocale()` and `applyTranslations()`.
|
||||
|
||||
- **Bug Fixes & Improvements:**
|
||||
- Fixed issues with evaluation of translation function calls in template literals (ensured proper syntax with `${t("key")}`).
|
||||
- Updated the t() function to be more defensive against missing keys.
|
||||
- Provided instructions and code examples to ensure the language change settings are reliably saved and applied across sessions.
|
||||
|
||||
---
|
||||
|
||||
## Changes 4/7/2025 v1.0.9
|
||||
|
||||
- TOTP one time recovery code added
|
||||
|
||||
@@ -26,6 +26,8 @@ Upload, organize, and share files through a sleek web interface. **FileRise** is
|
||||
|
||||
- 🎨 **Responsive UI (Dark/Light Mode):** FileRise is mobile-friendly out of the box – manage files from your phone or tablet with a responsive layout. Choose between Dark mode or Light theme, or let it follow your system preference. The interface remembers your preferences (layout, items per page, last visited folder, etc.) for a personalized experience each time.
|
||||
|
||||
- 🌐 **Internationalization & Localization:** FileRise supports multiple languages via an integrated i18n system. Users can switch languages through a user panel dropdown, and their choice is saved in local storage for a consistent experience across sessions. Currently available in English, Spanish, and French—please report any translation issues you encounter.
|
||||
|
||||
- 🗑️ **Trash & File Recovery:** Mistakenly deleted files? No worries – deleted items go to the Trash instead of immediate removal. Admins can restore files from Trash or empty it to free space. FileRise auto-purges old trash entries (default 3 days) to keep your storage tidy.
|
||||
|
||||
- ⚙️ **Lightweight & Self-Contained:** FileRise runs on PHP 8.1+ with no external database required – data is stored in files (users, metadata) for simplicity. It’s a single-folder web app you can drop into any Apache/PHP server or run as a container. Docker & Unraid ready: use our pre-built image for a hassle-free setup. Memory and CPU footprint is minimal, yet the app scales to thousands of files with pagination and sorting features.
|
||||
|
||||
180
index.html
180
index.html
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>FileRise</title>
|
||||
<title data-i18n-key="title">FileRise</title>
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('logout') === '1') {
|
||||
@@ -100,47 +100,47 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-title">
|
||||
<h1>FileRise</h1>
|
||||
<h1 data-i18n-key="header_title">FileRise</h1>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="header-buttons-wrapper" style="display: flex; align-items: center; gap: 10px;">
|
||||
<!-- Your header drop zone -->
|
||||
<div id="headerDropArea" class="header-drop-zone"></div>
|
||||
<div class="header-buttons">
|
||||
<button id="logoutBtn" title="Logout">
|
||||
<button id="logoutBtn" data-i18n-title="logout">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
</button>
|
||||
<button id="changePasswordBtn" title="Change Password" style="display: none;">
|
||||
<button id="changePasswordBtn" data-i18n-title="change_password" style="display: none;">
|
||||
<i class="material-icons">vpn_key</i>
|
||||
</button>
|
||||
<div id="restoreFilesModal" class="modal centered-modal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<h4 class="custom-restore-header">
|
||||
<i class="material-icons orange-icon">restore_from_trash</i>
|
||||
<span>Restore or</span>
|
||||
<span data-i18n-key="restore_text">Restore or</span>
|
||||
<i class="material-icons red-icon">delete_for_ever</i>
|
||||
<span>Delete Trash Items</span>
|
||||
<span data-i18n-key="delete_text">Delete Trash Items</span>
|
||||
</h4>
|
||||
<div id="restoreFilesList"
|
||||
style="max-height:300px; overflow-y:auto; border:1px solid #ccc; padding:10px; margin-bottom:10px;">
|
||||
<!-- Trash items will be loaded here -->
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<button id="restoreSelectedBtn" class="btn btn-primary">Restore Selected</button>
|
||||
<button id="restoreAllBtn" class="btn btn-secondary">Restore All</button>
|
||||
<button id="deleteTrashSelectedBtn" class="btn btn-warning">Delete Selected</button>
|
||||
<button id="deleteAllBtn" class="btn btn-danger">Delete All</button>
|
||||
<button id="closeRestoreModal" class="btn btn-dark">Close</button>
|
||||
<button id="restoreSelectedBtn" class="btn btn-primary" data-i18n-key="restore_selected">Restore Selected</button>
|
||||
<button id="restoreAllBtn" class="btn btn-secondary" data-i18n-key="restore_all">Restore All</button>
|
||||
<button id="deleteTrashSelectedBtn" class="btn btn-warning" data-i18n-key="delete_selected_trash">Delete Selected</button>
|
||||
<button id="deleteAllBtn" class="btn btn-danger" data-i18n-key="delete_all">Delete All</button>
|
||||
<button id="closeRestoreModal" class="btn btn-dark" data-i18n-key="close">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="addUserBtn" title="Add User" style="display: none;">
|
||||
<button id="addUserBtn" data-i18n-title="add_user" style="display: none;">
|
||||
<i class="material-icons">person_add</i>
|
||||
</button>
|
||||
<button id="removeUserBtn" title="Remove User" style="display: none;">
|
||||
<button id="removeUserBtn" data-i18n-title="remove_user" style="display: none;">
|
||||
<i class="material-icons">person_remove</i>
|
||||
</button>
|
||||
<button id="darkModeToggle" class="dark-mode-toggle">Dark Mode</button>
|
||||
<button id="darkModeToggle" class="dark-mode-toggle" data-i18n-key="dark_mode_toggle">Dark Mode</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,26 +162,26 @@
|
||||
<div class="col-12">
|
||||
<form id="authForm" method="post">
|
||||
<div class="form-group">
|
||||
<label for="loginUsername">User:</label>
|
||||
<label for="loginUsername" data-i18n-key="user">User:</label>
|
||||
<input type="text" class="form-control" id="loginUsername" name="username" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loginPassword">Password:</label>
|
||||
<label for="loginPassword" data-i18n-key="password">Password:</label>
|
||||
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block btn-login">Login</button>
|
||||
<button type="submit" class="btn btn-primary btn-block btn-login" data-i18n-key="login">Login</button>
|
||||
<div class="form-group remember-me-container">
|
||||
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
||||
<label for="rememberMeCheckbox">Remember me</label>
|
||||
<label for="rememberMeCheckbox" data-i18n-key="remember_me">Remember me</label>
|
||||
</div>
|
||||
</form>
|
||||
<!-- OIDC Login Option -->
|
||||
<div class="text-center mt-3">
|
||||
<button id="oidcLoginBtn" class="btn btn-secondary">Login with OIDC</button>
|
||||
<button id="oidcLoginBtn" class="btn btn-secondary" data-i18n-key="login_oidc">Login with OIDC</button>
|
||||
</div>
|
||||
<!-- Basic HTTP Login Option -->
|
||||
<div class="text-center mt-3">
|
||||
<a href="login_basic.php" class="btn btn-secondary">Use Basic HTTP Login</a>
|
||||
<a href="login_basic.php" class="btn btn-secondary" data-i18n-key="basic_http_login">Use Basic HTTP Login</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -194,20 +194,20 @@
|
||||
<!-- Left Column (60% for Upload Card) -->
|
||||
<div id="leftCol" class="col-md-7" style="display: flex; justify-content: center;">
|
||||
<div id="uploadCard" class="card" style="width: 100%;">
|
||||
<div class="card-header">Upload Files/Folders</div>
|
||||
<div class="card-header" data-i18n-key="upload_header">Upload Files/Folders</div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column">
|
||||
<div class="form-group flex-grow-1" style="margin-bottom: 1rem;">
|
||||
<div id="uploadDropArea"
|
||||
style="border:2px dashed #ccc; padding:20px; cursor:pointer; display:flex; flex-direction:column; justify-content:center; align-items:center; position:relative;">
|
||||
<span>Drop files/folders here or click 'Choose Files'</span>
|
||||
<span data-i18n-key="upload_instruction">Drop files/folders here or click 'Choose Files'</span>
|
||||
<br />
|
||||
<input type="file" id="file" name="file[]" class="form-control-file" multiple
|
||||
style="opacity:0; position:absolute; width:1px; height:1px;" />
|
||||
<button type="button" id="customChooseBtn">Choose Files</button>
|
||||
<button type="button" id="customChooseBtn" data-i18n-key="choose_files">Choose Files</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button>
|
||||
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto" data-i18n-key="upload">Upload</button>
|
||||
<div id="uploadProgressContainer"></div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -217,8 +217,8 @@
|
||||
<div id="rightCol" class="col-md-5" style="display: flex; justify-content: center;">
|
||||
<div id="folderManagementCard" class="card" style="width: 100%; position: relative;">
|
||||
<div class="card-header" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span>Folder Navigation & Management</span>
|
||||
<button id="folderHelpBtn" class="btn btn-link" title="Folder Help"
|
||||
<span data-i18n-key="folder_navigation">Folder Navigation & Management</span>
|
||||
<button id="folderHelpBtn" class="btn btn-link" data-i18n-title="folder_help"
|
||||
style="padding: 0; border: none; background: none;">
|
||||
<i class="material-icons folder-help-icon" style="font-size: 24px;">info</i>
|
||||
</button>
|
||||
@@ -228,42 +228,41 @@
|
||||
<div id="folderTreeContainer"></div>
|
||||
</div>
|
||||
<div class="folder-actions mt-3">
|
||||
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
|
||||
<button id="createFolderBtn" class="btn btn-primary" data-i18n-key="create_folder">Create Folder</button>
|
||||
<div id="createFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Create Folder</h4>
|
||||
<input type="text" id="newFolderName" class="form-control" placeholder="Enter folder name"
|
||||
<h4 data-i18n-key="create_folder_title">Create Folder</h4>
|
||||
<input type="text" id="newFolderName" class="form-control" data-i18n-placeholder="enter_folder_name" placeholder="Enter folder name"
|
||||
style="margin-top:10px;" />
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelCreateFolder" class="btn btn-secondary">Cancel</button>
|
||||
<button id="submitCreateFolder" class="btn btn-primary">Create</button>
|
||||
<button id="cancelCreateFolder" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="submitCreateFolder" class="btn btn-primary" data-i18n-key="create">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="renameFolderBtn" class="btn btn-secondary ml-2" title="Rename Folder">
|
||||
<button id="renameFolderBtn" class="btn btn-secondary ml-2" data-i18n-title="rename_folder">
|
||||
<i class="material-icons">drive_file_rename_outline</i>
|
||||
</button>
|
||||
<div id="renameFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Rename Folder</h4>
|
||||
<input type="text" id="newRenameFolderName" class="form-control"
|
||||
placeholder="Enter new folder name" style="margin-top:10px;" />
|
||||
<h4 data-i18n-key="rename_folder_title">Rename Folder</h4>
|
||||
<input type="text" id="newRenameFolderName" class="form-control" data-i18n-placeholder="rename_folder_placeholder" placeholder="Enter new folder name" style="margin-top:10px;" />
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelRenameFolder" class="btn btn-secondary">Cancel</button>
|
||||
<button id="submitRenameFolder" class="btn btn-primary">Rename</button>
|
||||
<button id="cancelRenameFolder" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="submitRenameFolder" class="btn btn-primary" data-i18n-key="rename">Rename</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="deleteFolderBtn" class="btn btn-danger ml-2" title="Delete Folder">
|
||||
<button id="deleteFolderBtn" class="btn btn-danger ml-2" data-i18n-title="delete_folder">
|
||||
<i class="material-icons">delete</i>
|
||||
</button>
|
||||
<div id="deleteFolderModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Delete Folder</h4>
|
||||
<p id="deleteFolderMessage">Are you sure you want to delete this folder?</p>
|
||||
<h4 data-i18n-key="delete_folder_title">Delete Folder</h4>
|
||||
<p id="deleteFolderMessage" data-i18n-key="delete_folder_message">Are you sure you want to delete this folder?</p>
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelDeleteFolder" class="btn btn-secondary">Cancel</button>
|
||||
<button id="confirmDeleteFolder" class="btn btn-danger">Delete</button>
|
||||
<button id="cancelDeleteFolder" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="confirmDeleteFolder" class="btn btn-danger" data-i18n-key="delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,10 +270,10 @@
|
||||
<div id="folderHelpTooltip" class="folder-help-tooltip"
|
||||
style="display: none; position: absolute; top: 50px; right: 15px; background: #fff; border: 1px solid #ccc; padding: 10px; z-index: 1000; box-shadow: 2px 2px 6px rgba(0,0,0,0.2);">
|
||||
<ul class="folder-help-list" style="margin: 0; padding-left: 20px;">
|
||||
<li>Click on a folder in the tree to view its files.</li>
|
||||
<li>Use [-] to collapse and [+] to expand folders.</li>
|
||||
<li>Select a folder and click "Create Folder" to add a subfolder.</li>
|
||||
<li>To rename or delete a folder, select it and then click the appropriate button.</li>
|
||||
<li data-i18n-key="folder_help_item_1">Click on a folder in the tree to view its files.</li>
|
||||
<li data-i18n-key="folder_help_item_2">Use [-] to collapse and [+] to expand folders.</li>
|
||||
<li data-i18n-key="folder_help_item_3">Select a folder and click "Create Folder" to add a subfolder.</li>
|
||||
<li data-i18n-key="folder_help_item_4">To rename or delete a folder, select it and then click the appropriate button.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -286,53 +285,53 @@
|
||||
|
||||
<!-- File List Section -->
|
||||
<div id="fileListContainer" style="display: none;">
|
||||
<h2 id="fileListTitle">Files in (Root)</h2>
|
||||
<h2 id="fileListTitle" data-i18n-key="file_list_title">Files in (Root)</h2>
|
||||
<div id="fileListActions" class="file-list-actions">
|
||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;">Delete Files</button>
|
||||
<button id="deleteSelectedBtn" class="btn action-btn" style="display: none;" data-i18n-key="delete_files">Delete Files</button>
|
||||
<div id="deleteFilesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Delete Selected Files</h4>
|
||||
<p id="deleteFilesMessage">Are you sure you want to delete the selected files?</p>
|
||||
<h4 data-i18n-key="delete_selected_files_title">Delete Selected Files</h4>
|
||||
<p id="deleteFilesMessage" data-i18n-key="delete_files_message">Are you sure you want to delete the selected files?</p>
|
||||
<div class="modal-footer">
|
||||
<button id="cancelDeleteFiles" class="btn btn-secondary">Cancel</button>
|
||||
<button id="confirmDeleteFiles" class="btn btn-danger">Delete</button>
|
||||
<button id="cancelDeleteFiles" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="confirmDeleteFiles" class="btn btn-danger" data-i18n-key="delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled>Copy Files</button>
|
||||
<button id="copySelectedBtn" class="btn action-btn" style="display: none;" disabled data-i18n-key="copy_files">Copy Files</button>
|
||||
<div id="copyFilesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Copy Selected Files</h4>
|
||||
<p id="copyFilesMessage">Select a target folder for copying the selected files:</p>
|
||||
<h4 data-i18n-key="copy_files_title">Copy Selected Files</h4>
|
||||
<p id="copyFilesMessage" data-i18n-key="copy_files_message">Select a target folder for copying the selected files:</p>
|
||||
<select id="copyTargetFolder" class="form-control modal-input"></select>
|
||||
<div class="modal-footer">
|
||||
<button id="cancelCopyFiles" class="btn btn-secondary">Cancel</button>
|
||||
<button id="confirmCopyFiles" class="btn btn-primary">Copy</button>
|
||||
<button id="cancelCopyFiles" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="confirmCopyFiles" class="btn btn-primary" data-i18n-key="copy">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled>Move Files</button>
|
||||
<button id="moveSelectedBtn" class="btn action-btn" style="display: none;" disabled data-i18n-key="move_files">Move Files</button>
|
||||
<div id="moveFilesModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Move Selected Files</h4>
|
||||
<p id="moveFilesMessage">Select a target folder for moving the selected files:</p>
|
||||
<h4 data-i18n-key="move_files_title">Move Selected Files</h4>
|
||||
<p id="moveFilesMessage" data-i18n-key="move_files_message">Select a target folder for moving the selected files:</p>
|
||||
<select id="moveTargetFolder" class="form-control modal-input"></select>
|
||||
<div class="modal-footer">
|
||||
<button id="cancelMoveFiles" class="btn btn-secondary">Cancel</button>
|
||||
<button id="confirmMoveFiles" class="btn btn-primary">Move</button>
|
||||
<button id="cancelMoveFiles" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="confirmMoveFiles" class="btn btn-primary" data-i18n-key="move">Move</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled>Download ZIP</button>
|
||||
<button id="extractZipBtn" class="btn btn-sm btn-info" title="Extract Zip">Extract Zip</button>
|
||||
<button id="downloadZipBtn" class="btn action-btn" style="display: none;" disabled data-i18n-key="download_zip">Download ZIP</button>
|
||||
<button id="extractZipBtn" class="btn btn-sm btn-info" data-i18n-title="extract_zip" data-i18n-key="extract_zip_button">Extract Zip</button>
|
||||
<div id="downloadZipModal" class="modal" style="display:none;">
|
||||
<div class="modal-content">
|
||||
<h4>Download Selected Files as Zip</h4>
|
||||
<p>Enter a name for the zip file:</p>
|
||||
<input type="text" id="zipFileNameInput" class="form-control" placeholder="files.zip" />
|
||||
<h4 data-i18n-key="download_zip_title">Download Selected Files as Zip</h4>
|
||||
<p data-i18n-key="download_zip_prompt">Enter a name for the zip file:</p>
|
||||
<input type="text" id="zipFileNameInput" class="form-control" data-i18n-placeholder="zip_placeholder" placeholder="files.zip" />
|
||||
<div class="modal-footer" style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelDownloadZip" class="btn btn-secondary">Cancel</button>
|
||||
<button id="confirmDownloadZip" class="btn btn-primary">Download</button>
|
||||
<button id="cancelDownloadZip" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="confirmDownloadZip" class="btn btn-primary" data-i18n-key="download">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -347,50 +346,49 @@
|
||||
<div id="changePasswordModal" class="modal" style="display:none;">
|
||||
<div class="modal-content" style="max-width:400px; margin:auto;">
|
||||
<span id="closeChangePasswordModal" style="cursor:pointer;">×</span>
|
||||
<h3>Change Password</h3>
|
||||
<input type="password" id="oldPassword" placeholder="Old Password" style="width:100%; margin: 5px 0;" />
|
||||
<input type="password" id="newPassword" placeholder="New Password" style="width:100%; margin: 5px 0;" />
|
||||
<input type="password" id="confirmPassword" placeholder="Confirm New Password"
|
||||
style="width:100%; margin: 5px 0;" />
|
||||
<button id="saveNewPasswordBtn" class="btn btn-primary" style="width:100%;">Save</button>
|
||||
<h3 data-i18n-key="change_password_title">Change Password</h3>
|
||||
<input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password" placeholder="Old Password" style="width:100%; margin: 5px 0;" />
|
||||
<input type="password" id="newPassword" class="form-control" data-i18n-placeholder="new_password" placeholder="New Password" style="width:100%; margin: 5px 0;" />
|
||||
<input type="password" id="confirmPassword" class="form-control" data-i18n-placeholder="confirm_new_password" placeholder="Confirm New Password" style="width:100%; margin: 5px 0;" />
|
||||
<button id="saveNewPasswordBtn" class="btn btn-primary" data-i18n-key="save" style="width:100%;">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="addUserModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h3>Create New User</h3>
|
||||
<label for="newUsername">Username:</label>
|
||||
<h3 data-i18n-key="create_new_user_title">Create New User</h3>
|
||||
<label for="newUsername" data-i18n-key="username">Username:</label>
|
||||
<input type="text" id="newUsername" class="form-control" />
|
||||
<label for="addUserPassword">Password:</label>
|
||||
<label for="addUserPassword" data-i18n-key="password">Password:</label>
|
||||
<input type="password" id="addUserPassword" class="form-control" />
|
||||
<div id="adminCheckboxContainer">
|
||||
<input type="checkbox" id="isAdmin" />
|
||||
<label for="isAdmin">Grant Admin Access</label>
|
||||
<label for="isAdmin" data-i18n-key="grant_admin">Grant Admin Access</label>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<button id="cancelUserBtn" class="btn btn-secondary">Cancel</button>
|
||||
<button id="saveUserBtn" class="btn btn-primary">Save User</button>
|
||||
<button id="cancelUserBtn" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="saveUserBtn" class="btn btn-primary" data-i18n-key="save_user">Save User</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="removeUserModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h3>Remove User</h3>
|
||||
<label for="removeUsernameSelect">Select a user to remove:</label>
|
||||
<h3 data-i18n-key="remove_user_title">Remove User</h3>
|
||||
<label for="removeUsernameSelect" data-i18n-key="select_user_remove">Select a user to remove:</label>
|
||||
<select id="removeUsernameSelect" class="form-control"></select>
|
||||
<div class="button-container">
|
||||
<button id="cancelRemoveUserBtn" class="btn btn-secondary">Cancel</button>
|
||||
<button id="deleteUserBtn" class="btn btn-danger">Delete User</button>
|
||||
<button id="cancelRemoveUserBtn" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="deleteUserBtn" class="btn btn-danger" data-i18n-key="delete_user">Delete User</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="renameFileModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h4>Rename File</h4>
|
||||
<input type="text" id="newFileName" class="form-control" placeholder="Enter new file name"
|
||||
<h4 data-i18n-key="rename_file_title">Rename File</h4>
|
||||
<input type="text" id="newFileName" class="form-control" data-i18n-placeholder="rename_file_placeholder" placeholder="Enter new file name"
|
||||
style="margin-top:10px;" />
|
||||
<div style="margin-top:15px; text-align:right;">
|
||||
<button id="cancelRenameFile" class="btn btn-secondary">Cancel</button>
|
||||
<button id="submitRenameFile" class="btn btn-primary">Rename</button>
|
||||
<button id="cancelRenameFile" class="btn btn-secondary" data-i18n-key="cancel">Cancel</button>
|
||||
<button id="submitRenameFile" class="btn btn-primary" data-i18n-key="rename">Rename</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -398,8 +396,8 @@
|
||||
<div class="modal-content">
|
||||
<p id="confirmMessage"></p>
|
||||
<div class="modal-actions">
|
||||
<button id="confirmYesBtn" class="btn btn-primary">Yes</button>
|
||||
<button id="confirmNoBtn" class="btn btn-secondary">No</button>
|
||||
<button id="confirmYesBtn" class="btn btn-primary" data-i18n-key="yes">Yes</button>
|
||||
<button id="confirmNoBtn" class="btn btn-secondary" data-i18n-key="no">No</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;">×</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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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">×</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);
|
||||
|
||||
@@ -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 || [];
|
||||
|
||||
@@ -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); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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">×</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>
|
||||
|
||||
@@ -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;">×</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 -->
|
||||
|
||||
@@ -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
449
js/i18n.js
Normal 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')));
|
||||
});
|
||||
}
|
||||
12
js/main.js
12
js/main.js
@@ -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.
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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 Drag–and–Drop 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) -->
|
||||
|
||||
Reference in New Issue
Block a user