add: 添加 i18n 翻译
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
|
||||
import { post, uploadFile, downloadURL } from "../rc.js";
|
||||
import { toast, formatBytes, formatTime } from "../state.js";
|
||||
import { t } from "../i18n.js";
|
||||
|
||||
export async function renderBrowse({ remote, path }) {
|
||||
const app = document.getElementById("app");
|
||||
if (!remote) {
|
||||
app.innerHTML = `<div class="empty"><h3>No remote selected</h3><p><a href="#/remotes">Pick a remote</a> to browse.</p></div>`;
|
||||
app.innerHTML = `<div class="empty"><h3>${t("browser.no_remote_title")}</h3><p><a href="#/remotes">${t("browser.pick_remote")}</a> ${t("browser.no_remote_body")}</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,13 +21,13 @@ export async function renderBrowse({ remote, path }) {
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<input type="file" id="upload-input" multiple style="display:none">
|
||||
<button class="btn btn-secondary btn-sm" data-action="mkdir">New folder</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="upload">Upload</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="mkdir">${t("browser.mkdir")}</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="upload">${t("browser.upload")}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="breadcrumbs" class="breadcrumbs"></div>
|
||||
<div id="browser-card" class="card-outline">
|
||||
<p class="empty">Loading…</p>
|
||||
<p class="empty">${t("loading.title")}…</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -45,24 +46,23 @@ export async function renderBrowse({ remote, path }) {
|
||||
const items = (res && res.list) || [];
|
||||
renderTable(card, fs, path, items);
|
||||
} catch (e) {
|
||||
card.innerHTML = `<div class="empty"><h3>Couldn’t list</h3><p>${escapeHtml(e.message)}</p></div>`;
|
||||
toast(`List failed: ${e.message}`, "error");
|
||||
card.innerHTML = `<div class="empty"><h3>${t("error.couldnt_list")}</h3><p>${escapeHtml(e.message)}</p></div>`;
|
||||
toast(`${t("browser.browse_failed")}: ${e.message}`, "error");
|
||||
}
|
||||
|
||||
// --- Toolbar handlers ---
|
||||
app.querySelector('[data-action="mkdir"]').addEventListener("click", () => {
|
||||
openModal(
|
||||
"New folder",
|
||||
t("browser.mkdir_title"),
|
||||
[
|
||||
{ name: "name", label: "Folder name", type: "text", placeholder: "new-folder" },
|
||||
{ name: "name", label: t("browser.mkdir_field"), type: "text", placeholder: t("browser.mkdir_placeholder") },
|
||||
],
|
||||
async ({ name }) => {
|
||||
if (!name) return;
|
||||
const target = path ? `${path}/${name}` : name;
|
||||
await post("operations/mkdir", { fs, remote: target });
|
||||
toast(`Created ${name}`, "success");
|
||||
toast(t("browser.mkdir_created", name), "success");
|
||||
},
|
||||
).catch((e) => toast(`mkdir failed: ${e.message}`, "error"));
|
||||
).catch((e) => toast(`${t("browser.mkdir_failed")}: ${e.message}`, "error"));
|
||||
});
|
||||
|
||||
app.querySelector('[data-action="upload"]').addEventListener("click", () => {
|
||||
@@ -75,18 +75,17 @@ export async function renderBrowse({ remote, path }) {
|
||||
if (files.length === 0) return;
|
||||
try {
|
||||
await uploadFile(fs, path || "", files);
|
||||
toast(`Uploaded ${files.length} file(s)`, "success");
|
||||
// Re-render list
|
||||
toast(t("browser.uploaded", files.length), "success");
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
toast(`Upload failed: ${e.message}`, "error");
|
||||
toast(`${t("browser.upload_failed")}: ${e.message}`, "error");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderBreadcrumbs(el, remote, path) {
|
||||
const segments = (path || "").split("/").filter(Boolean);
|
||||
let html = `<a href="#/remotes">Remotes</a><span class="sep">/</span>`;
|
||||
let html = `<a href="#/remotes">${t("nav.remotes")}</a><span class="sep">/</span>`;
|
||||
html += `<a href="#/browse/${encodeURIComponent(remote)}">${escapeHtml(remote)}</a>`;
|
||||
let acc = "";
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
@@ -107,14 +106,13 @@ function renderTable(card, fs, path, items) {
|
||||
if (!items || items.length === 0) {
|
||||
card.innerHTML = `
|
||||
<div class="empty">
|
||||
<h3>Empty folder</h3>
|
||||
<p>No files here. Use <strong>Upload</strong> in the toolbar to add some.</p>
|
||||
<h3>${t("browser.empty_title")}</h3>
|
||||
<p>${t("browser.empty_body")}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort: directories first, then files; alphabetical within each group.
|
||||
items.sort((a, b) => {
|
||||
if (a.IsDir !== b.IsDir) return a.IsDir ? -1 : 1;
|
||||
return a.Name.localeCompare(b.Name);
|
||||
@@ -127,20 +125,19 @@ function renderTable(card, fs, path, items) {
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="col-num">Size</th>
|
||||
<th>Modified</th>
|
||||
<th class="col-num">Actions</th>
|
||||
<th>${t("browser.col.name")}</th>
|
||||
<th class="col-num">${t("browser.col.size")}</th>
|
||||
<th>${t("browser.col.modified")}</th>
|
||||
<th class="col-num">${t("browser.col.actions")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${parentHref ? `<tr class="row-dir"><td class="col-name"><a href="${parentHref}">../</a></td><td></td><td></td><td></td></tr>` : ""}
|
||||
${parentHref ? `<tr class="row-dir"><td class="col-name"><a href="${parentHref}">${t("browser.up")}</a></td><td></td><td></td><td></td></tr>` : ""}
|
||||
${rows}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
// Wire row action buttons
|
||||
card.querySelectorAll("[data-delete]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => onDelete(fs, path, btn.dataset.delete));
|
||||
});
|
||||
@@ -170,8 +167,8 @@ function row(fs, path, item) {
|
||||
<td class="col-num col-mono">${escapeHtml(formatBytes(item.Size))}</td>
|
||||
<td class="col-mono">${escapeHtml(formatTime(item.ModTime))}</td>
|
||||
<td class="col-num">
|
||||
<button class="btn btn-secondary btn-sm" data-rename="${escapeHtml(itemPath)}">Rename</button>
|
||||
<button class="btn btn-danger btn-sm" data-delete="${escapeHtml(itemPath)}">Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-rename="${escapeHtml(itemPath)}">${t("browser.rename")}</button>
|
||||
<button class="btn btn-danger btn-sm" data-delete="${escapeHtml(itemPath)}">${t("browser.delete")}</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -197,14 +194,13 @@ function parentLink(fs, path) {
|
||||
}
|
||||
|
||||
async function onDelete(fs, path, itemPath) {
|
||||
// itemPath is relative to fs root
|
||||
if (!confirm(`Delete ${itemPath}? This cannot be undone.`)) return;
|
||||
if (!confirm(t("browser.delete_confirm", itemPath))) return;
|
||||
try {
|
||||
await post("operations/deletefile", { fs, remote: itemPath });
|
||||
toast(`Deleted ${itemPath}`, "success");
|
||||
toast(t("browser.deleted", itemPath), "success");
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
toast(`Delete failed: ${e.message}`, "error");
|
||||
toast(`${t("browser.delete_file_failed")}: ${e.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +208,9 @@ async function onRename(fs, path, itemPath) {
|
||||
const segments = itemPath.split("/");
|
||||
const oldName = segments.pop();
|
||||
try {
|
||||
const result = await openModal(
|
||||
`Rename ${oldName}`,
|
||||
[{ name: "name", label: "New name", type: "text", value: oldName }],
|
||||
await openModal(
|
||||
t("browser.rename_title", oldName),
|
||||
[{ name: "name", label: t("browser.rename_field"), type: "text", value: oldName }],
|
||||
async ({ name }) => {
|
||||
if (!name || name === oldName) return;
|
||||
const dir = segments.join("/");
|
||||
@@ -225,19 +221,16 @@ async function onRename(fs, path, itemPath) {
|
||||
dstFs: fs,
|
||||
dstRemote: dst,
|
||||
});
|
||||
toast(`Renamed to ${name}`, "success");
|
||||
toast(t("browser.renamed", name), "success");
|
||||
},
|
||||
);
|
||||
// Modal succeeded → reload to refresh list
|
||||
location.reload();
|
||||
} catch (e) {
|
||||
toast(`Rename failed: ${e.message}`, "error");
|
||||
toast(`${t("browser.rename_failed")}: ${e.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Modal helper ---
|
||||
let modalResolver = null;
|
||||
|
||||
export function openModal(title, fields, onSubmit) {
|
||||
return new Promise((resolve) => {
|
||||
const root = document.getElementById("modal-root");
|
||||
@@ -257,8 +250,8 @@ export function openModal(title, fields, onSubmit) {
|
||||
<h3>${escapeHtml(title)}</h3>
|
||||
${formHtml}
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn btn-secondary" data-cancel>Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">OK</button>
|
||||
<button type="button" class="btn btn-secondary" data-cancel>${t("browser.modal.cancel")}</button>
|
||||
<button type="submit" class="btn btn-primary">${t("browser.modal.ok")}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -292,7 +285,6 @@ export function openModal(title, fields, onSubmit) {
|
||||
toast(err.message, "error");
|
||||
}
|
||||
});
|
||||
// Focus first input
|
||||
const first = form.elements[fields[0].name];
|
||||
if (first) first.focus();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user