From df19878c2a7ef637f09f3a3c50670deea1e1b886 Mon Sep 17 00:00:00 2001 From: Lemon-miaow Date: Tue, 6 Oct 2026 00:02:20 +0800 Subject: [PATCH] refactor(panel): standardize interface copy in documentation style --- .../components/CreateServerDialog.test.tsx | 8 +- .../src/components/DistributedNodes.test.tsx | 4 +- .../src/components/EditServerDialog.test.tsx | 2 +- panel/src/components/States.test.tsx | 4 +- panel/src/components/files/opText.test.ts | 28 +-- .../players/WakeListSection.test.tsx | 10 +- panel/src/i18n/resources/en-US/account.json | 74 +++---- panel/src/i18n/resources/en-US/admin.json | 78 +++---- panel/src/i18n/resources/en-US/auth.json | 44 ++-- panel/src/i18n/resources/en-US/backups.json | 44 ++-- panel/src/i18n/resources/en-US/common.json | 24 +-- panel/src/i18n/resources/en-US/dashboard.json | 30 +-- panel/src/i18n/resources/en-US/errors.json | 202 +++++++++--------- panel/src/i18n/resources/en-US/files.json | 112 +++++----- panel/src/i18n/resources/en-US/lobby.json | 12 +- panel/src/i18n/resources/en-US/ops.json | 6 +- panel/src/i18n/resources/en-US/schedules.json | 50 ++--- panel/src/i18n/resources/en-US/servers.json | 114 +++++----- .../src/i18n/resources/en-US/submissions.json | 12 +- panel/src/i18n/resources/zh-CN/account.json | 68 +++--- panel/src/i18n/resources/zh-CN/admin.json | 68 +++--- panel/src/i18n/resources/zh-CN/auth.json | 42 ++-- panel/src/i18n/resources/zh-CN/backups.json | 40 ++-- panel/src/i18n/resources/zh-CN/common.json | 22 +- panel/src/i18n/resources/zh-CN/dashboard.json | 18 +- panel/src/i18n/resources/zh-CN/errors.json | 130 +++++------ panel/src/i18n/resources/zh-CN/files.json | 108 +++++----- panel/src/i18n/resources/zh-CN/lobby.json | 12 +- panel/src/i18n/resources/zh-CN/ops.json | 4 +- panel/src/i18n/resources/zh-CN/schedules.json | 48 ++--- panel/src/i18n/resources/zh-CN/servers.json | 84 ++++---- .../src/i18n/resources/zh-CN/submissions.json | 10 +- panel/src/lib/api.test.ts | 84 ++++---- panel/src/pages/Account.test.tsx | 2 +- panel/src/pages/AccountReauth.test.tsx | 14 +- panel/src/pages/AccountSessions.test.tsx | 20 +- panel/src/pages/Login.test.tsx | 4 +- panel/src/pages/MySubmissionsPage.test.tsx | 8 +- panel/src/pages/ServerBackups.test.tsx | 2 +- panel/src/pages/ServerConsole.test.tsx | 8 +- panel/src/pages/ServerLuckPerms.test.tsx | 20 +- panel/src/pages/ServerSchedules.test.tsx | 16 +- .../src/pages/admin/AuthSourcesPage.test.tsx | 2 +- panel/src/pages/admin/BuildScanPanel.test.tsx | 14 +- panel/src/pages/admin/DBBackupCard.test.tsx | 10 +- panel/src/pages/admin/ImageBuildPage.test.tsx | 2 +- panel/src/pages/admin/LobbyPage.test.tsx | 2 +- .../src/pages/admin/UpdateReportCard.test.tsx | 4 +- panel/src/pages/admin/UserDetailPage.test.tsx | 10 +- panel/src/pages/servers/ServersPage.test.tsx | 4 +- 50 files changed, 869 insertions(+), 869 deletions(-) diff --git a/panel/src/components/CreateServerDialog.test.tsx b/panel/src/components/CreateServerDialog.test.tsx index cd61eb0..615acaa 100644 --- a/panel/src/components/CreateServerDialog.test.tsx +++ b/panel/src/components/CreateServerDialog.test.tsx @@ -154,7 +154,7 @@ describe("CreateServerDialog names", () => { ["survival-", "Use 3–32 lowercase letters, digits or hyphens, with no hyphen at either end."], ["my_world", "Use 3–32 lowercase letters, digits or hyphens, with no hyphen at either end."], ["a".repeat(33), "Use 3–32 lowercase letters, digits or hyphens, with no hyphen at either end."], - ["lobby", "“lobby” is kept for the platform. Pick another."], + ["lobby", "“lobby” is reserved by the platform. Use a different name."], ])("refuses the name %j before sending, and says why", async (name, why) => { const user = await openDialog(); await fillValid(user); @@ -176,7 +176,7 @@ describe("CreateServerDialog names", () => { await user.type(subdomainBox(), "api"); expect(create().disabled).toBe(true); - expect(screen.getByText("“api” is kept for the platform. Pick another.")).toBeTruthy(); + expect(screen.getByText("“api” is reserved by the platform. Use a different name.")).toBeTruthy(); expect(screen.queryByText(/Will be reachable at/)).toBeNull(); }); @@ -249,10 +249,10 @@ describe("CreateServerDialog on reopening", () => { const alert = await screen.findByRole("alert"); expect(alert.textContent).toContain(humanizeError(outage)); - expect(screen.getByText("Image list not loaded")).toBeTruthy(); + expect(screen.getByText("Image list unavailable")).toBeTruthy(); expect(screen.queryByText("No whitelisted images")).toBeNull(); - await user.click(within(alert).getByRole("button", { name: "Try again" })); + await user.click(within(alert).getByRole("button", { name: "Retry" })); expect(calls.listImages).toHaveBeenCalledTimes(2); await pick(user, "Image", PAPER); expect(screen.queryByRole("alert")).toBeNull(); diff --git a/panel/src/components/DistributedNodes.test.tsx b/panel/src/components/DistributedNodes.test.tsx index ddd6800..d7d6f93 100644 --- a/panel/src/components/DistributedNodes.test.tsx +++ b/panel/src/components/DistributedNodes.test.tsx @@ -26,7 +26,7 @@ describe("durable migration", () => { await screen.findByText("source node is offline"); expect(calls.migration).toHaveBeenCalledWith("survival"); expect(screen.getByText("persisted-op")).toBeTruthy(); - await user.click(screen.getByRole("button", { name: "Try again" })); + await user.click(screen.getByRole("button", { name: "Retry" })); await waitFor(() => expect(calls.retryMigration).toHaveBeenCalledWith("survival", "persisted-op")); expect(changed).toHaveBeenCalled(); expect(calls.migrateServer).not.toHaveBeenCalled(); @@ -36,7 +36,7 @@ describe("durable migration", () => { const user = userEvent.setup(); render( {}} />); await user.click(screen.getByRole("button", { name: "Migrate world" })); - const retry = await screen.findByRole("button", { name: "Try again" }); + const retry = await screen.findByRole("button", { name: "Retry" }); expect((retry as HTMLButtonElement).disabled).toBe(true); await user.click(retry); expect(calls.retryMigration).not.toHaveBeenCalled(); diff --git a/panel/src/components/EditServerDialog.test.tsx b/panel/src/components/EditServerDialog.test.tsx index 7a78477..9c5374b 100644 --- a/panel/src/components/EditServerDialog.test.tsx +++ b/panel/src/components/EditServerDialog.test.tsx @@ -109,7 +109,7 @@ describe("EditServerDialog request body", () => { for (const bad of ["two", "0", "1.5m", "-1"]) { await user.clear(cpuInput()); await user.type(cpuInput(), bad); - expect(screen.getByText("Enter cores (1, 1.5) or millicores (500m), above zero."), bad).toBeTruthy(); + expect(screen.getByText("The CPU limit must be greater than zero, expressed as cores (for example, 1 or 1.5) or millicores (for example, 500m)."), bad).toBeTruthy(); expect(cpuInput().getAttribute("aria-invalid"), bad).toBe("true"); expect(save().disabled, bad).toBe(true); } diff --git a/panel/src/components/States.test.tsx b/panel/src/components/States.test.tsx index af15bb8..a9fc5fa 100644 --- a/panel/src/components/States.test.tsx +++ b/panel/src/components/States.test.tsx @@ -70,7 +70,7 @@ describe("NotRunning", () => { ] as const) { const { unmount } = render(notRunning(phase, desired)); expect(screen.getByText("Server is starting"), `${phase}/${desired}`).toBeTruthy(); - expect(screen.getByText(/switches over by itself/), `${phase}/${desired}`).toBeTruthy(); + expect(screen.getByText(/updates the startup status automatically/), `${phase}/${desired}`).toBeTruthy(); expect(screen.queryByRole("button"), `${phase}/${desired}`).toBeNull(); unmount(); } @@ -122,7 +122,7 @@ describe("RefreshError", () => { const err = { status: 429, code: "quota_exceeded", message: "raw" }; render(); const alert = screen.getByRole("alert"); - expect(alert.textContent).toContain("Couldn't refresh; what you see is the last status read."); + expect(alert.textContent).toContain("Refresh failed. The last successfully retrieved status is displayed."); expect(alert.textContent).toContain(humanizeError(err)); }); }); diff --git a/panel/src/components/files/opText.test.ts b/panel/src/components/files/opText.test.ts index 8923c13..ff3c913 100644 --- a/panel/src/components/files/opText.test.ts +++ b/panel/src/components/files/opText.test.ts @@ -7,50 +7,50 @@ const e = (code: string, over: Record = {}) => ({ code, message describe("opErrorText", () => { it("tells an upload that found its file there from an extraction that would replace files", () => { expect(opErrorText("upload", e("file_exists"))).toBe( - "A file with this name is already here, so nothing was replaced. Upload it again and choose Replace.", + "A file with this name already exists. The upload did not replace it. Select Replace when uploading again.", ); expect(opErrorText("unzip", e("file_exists", { conflicts: ["a", "b"], conflict_count: 250 }))).toBe( - "It would replace 250 files already here, so nothing was extracted yet.", + "Extraction would overwrite 250 existing files. The operation has not been performed.", ); // Without the count, the list is counted. expect(opErrorText("unzip", e("file_exists", { conflicts: ["a"] }))).toBe( - "It would replace 1 file already here, so nothing was extracted yet.", + "Extraction would overwrite 1 existing file. The operation has not been performed.", ); }); it("names both sizes of a volume too small, and falls back when they are missing", () => { expect(opErrorText("unzip", e("volume_full", { need: 3 * 1024 * 1024, avail: 1024 }))).toBe( - "Not enough room on the world volume: 3.0 MiB needed, 1.0 KiB free. Nothing was changed.", + "Insufficient world volume space: 3.0 MiB required, 1.0 KiB available. This operation did not modify files.", ); // A full volume: the Job leaves the zero out. expect(opErrorText("upload", e("volume_full", { need: 2048 }))).toBe( - "Not enough room on the world volume: 2.0 KiB needed, 0 B free. Nothing was changed.", + "Insufficient world volume space: 2.0 KiB required, 0 B available. This operation did not modify files.", ); expect(opErrorText("upload", e("volume_full"))).toBe(humanizeError({ status: 0, code: "volume_full", message: "raw words" })); }); it("names the archive entry at fault", () => { - expect(opErrorText("unzip", e("archive_invalid"))).toBe("The archive is damaged, or not a zip file. Nothing was changed."); + expect(opErrorText("unzip", e("archive_invalid"))).toBe("The archive is damaged or is not in ZIP format. This operation did not modify files."); expect(opErrorText("unzip", e("archive_invalid", { entry: "world/level.dat" }))).toBe( - "world/level.dat in the archive is damaged (its size or checksum does not match). Nothing was changed.", + "The size or checksum of archive entry world/level.dat does not match. This operation did not modify files.", ); expect(opErrorText("unzip", e("archive_unsafe", { entry: "../../etc/passwd" }))).toBe( - "../../etc/passwd in the archive would land outside this folder, or is a device file. Nothing in the archive was extracted.", + "Archive entry ../../etc/passwd contains an out-of-directory path or a device file. The entire archive was rejected without extraction.", ); expect(opErrorText("unzip", e("archive_symlink", { entry: "world/link" }))).toBe( - "world/link in the archive is a symbolic link. Nothing in the archive was extracted.", + "Archive entry world/link is a symbolic link. The entire archive was rejected without extraction.", ); expect(opErrorText("unzip", e("type_conflict", { entry: "plugins" }))).toBe( - "plugins is a file on one side and a folder on the other, which replacing cannot resolve. Rename or delete plugins here, then extract again.", + "Entry plugins conflicts with an existing entry of the same name because one is a file and the other is a folder. Rename or delete the existing plugins before extraction.", ); }); it("gives a Job that ran out of time its own words", () => { expect(opErrorText("unzip", e("job_failed", { message: "job failed: DeadlineExceeded" }))).toBe( - "The background task did not finish within 2 hours and was stopped. Refresh the list to check, then try again.", + "The background task exceeded its two-hour execution limit and was terminated. Refresh the file list to verify the result before retrying.", ); expect(opErrorText("upload", e("job_failed", { message: "job failed: BackoffLimitExceeded" }))).toBe( - "The background task ended without saying why. Refresh the list to check, then try again.", + "The background task ended without a failure reason. Refresh the file list to verify the result before retrying.", ); }); @@ -58,10 +58,10 @@ describe("opErrorText", () => { // The message felis-api words a memory kill with. const oom = "the file operation ran out of memory (OOMKilled); an archive of this many files has to be split into smaller ones"; expect(opErrorText("unzip", e("job_failed", { message: oom }))).toBe( - "The archive holds more files than the extraction task has memory to list, so the system stopped it. The files on the server were not changed. Split it into several smaller zips and extract each one.", + "The extraction task terminated because its file list exceeded available memory. Server files were not modified. Split the content into smaller ZIP archives and upload and extract them separately.", ); expect(opErrorText("upload", e("job_failed", { message: oom }))).toBe( - "The background task ran out of memory and the system stopped it. The files on the server were not changed. Try again.", + "The background task terminated due to insufficient memory. Server files were not modified. Check task resources before retrying.", ); }); diff --git a/panel/src/components/players/WakeListSection.test.tsx b/panel/src/components/players/WakeListSection.test.tsx index 1d13f3f..0298d05 100644 --- a/panel/src/components/players/WakeListSection.test.tsx +++ b/panel/src/components/players/WakeListSection.test.tsx @@ -59,12 +59,12 @@ describe("WakeListSection", () => { it("says the list has no effect under the other two policies", async () => { calls.serverAllowlist.mockResolvedValue([]); const owner = render(); - expect(await screen.findByText(/Owner only, so only you and admins/)).toBeTruthy(); - expect(screen.getByText("No player has joined this server yet.")).toBeTruthy(); + expect(await screen.findByText(/Owner only policy permits only the server owner and administrators/)).toBeTruthy(); + expect(screen.getByText("No player join records available.")).toBeTruthy(); owner.unmount(); render(); - expect(await screen.findByText(/Public, so any player's join starts it/)).toBeTruthy(); + expect(await screen.findByText(/Public policy allows any player’s join to trigger startup/)).toBeTruthy(); }); it("revokes and restores in place, reading the list again after each change", async () => { @@ -97,14 +97,14 @@ describe("WakeListSection", () => { calls.setAllowlistWake.mockRejectedValue({ status: 403, code: "forbidden", message: "forbidden" }); const first = render(); await user.click(await screen.findByRole("button", { name: "Take away Steve's right to wake the server" })); - expect((await screen.findByRole("alert")).textContent).toBe("You are not allowed to do that."); + expect((await screen.findByRole("alert")).textContent).toBe("The current account is not authorized to perform this operation."); expect(within(rowOf("Steve")).queryByText("Revoked")).toBeNull(); first.unmount(); calls.serverAllowlist.mockRejectedValue({ status: 500, code: "internal", message: "db down" }); render(); expect((await screen.findByRole("alert")).textContent).toBe( - "Couldn't load the wake list. The operation failed because of an internal server error. Ask an admin to check the logs.", + "Unable to load the wake list. The operation failed because of an internal server error. Ask an admin to check the logs.", ); }); diff --git a/panel/src/i18n/resources/en-US/account.json b/panel/src/i18n/resources/en-US/account.json index 156717f..2317de2 100644 --- a/panel/src/i18n/resources/en-US/account.json +++ b/panel/src/i18n/resources/en-US/account.json @@ -2,8 +2,8 @@ "title": "Account", "subtitle": "Identity and Minecraft linking.", "sessions_title": "Signed-in devices", - "sessions_desc": "Every browser signed in to your account. Sign out any you don't recognize.", - "sessions_staff_idle": "Operator sessions sign out on their own after 30 minutes without activity.", + "sessions_desc": "Review browser sessions signed in to the current account. Revoke any unrecognized device session.", + "sessions_staff_idle": "Operations account sessions expire after 30 minutes of inactivity.", "loading_sessions": "Loading devices…", "sessions_empty": "No devices are signed in with a panel session.", "session_this_device": "This device", @@ -12,37 +12,37 @@ "session_active_now": "Active now", "session_active": "Active {{when}}", "session_signed_in": "Signed in {{when}}", - "session_sign_out_aria": "Sign out {{device}}", + "session_sign_out_aria": "Sign out the session on {{device}}", "session_signed_out_device": "Signed out {{device}}.", "sessions_sign_out_others": "Sign out other devices", - "sessions_sign_out_others_title": "Sign out every other device?", - "sessions_sign_out_others_desc": "Every device except this one is signed out and has to sign in again.", - "sessions_signed_out_others_one": "Signed out {{count}} other device.", - "sessions_signed_out_others_other": "Signed out {{count}} other devices.", + "sessions_sign_out_others_title": "Sign out all other device sessions?", + "sessions_sign_out_others_desc": "Sessions on all devices except the current device will be invalidated. Sign-in will be required again.", + "sessions_signed_out_others_one": "The session on {{count}} other device has been signed out.", + "sessions_signed_out_others_other": "Sessions on {{count}} other devices have been signed out.", "sign_out": "Sign out", "signing_out": "Signing out…", "minecraft_link": "Minecraft link", "checking_link": "Checking link status…", "linked_title": "Your Minecraft account is linked.", - "linked_desc": "Linked sessions can claim and own servers — ownership operations (claim, wake, stop) are unblocked.", + "linked_desc": "The Minecraft account is linked. Server claiming, starting, stopping, and other authorized management operations are available.", "uuid_label": "UUID", "step1_title": "Get a code in-game", - "step1_desc_prefix": "Join any server and run ", - "step1_desc_suffix": " in chat. The server already knows your verified identity, so it hands you a one-time code (valid about 10 minutes).", - "step2_title": "Enter it here", + "step1_desc_prefix": "Connect to a server and run ", + "step1_desc_suffix": ". After identity verification, the server provides a one-time binding code valid for approximately 10 minutes.", + "step2_title": "Enter the binding code", "link_code": "Link code", "link_code_placeholder": "ABCD2345", "verify_btn": "Link", "verifying": "Verifying…", "email_verification": "Email Verification", - "email_desc": "Verify your email address to secure your account.", + "email_desc": "Verify the registered email address for account authentication.", "email_verified": "Verified", "send_code": "Send Code", "sending_code": "Sending…", "email_step1": "Enter Email Address", - "email_step1_desc": "Enter the email address you want to bind to request a verification code.", + "email_step1_desc": "Enter the email address to register and request a verification code.", "email_step2": "Enter Verification Code", - "email_step2_desc": "Enter the 6-digit verification code sent to your email (valid for 10 minutes).", + "email_step2_desc": "Enter the six-digit verification code from the email. The code is valid for 10 minutes.", "email_verify_btn": "Verify", "email_verifying": "Verifying…", "email_otp_sent": "Verification code sent.", @@ -50,7 +50,7 @@ "change_email": "Change email", "continue_btn": "Continue", "passkeys": "Passkeys", - "passkeys_desc": "Passkeys let you log in securely using your fingerprint, face, or screen lock PIN.", + "passkeys_desc": "Passkeys provide panel sign-in using a fingerprint, facial recognition, or device PIN.", "no_passkeys": "No registered passkeys.", "loading_passkeys": "Loading passkeys…", "add_passkey": "Add Passkey", @@ -61,41 +61,41 @@ "last_used": "Last used: ", "never": "Never", "passkey_delete_aria": "Delete passkey “{{name}}”", - "passkey_delete_title": "Delete this passkey?", - "passkey_delete_desc": "“{{name}}” (registered {{created}}) will no longer sign you in. You can register it again later. Your other devices are signed out too.", + "passkey_delete_title": "Delete this Passkey?", + "passkey_delete_desc": "Deleting “{{name}}” (registered {{created}}) prevents further sign-in with this key and signs out sessions on other devices. The key can be registered again if required.", "passkey_delete_confirm": "Delete", - "passkey_last_hint": "This is your only passkey and your email is not verified, so deleting it would lock you out. Verify an email or add another passkey first.", + "passkey_last_hint": "The account has only one Passkey and no verified email address. Removing it would eliminate the available sign-in method. Verify an email address or register another Passkey first.", "migration": "Account migration", - "migration_desc": "Move everything a retired account owns onto this one. Migration starts in-game and finishes here.", + "migration_desc": "Transfer all servers owned by a retired account to the current account. Initiate migration in-game and complete it in the panel.", "account_id": "Account ID", "migration_checking": "Checking migration status…", "migration_none_prefix": "No migration in progress. To move this account's servers to another account, run ", "migration_none_suffix": " in-game on the account being retired.", - "migration_confirm_title": "Confirm it's you", + "migration_confirm_title": "Account identity verification", "migration_confirm_desc": "Migration retires this account, so it needs a step-up check first.", "migration_confirming": "Confirming…", "migration_confirm_passkey_btn": "Confirm with passkey", "migration_confirm_otp_btn": "Send a code to my email", "migration_issue_title": "Name the destination account", - "migration_issue_desc": "Paste the Account ID shown on the destination account's own page here, then issue a one-time transfer code.", - "migration_issue_deadline": "Issue it before {{time}}. The confirmation counts only in this browser; after that, confirm again. A verified email on this account gets a notice once the code is issued.", + "migration_issue_desc": "Enter the Account ID displayed on the destination account page, then issue a one-time transfer code.", + "migration_issue_deadline": "Issue the code before {{time}}. Identity confirmation is valid only in the current browser and must be repeated after expiry. A notification will be sent to the verified email address after issuance.", "migration_target_placeholder": "Destination Account ID", "migration_issuing": "Issuing…", "migration_issue_btn": "Issue code", "migration_code_title": "Transfer code (shown once)", - "migration_code_desc": "Sign in as the destination account and redeem this code there before it expires", + "migration_code_desc": "Sign in to the destination account and redeem this code before it expires.", "migration_code_pending": "A transfer code has been issued. Redeem it from the destination account before it expires", "migration_redeem_title": "Redeem a transfer code", - "migration_redeem_desc": "Received a code from an account being retired? Redeem it here to take over its servers.", + "migration_redeem_desc": "Redeem the transfer code issued by the source account to transfer its servers to the current account.", "migration_redeem_placeholder": "Transfer code", "migration_redeeming": "Redeeming…", "migration_redeem_btn": "Redeem", "migration_redeemed": "Migration complete — {{count}} server(s) moved to this account.", - "reauth_title": "Confirm it's you", - "reauth_desc": "Adding or removing a passkey and changing the email need a fresh check with a way in you already have. The check lasts 5 minutes.", - "reauth_checking": "Checking how you can confirm…", + "reauth_title": "Account identity verification", + "reauth_desc": "This operation requires identity verification. Use a registered Passkey or verified email address. Verification remains valid for five minutes.", + "reauth_checking": "Checking available identity verification methods…", "reauth_passkey_btn": "Use a passkey", - "reauth_passkey_waiting": "Waiting for your passkey…", + "reauth_passkey_waiting": "Waiting for Passkey verification…", "reauth_or": "or", "reauth_email_btn": "Email a code to {{email}}", "reauth_code_label": "Code sent to {{email}}", @@ -104,24 +104,24 @@ "reauth_resend": "Send another code", "reauth_sign_in_desc": "Operator accounts can also sign out and sign in again. A fresh sign-in counts for 5 minutes.", "reauth_sign_in_btn": "Sign out and sign in again", - "reauth_done_continue": "Confirmed. Press Continue to add the passkey.", - "email_change_desc": "Enter the new address and we'll send it a code. Once it's verified, sign-in codes go there, the old address gets a notice, and your other devices are signed out.", - "staff_linked_desc": "This role is linked to your administrator account and identifies you in-game.", - "staff_link_desc": "Link a game role to identify yourself in-game. Panel management is already available; you can do this later.", + "reauth_done_continue": "Identity verified. Select Continue to add a Passkey.", + "email_change_desc": "Enter the new email address to receive a verification code. After verification, sign-in codes will be sent to the new address, a change notification will be sent to the previous address, and sessions on other devices will be signed out.", + "staff_linked_desc": "This profile is linked to the current administrator account. Its management identity is recognized when joining the game.", + "staff_link_desc": "Link a Minecraft profile to identify its management role when joining the game. Panel administration does not require this link and it can be configured later.", "staff_sources_loading": "Loading authentication sources…", "staff_source_mojang": "Minecraft official", "staff_source": "Authentication source", "staff_profile": "Minecraft role name or UUID", "staff_lookup": "Look up role", "staff_working": "Working…", - "staff_lookup_unsupported": "This source needs api_url configured before roles can be looked up. You can continue using the panel.", - "staff_confirm_desc": "Confirm this role as your game identity. It will carry your administrator authority in-game.", + "staff_lookup_unsupported": "This authentication source requires api_url for profile lookup. Panel administration remains available.", + "staff_confirm_desc": "Link this profile to the current administrator account to identify its management role when joining the game.", "staff_confirm": "Confirm role link", "staff_source_help": "Yggdrasil is Minecraft’s account authentication protocol. Official and third-party providers use it to supply role profiles. Select the source used by your launcher and enter a role name or UUID, not an email.", "game_guide": "Prepare to enter Minecraft", "game_version": "Use Minecraft Java Edition {{version}} for the login server and lobby. Target servers may require another version or mods; check their instructions.", - "game_version_unknown": "The login server’s client version is not configured. Ask the operator to confirm it and set game_version; the panel build version does not determine it.", - "game_lobby": "The login server verifies your identity before sending you to the lobby. The lobby is a hub for choosing a target server: use /menu and wait for it to start. Check the server list if login or lobby is not ready.", + "game_version_unknown": "The login service client version is not configured. An operator must verify and set game_version. The panel version does not indicate the game version.", + "game_lobby": "Players enter the lobby after authentication through the login service. The lobby provides server selection. Run /menu to open the server menu and wait for startup. Check the service list if the login service or lobby is not ready.", "game_thirdparty": "For a third-party source, configure the same provider in your launcher. Profile lookup reads an identity; joining still requires authentication with that provider.", - "staff_code_alternative": "Provider does not support lookup? Use an in-game link code" + "staff_code_alternative": "Use an in-game binding code when profile lookup is unavailable" } diff --git a/panel/src/i18n/resources/en-US/admin.json b/panel/src/i18n/resources/en-US/admin.json index 265dc2e..ba37788 100644 --- a/panel/src/i18n/resources/en-US/admin.json +++ b/panel/src/i18n/resources/en-US/admin.json @@ -9,7 +9,7 @@ "add_image_btn": "Add Image", "image_ref_placeholder": "e.g. registry.felis.svc:5000/image:tag", "delete_image_tooltip": "Delete Whitelist Image", - "image_source_built": "Built here", + "image_source_built": "Platform build", "image_source_external": "External", "image_source_recommended": "Recommended", "delete_image_title": "Remove this image from the whitelist?", @@ -32,10 +32,10 @@ "download_context_btn": "Download context", "download_context_hint": "Downloads the uploaded context (.tar.gz) — it contains the Dockerfile that will actually be executed.", "context_sha256_label": "Context SHA-256", - "context_sha256_hint": "Approving binds this digest: if the submitter uploads again, the build refuses to run. When reviewing offline, compare it with sha256sum of your download.", - "context_sha256_missing": "No build context uploaded yet, or it was uploaded before digests were recorded and must be uploaded again.", - "context_sha256_downloaded": "Matches the file you just downloaded; approving binds exactly these bytes.", - "context_sha256_stale": "The copy you downloaded ({{digest}}…) was replaced by a newer upload; download and review it again.", + "context_sha256_hint": "Approval pins this digest. Builds reject subsequent uploads that do not match it. Use sha256sum to verify downloaded files during offline review.", + "context_sha256_missing": "No build context is available, or it predates digest recording. The submitter must upload it again.", + "context_sha256_downloaded": "The digest matches the downloaded file. Approval pins this content.", + "context_sha256_stale": "The downloaded version ({{digest}}…) was replaced by a new upload. Download and review the latest version.", "approve_needs_context": "Nothing to approve: the context has not been uploaded, or must be uploaded again", "base_image_label": "Base Image", "base_image_placeholder": "e.g. library/postgres:15", @@ -44,11 +44,11 @@ "build_status_succeeded": "Succeeded", "build_status_failed": "Failed", "build_status_cancelled": "Cancelled", - "build_queued_hint": "The concurrent build limit ([registry] max_concurrent_builds) is reached; this build starts on its own when an earlier one finishes.", + "build_queued_hint": "The concurrent build limit ([registry] max_concurrent_builds) has been reached. The task starts automatically after a running build completes.", "view_logs_btn": "Scan & logs", "cancel_build_btn": "Cancel", "no_builds_title": "No Builds Found", - "no_builds_hint": "You can trigger your first image build task using the form on the top right.", + "no_builds_hint": "Submit an image build task using the build form.", "no_images_title": "No images whitelisted", "no_images_hint": "The whitelist is empty — a platform admin must add one (CLI for now).", "enabled": "enabled", @@ -112,7 +112,7 @@ "updates_stat_end": "End Time", "updates_stat_timezone": "Local Timezone", "dbbackup_title": "Control-plane database backup", - "dbbackup_subtitle": "Users, passkeys, server ownership, quotas, audit logs and the world-archive index live in this database. felis-db-backup.timer on the host backs it up daily, and every upgrade snapshots it before migrating.", + "dbbackup_subtitle": "The control-plane database stores users, Passkeys, server ownership, quotas, audit logs, and world archive indexes. felis-db-backup.timer performs daily backups, and a separate snapshot is created before upgrade migrations.", "dbbackup_status_ok": "Healthy", "dbbackup_status_stale": "Overdue", "dbbackup_status_never": "Never backed up", @@ -129,18 +129,18 @@ "dbbackup_label_pre_restore": "Pre-restore snapshot", "dbbackup_label_offsite": "Off-site copy snapshot", "dbbackup_label_manual": "Manual", - "dbbackup_never_title": "No database backup has been recorded yet", + "dbbackup_never_title": "No database backup records", "dbbackup_stale_title": "The newest backup is more than {{hours}} hours old", "dbbackup_daily_stale_title": "The daily backup last completed {{when}}, past the {{hours}}-hour limit", - "dbbackup_daily_never_title": "The daily timer has not completed a backup yet", - "dbbackup_daily_fix_hint": "The newer backup above was taken by hand or by another job and can be restored from, but it grows older every day the timer stays broken. Run the daily backup on the host now, then read its log to find out why it stopped:", + "dbbackup_daily_never_title": "The daily timer has not completed a backup", + "dbbackup_daily_fix_hint": "The newer backup was created manually or by another task and is available for restoration. It does not confirm that the daily timer is healthy. Run a daily backup on the host and inspect the timer logs:", "dbbackup_servers_title": "The newest backup lacks the server definitions", - "dbbackup_servers_hint": "The cluster did not answer when the backup was taken. Restoring from it brings back accounts and the database, but no servers. Once the cluster answers, take a backup on the host again:", - "dbbackup_fix_hint": "If the host failed now, accounts, server ownership and the archive index could not be recovered. Run the daily backup on the host now, then read its log to find out why the timer missed it:", + "dbbackup_servers_hint": "The cluster did not respond during backup, so server definitions are missing. Restoring the database alone cannot recreate servers. Perform a complete backup after cluster connectivity recovers:", + "dbbackup_fix_hint": "Without a valid database backup, host failure may prevent recovery of accounts, server ownership, and archive indexes. Run a daily backup on the host and inspect the timer logs:", "dbbackup_copy": "Copy command", - "dbbackup_offsite_note": "Backups are kept on this host only and are lost with its disk. Copy the backup directory to another machine regularly; restore and disaster-recovery steps are in the troubleshooting guide, §16.", + "dbbackup_offsite_note": "Backups are stored only on the current host. Disk or host failure may affect both backups and original data. Copy backups regularly to an independent host. Restoration and disaster recovery procedures are documented in troubleshooting §16.", "versions_title": "Component versions", - "versions_subtitle": "felis-update-check.timer compares what this host runs with the newest upstream releases every day. Felis applies nothing on its own.", + "versions_subtitle": "felis-update-check.timer compares installed component versions with upstream releases daily. Felis does not apply updates automatically.", "versions_status_available_one": "{{count}} update available", "versions_status_available_other": "{{count}} updates available", "versions_status_current": "Up to date", @@ -157,19 +157,19 @@ "versions_state_available": "Update available", "versions_state_current": "Up to date", "versions_state_unknown": "Feed unreachable", - "versions_state_unreadable": "Version unreadable", + "versions_state_unreadable": "Version read failed", "versions_state_pinned": "Pinned", "versions_apply_hint": "Run this read-only check on the host. It shows the target, scope and an exact `--apply` command. Review it, then run that command during the maintenance window:", - "versions_never_title": "No version check has been recorded yet", + "versions_never_title": "No version check records", "versions_stale_title": "The newest version check is more than {{hours}} hours old", - "versions_fix_hint": "The versions below may be out of date. Run a check on the host now, then read the timer's log to find out why it did not run:", + "versions_fix_hint": "The displayed version information may be outdated. Run a new check on the host and inspect the timer logs to determine why the scheduled check did not run:", "build_import_submission_label": "Import parameters from submission", "build_import_submission_placeholder": "Select a user submission...", - "build_import_submission_none": "No submissions to import yet", + "build_import_submission_none": "No submissions available for import", "build_import_submission_no_match": "No submissions match “{{query}}”", - "build_import_submission_unavailable": "Submission list not loaded", - "build_import_submission_failed": "Couldn't load submissions: {{reason}}", - "build_import_submission_more": "Listing the newest {{shown}} of {{total}}; search above to find older ones.", + "build_import_submission_unavailable": "Submission list unavailable", + "build_import_submission_failed": "Unable to load submissions: {{reason}}", + "build_import_submission_more": "Displaying the latest {{shown}} of {{total}} submissions. Use search to locate older submissions.", "build_import_search_placeholder": "Search submissions by name, ID or submitter", "build_import_submission_hint": "Selecting a submission automatically populates the Image Reference, Context Reference, and the corresponding Dockerfile audit header.", "build_import_submission_warning_title": "Warning: This submission is currently \"{{status}}\"", @@ -201,7 +201,7 @@ "users_col_created": "Created", "users_view_detail": "Details", "user_not_found_title": "No such user", - "user_not_found_body": "This account may have been deleted. Find it again from the user list.", + "user_not_found_body": "The account does not exist or has been deleted. Return to the user list and search again.", "users_empty_title": "No Users Found", "users_empty_hint": "No users match the current filters.", "users_back_to_list": "Back to user list", @@ -209,12 +209,12 @@ "users_field_username": "Username", "users_field_email": "Email", "users_field_role": "Role", - "users_role_locked_self": "You can't change your own role.", - "users_role_locked_owner": "The owner's role is fixed. Only the host's break-glass console (sudo felis breakGlass) manages the owner.", + "users_role_locked_self": "The current account’s role cannot be changed.", + "users_role_locked_owner": "The Owner role cannot be changed. Manage it through the host recovery console (sudo felis breakGlass).", "users_save_btn": "Save Changes", "users_save_ok": "Changes saved successfully.", "users_linked_accounts": "Linked Minecraft Accounts", - "users_no_linked": "No Minecraft accounts linked yet.", + "users_no_linked": "No Minecraft accounts linked.", "users_link_add": "Link Account", "users_link_source": "Auth Source", "users_link_source_mojang": "Mojang", @@ -230,7 +230,7 @@ "users_quota_memory": "Max Memory (MiB)", "users_quota_storage": "Max Storage (GiB)", "users_quota_unlimited": "Unlimited", - "users_quota_hint": "Leave a box empty for unlimited. 0 grants none of it, so every claim that needs it is refused. A cap below current use only stops new claims; servers the user already has stay.", + "users_quota_hint": "Leave blank for no limit. Zero allocates no resources for that category. Claims exceeding the quota are rejected. Reducing a quota restricts subsequent claims without affecting existing servers.", "users_sessions": "Sessions", "users_no_sessions": "No active sessions.", "users_session_expires": "Expires", @@ -243,8 +243,8 @@ "users_session_revoke_all_dlg_desc": "Are you sure you want to revoke all active sessions? The user will be logged out from every device.", "users_session_revoke_confirm": "Revoke", "users_danger_zone": "Danger Zone", - "users_protected_self": "You can't disable or delete the account you're signed in with.", - "users_protected_owner": "The owner account can't be disabled or deleted from the panel. Only the host's break-glass console (sudo felis breakGlass) manages it.", + "users_protected_self": "The signed-in account cannot be disabled or deleted.", + "users_protected_owner": "The Owner account cannot be disabled or deleted from the panel. Manage it through the host recovery console (sudo felis breakGlass).", "users_danger_disable": "Disable User", "users_danger_disable_desc": "Prevent this user from logging in. All active sessions will be revoked immediately.", "users_danger_disable_btn": "Disable User", @@ -260,12 +260,12 @@ "users_danger_delete": "Delete User", "users_danger_delete_desc": "Soft-delete this user. Owned servers are released, all sessions are revoked, and the account is permanently disabled. This action cannot be undone through the panel.", "users_danger_delete_btn": "Delete User", - "users_danger_delete_yes": "Yes, Delete Permanently", + "users_danger_delete_yes": "Confirm permanent deletion", "users_danger_passkeys": "Unbind passkeys", - "users_danger_passkeys_desc": "Sever every passkey this account holds — the remediation when an authenticator is lost or compromised. Other login doors (email code, in-game approval) keep working.", + "users_danger_passkeys_desc": "Remove all Passkeys registered to this account to address lost or compromised authenticators. Other sign-in methods, including email codes and in-game approval, remain available.", "users_danger_passkeys_btn": "Unbind passkeys", "users_danger_passkeys_dlg_title": "Unbind passkeys", - "users_danger_passkeys_dlg_desc": "Every passkey for this account will be permanently removed. This is not a lockout: the user can sign in another way and re-enroll from the account page.", + "users_danger_passkeys_dlg_desc": "All Passkeys registered to this account will be permanently removed. Other sign-in methods remain available. Keys can be registered again from Account after sign-in.", "users_danger_passkeys_yes": "Yes, unbind", "add_image_desc": "Register an external Docker image reference on the whitelist for later server creation.", "images_search_placeholder": "Search image name or source...", @@ -274,14 +274,14 @@ "submissions_search_placeholder": "Search modpack name or submitter...", "trigger_build_desc": "Enter the build parameters to launch a Kaniko pipeline job in an isolated namespace.", "builds_search_placeholder": "Search build ID, image reference, or status...", - "builds_refresh_failed": "Couldn't refresh the build list: {{reason}}", + "builds_refresh_failed": "Unable to refresh the build list: {{reason}}", "build_requested_by_you": "You", "scan_title": "Security scan", "scan_blocked": "Blocked", "scan_passed": "Passed", "scan_loading": "Loading the security scan…", - "scan_none": "No security scan was kept for this build. It stopped before the scan step, or it ran before builds kept their scans.", - "scan_load_failed": "Couldn't load the security scan: {{reason}}", + "scan_none": "This build has no retained security scan record. It may have ended before scanning or been created before scan retention was enabled.", + "scan_load_failed": "Unable to load the security scan: {{reason}}", "scan_meta_one": "Scanned {{when}} · {{count}} package", "scan_meta_other": "Scanned {{when}} · {{count}} packages", "scan_policy": "Blocks on {{severities}}; vulnerabilities with no fixed release are listed only", @@ -289,14 +289,14 @@ "scan_counts_label": "Findings by severity", "scan_blocking_count_one": "{{count}} blocks the image", "scan_blocking_count_other": "{{count}} block the image", - "scan_clean": "Trivy found nothing to report in this image.", + "scan_clean": "Trivy detected no findings in this image.", "scan_showing": "Showing {{shown}} of {{total}} findings. The full report lists every one.", "scan_col_id": "ID", "scan_col_severity": "Severity", "scan_col_package": "Package", "scan_col_version": "Installed → fixed", "scan_col_target": "Found in", - "scan_no_fix": "no fix yet", + "scan_no_fix": "No fix available", "scan_blocks": "Blocks", "scan_policy_accepts_one": "{{count}} ID accepted as a known risk", "scan_policy_accepts_other": "{{count}} IDs accepted as known risks", @@ -305,8 +305,8 @@ "scan_secret": "Secret: {{title}}", "scan_download_report": "Trivy report", "scan_download_sbom": "SBOM", - "scan_not_kept": "Not kept with this build: the file was too large, or the step that writes it failed.", - "scan_download_failed": "Couldn't download: {{reason}}", + "scan_not_kept": "The file was not retained for this build. It may have exceeded the size limit or its generation step may have failed.", + "scan_download_failed": "Unable to download: {{reason}}", "platform_title": "Platform settings", "platform_subtitle": "Global controls for server startup and wake requests.", "platform_reload": "Reload", diff --git a/panel/src/i18n/resources/en-US/auth.json b/panel/src/i18n/resources/en-US/auth.json index eb7389c..6403728 100644 --- a/panel/src/i18n/resources/en-US/auth.json +++ b/panel/src/i18n/resources/en-US/auth.json @@ -13,56 +13,56 @@ "otp_placeholder": "Enter 6-digit code", "send_otp": "Send Code", "sending_otp": "Sending…", - "otp_sent": "Verification code sent to your email. If you received several, any code from the last few minutes works.", + "otp_sent": "A verification code has been sent to the registered email address. Codes received within their validity period remain usable.", "otp_btn": "Verify & Sign In", "resend_in": "s", "passkey_btn": "Sign in with Passkey", - "passkey_email_hint": "Enter your registered email above to use a passkey, or leave it empty to sign in with a discoverable passkey.", + "passkey_email_hint": "To sign in with a Passkey, enter the registered email address or leave it blank for passwordless sign-in.", "bind_code": "Bind Code", "bind_code_placeholder": "e.g., ABCD2345", - "bind_hint": "In Minecraft (Java Edition), join the address below. The login server gives a one-time bind code on your first join; after that, type /link in-game for a new one.", - "bind_hint_no_address": "In Minecraft (Java Edition), join this server. The login server gives a one-time bind code on your first join; after that, type /link in-game for a new one.", - "no_owner_title": "No Owner yet, so nobody can sign in", - "no_owner_subtitle": "The Owner is the account that owns this server", - "no_owner_intro": "The host administrator needs to initialize the first Owner login. Minecraft is not required.", + "bind_hint": "Connect to the address below using Minecraft Java Edition. The login service provides a one-time binding code on first connection. Run /link in-game to obtain subsequent codes.", + "bind_hint_no_address": "Connect to this server using Minecraft Java Edition. The login service provides a one-time binding code on first connection. Run /link in-game to obtain subsequent codes.", + "no_owner_title": "Owner account not initialized; sign-in unavailable", + "no_owner_subtitle": "Owner is the platform owner account", + "no_owner_intro": "The Owner sign-in method has not been initialized. The host administrator must complete the following setup. Minecraft startup is not required.", "no_owner_step_setup": "Run this command on the host to finish deployment and configure panel access:", - "no_owner_step_link": "Open the one-time setup link in your browser, record an email, and create a passkey. Link a Minecraft role later from Account.", + "no_owner_step_link": "Open the one-time setup link provided by the terminal in a browser, register an email address, and create a Passkey. A Minecraft profile can be linked from Account.", "no_owner_recheck": "Finished setup? Check again", "no_owner_rechecking": "Checking…", - "no_owner_still_unbound": "There is still no Owner. Check that the terminal has shown the setup link.", + "no_owner_still_unbound": "No initialized Owner account was detected. Confirm that the terminal generated a setup link and that account initialization is complete.", "bind_btn": "Verify & Sign In", "binding": "Verifying…", "op_hint": "Staff only: a code is emailed to you, and an online operator must approve the request in-game before you can sign in.", "op_start_btn": "Request operator sign-in", - "op_approve_hint": "A code has been emailed to you (if you received several, any recent one works). Ask an online operator to approve this request in-game:", + "op_approve_hint": "A verification code has been sent to the registered email address. Codes received within their validity period remain usable. An online administrator must approve this request in-game:", "op_waiting": "Waiting for in-game approval…", - "op_approved": "Approved — enter the code from your email.", + "op_approved": "Request approved. Enter the verification code from the email.", "op_restart": "Start over", "op_expires_in": "This request expires in {{time}}.", "op_expired": "This request expired before it was used. Request a new one to sign in.", "op_request_again": "Request again", - "op_poll_retrying": "Can't reach Felis right now. Still waiting, checking less often…", + "op_poll_retrying": "Unable to connect to Felis. Polling will continue at a reduced frequency.", "saving": "Saving…", - "setup_title": "Set up your account", + "setup_title": "Account initialization", "setup_welcome": "Welcome, {{name}}", "setup_preparing": "Preparing setup…", "setup_entering": "Entering the console…", - "setup_invalid_title": "Can't start setup", - "setup_invalid_subtitle": "This setup link is invalid or has already been used", + "setup_invalid_title": "Unable to start initialization", + "setup_invalid_subtitle": "The setup link is invalid or has already been used", "setup_invalid_hint_prefix": "Re-run ", "setup_invalid_hint_suffix": " on the server to get a fresh setup link, or head to the sign-in page.", - "setup_failed_title": "Setup didn't start", - "setup_failed_subtitle": "The server couldn't sign you in just now. The setup link is kept, so you can try again.", - "setup_retry": "Try again", + "setup_failed_title": "Initialization failed to start", + "setup_failed_subtitle": "The server could not complete sign-in. The setup link remains valid. Retry the operation.", + "setup_retry": "Retry", "setup_goto_login": "Go to sign in", "setup_email_step": "Step 1 · Add your email", - "setup_email_desc": "We'll save this address for your account. You can configure email delivery (SMTP) and verify it later from Settings — it isn't required to finish setup.", + "setup_email_desc": "The email address will be saved to the current account. Mail delivery (SMTP) and email verification can be configured later and are not required for account initialization.", "setup_email_save": "Save & continue", "setup_passkey_step": "Step 2 · Register a passkey", - "setup_passkey_desc": "Create a passkey with your fingerprint, face, or device PIN. It's how you'll sign in to the console — required to finish setup.", + "setup_passkey_desc": "Create a Passkey using a fingerprint, facial recognition, or device PIN to sign in to the console. Passkey registration is required to complete initialization.", "setup_create_passkey": "Create passkey", "setup_registering": "Registering…", "setup_default_passkey_name": "Default passkey", - "session_ended_notice": "Your session expired or was revoked. Sign in again to go back to the page you were on.", - "setup_game_optional": "Finish login setup to open the panel. A Minecraft role can be linked later from Account." + "session_ended_notice": "The sign-in session has expired or been revoked. Sign in again to return to the originally requested page.", + "setup_game_optional": "The management panel is available after sign-in setup is complete. A Minecraft profile can be linked later from Account." } diff --git a/panel/src/i18n/resources/en-US/backups.json b/panel/src/i18n/resources/en-US/backups.json index 7c66053..c6a3631 100644 --- a/panel/src/i18n/resources/en-US/backups.json +++ b/panel/src/i18n/resources/en-US/backups.json @@ -1,6 +1,6 @@ { "title": "Backups & restore", - "subtitle": "A world that was played is backed up automatically once a day after its server stops, and again before it is archived after long inactivity. You can use these backups to restore the world — the server must be stopped first.", + "subtitle": "Servers that have run and are stopped are backed up daily. A backup is also created before long-term idle reclamation. Stop the server before restoring a world.", "back_to_console": "Back to console", "not_yours_title": "No permission to access backups", "not_yours_body": "Only the owner or an admin can view, restore and delete this server's backups.", @@ -14,27 +14,27 @@ "reason_label": "Reason: {{reason}}", "backup_now": "Back up now", "backup_in_progress": "Starting backup…", - "backup_started": "Backup started — its progress shows under recent operations below.", - "backup_world_busy": "A backup, restore or world export is already running; back up once it finishes.", + "backup_started": "The backup task has started. Progress is available under Recent operations.", + "backup_world_busy": "A backup, restoration, or world export is in progress. Wait for the current operation to complete before starting a backup.", "backup_requires_stopped": "Stop the server before backing it up.", "export_world": "Export world", - "export_world_hint": "Download the server's current world as a .tar.gz archive.", + "export_world_hint": "Package the current world as a .tar.gz file for download.", "export_preparing": "Preparing…", "export_requires_stopped": "Stop the server before exporting its world.", - "export_world_busy": "A backup, restore or export is running on this world; export once it finishes.", - "export_another": "Another download is being prepared; wait for it to start.", - "export_progress_world": "Archiving the world — the download starts by itself once it is ready. Keep this page open; the server can't start until the download ends.", - "export_progress_backup": "Preparing the backup — the download starts by itself in a moment. Keep this page open.", + "export_world_busy": "The world is undergoing backup, restoration, or export. Wait for the current operation to complete before starting an export.", + "export_another": "Another download is being prepared. Retry after preparation completes.", + "export_progress_world": "The world is being archived. The download starts automatically after preparation. Keep the page open. The server cannot start until the download ends.", + "export_progress_backup": "The backup download is being prepared. Transfer starts automatically after preparation. Keep the page open.", "export_started_world": "Download started: your browser is saving {{filename}}. The server can start again once it finishes.", "export_started_backup": "Download started: your browser is saving {{filename}}.", - "export_failed": "The export failed and nothing was downloaded.", + "export_failed": "Export failed. No content was downloaded.", "export_failed_because": "The export failed: {{reason}}", "download_btn": "Download backup", "expires_in": "Expires {{when}}", "expired": "Expired", "former_owner": "Former owner: {{owner}}", "empty_title": "No backups for this server", - "empty_hint": "A scheduled backup is saved once the server has run and stopped — or create one now with “Back up now”.", + "empty_hint": "Servers that have run and are stopped receive scheduled backups automatically. Select Back up now to create a manual backup.", "jobs_title": "Recent operations", "jobs_empty": "No recent backup, restore or export operations.", "job_backup": "Backup", @@ -53,32 +53,32 @@ "chain_abandoned": "Not restored; the world was left as it was", "chain_abandoned_because": "Not restored: {{reason}}", "chain_abandoned_snapshot_failed": "Snapshot failed, so the world was left as it was", - "chain_abandoned_not_configured": "Restore is not configured; nothing was restored", - "chain_abandoned_server_gone": "The server no longer exists; nothing was restored", + "chain_abandoned_not_configured": "Restoration not performed: restore service not configured", + "chain_abandoned_server_gone": "Restoration not performed: server no longer exists", "chain_abandoned_server_started": "The server was started before the restore could run", "chain_abandoned_restore_busy": "Another restore is running on this world", "restore_btn": "Restore this backup", "restore_btn_short": "Restore", "restore_confirm": "Restoring a backup will stop the server (all online players will be disconnected) and completely overwrite the current world with this backup. Without a safety snapshot this cannot be undone. Do you want to continue?", - "restore_confirm_safe": "Restoring a backup will stop the server (all online players will be disconnected), take a safety snapshot of the current world, and overwrite the world with this backup once the snapshot succeeds. If you picked the wrong backup, restore the pre-restore snapshot to get it back. Continue?", + "restore_confirm_safe": "Restoration stops the server and disconnects online players, creates a safety snapshot of the current world, and overwrites the world with the selected backup after the snapshot succeeds. Restore the pre-restore snapshot if the result must be reversed. Confirm restoration?", "safety_snapshot_label": "Take a safety snapshot of the current world first (recommended)", "safety_snapshot_hint": "The restore starts once the snapshot succeeds; if it fails, nothing is restored and the world stays as it is.", - "safety_snapshot_off_hint": "No snapshot: the current world is overwritten right away and cannot be recovered.", + "safety_snapshot_off_hint": "No safety snapshot will be created. The current world is overwritten directly. Existing data without another backup cannot be recovered.", "restore_confirm_yes": "Confirm restore", "restore_confirm_yes_unsafe": "Overwrite and restore", "cancel": "Cancel", "restore_started_short": "Restore started", "restore_snapshot_started_short": "Snapshot, then restore", - "stopping": "Stopping the server (players online get a 30-second warning first)…", + "stopping": "Stopping the server. If players are online, an in-game notice is sent 30 seconds before disconnection…", "restoring": "Restoring…", "stop_timeout": "The server did not stop in time. Close this window and try again shortly.", "corrupt_badge": "Corrupt", - "corrupt_hint": "A read-back found this archive no longer matches what was written, so it can't be restored intact. It stays listed until it expires so the loss is visible, unless you delete it.", + "corrupt_hint": "Archive read-back verification failed. The archive cannot be restored intact. It is retained until expiry for investigation and can be deleted manually if no longer required.", "corrupt_short": "Corrupt", "verified_at": "Verified {{when}}", "skipped_entries_one": "{{count}} entry not archived", "skipped_entries_other": "{{count}} entries not archived", - "skipped_entries_hint": "The world held symlinks or other entries that aren't plain files, so the archive skipped them and a restore won't bring them back.", + "skipped_entries_hint": "Symbolic links and other non-regular entries were excluded from the archive and will not be included in restoration.", "col_created": "Backup Time", "col_size": "Size", "col_expires": "Expires", @@ -86,12 +86,12 @@ "col_actions": "Actions", "delete_btn": "Delete backup", "delete_title": "Delete this backup?", - "delete_confirm": "The backup from {{relative}} ({{absolute}}, {{size}}) leaves this list at once and can no longer be restored. This cannot be undone.", + "delete_confirm": "The backup from {{relative}} ({{absolute}}), size {{size}}, will be removed from the list immediately and will no longer be available for restoration. This operation cannot be undone.", "delete_cleanup_note": "Its archive is removed at the next daily cleanup, and its off-site copy at the next sync.", - "delete_archive_warning": "This backup is the archive kept when the world was reclaimed or released. Unless the world on the server has been restored from it, deleting it loses that world for good.", - "delete_only_warning": "This is the only backup this server has.", + "delete_archive_warning": "This backup is an archive created during world reclamation or server release. After deletion, world data without another recoverable copy cannot be restored.", + "delete_only_warning": "This is the server’s only current backup.", "delete_confirm_yes": "Delete backup", - "delete_restore_busy": "A restore may be reading this backup. Delete it once the restore finishes.", + "delete_restore_busy": "A restoration task may be reading this backup. Wait for restoration to complete before deleting it.", "deleted": "Backup deleted.", - "delete_gone": "This backup was already gone; the list has been refreshed." + "delete_gone": "The backup no longer exists. The list has been refreshed." } diff --git a/panel/src/i18n/resources/en-US/common.json b/panel/src/i18n/resources/en-US/common.json index c9b2264..5821ef1 100644 --- a/panel/src/i18n/resources/en-US/common.json +++ b/panel/src/i18n/resources/en-US/common.json @@ -1,19 +1,19 @@ { "loading": "Loading…", - "try_again": "Try again", + "try_again": "Retry", "cancel": "Cancel", "save": "Save", "create": "Create", "creating": "Creating…", "close_sr": "Close", "not_authorized_title": "Not authorized", - "not_authorized_body": "This area is for platform administrators. If you reached it by typing the URL, your account does not have admin access — and the API would refuse the request anyway.", + "not_authorized_body": "The current account lacks platform administration permissions and cannot access this page.", "back_to_dashboard": "Back to your dashboard", - "not_wired_yet": "Not wired yet", + "not_wired_yet": "Feature not integrated", "pending_backend_prefix": "pending backend: ", "checking_access": "Checking access…", - "access_unknown_title": "Couldn't check your access", - "access_unknown_body": "Your account details didn't load, so this page stays hidden until they do.", + "access_unknown_title": "Unable to verify account permissions", + "access_unknown_body": "Account information could not be loaded. Page content is unavailable until access permissions are verified.", "loading_config": "Loading config…", "brand_name": "Felis", "brand_tagline": "K8s-native Minecraft orchestration", @@ -23,22 +23,22 @@ "pagination_prev": "Previous", "pagination_next": "Next", "pagination_of": "Page {{current}} of {{total}}", - "crash_title": "This page hit an error", - "crash_body": "The panel ran into a problem rendering this page. Reloading usually fixes it; if it keeps happening, send the error details below to your admin.", + "crash_title": "Page loading error", + "crash_body": "Page rendering failed. Reload the page. If the issue persists, provide the error details to an administrator.", "crash_updated_title": "The panel was updated", - "crash_updated_body": "A new version of the panel was just released. Reload to open this page.", + "crash_updated_body": "The panel version has changed. Reload the page.", "crash_reload": "Reload", "crash_details": "Error details", "crash_copy": "Copy error details", "not_found_title": "Page not found", - "not_found_body": "The name in the link is not valid, or the page does not exist.", - "connection_lost": "Can't reach Felis: the network is down, or your Cloudflare Access sign-in expired. This clears by itself once the network is back; an expired Access sign-in needs a page reload.", + "not_found_body": "The name in the link is invalid, or the requested page does not exist.", + "connection_lost": "Unable to connect to Felis. The network may be unavailable or the Cloudflare Access session may have expired. The connection resumes automatically when the network recovers. If the Access session has expired, reload the page and sign in again.", "reload_page": "Reload page", - "config_unavailable": "The panel configuration (/config.json) did not load, so server addresses and links shown here may be wrong. Reload to try again.", + "config_unavailable": "Panel configuration (/config.json) failed to load. Server addresses and links may be inaccurate. Reload the page to retry.", "build_dev": "dev", "open_menu": "Open menu", "copy_address": "Copy address {{address}}", "address_copied": "Copied", "address_copy_failed": "Selected — copy it from the menu", - "refresh_failed": "Couldn't refresh; what you see is the last status read." + "refresh_failed": "Refresh failed. The last successfully retrieved status is displayed." } diff --git a/panel/src/i18n/resources/en-US/dashboard.json b/panel/src/i18n/resources/en-US/dashboard.json index 07d3625..7f17454 100644 --- a/panel/src/i18n/resources/en-US/dashboard.json +++ b/panel/src/i18n/resources/en-US/dashboard.json @@ -1,7 +1,7 @@ { "title": "Dashboard", - "subtitle": "Your fleet at a glance.", - "no_servers_hint": "Create your first server or claim an unowned one.", + "subtitle": "Server runtime overview.", + "no_servers_hint": "Create a server, claim an existing server, or request allocation from an administrator.", "stat_servers": "Servers", "stat_running": "Running", "stat_players_online": "Players online", @@ -12,10 +12,10 @@ "active_load": "Player Load Rate", "status_distribution": "Node Status Distribution", "account_linked_title": "Account Linked", - "account_linked_desc": "Your identity is bound to a Minecraft UUID. You have full server ownership and wake permissions.", + "account_linked_desc": "The Web account is linked to a Minecraft profile. Authorized claiming and startup operations are available.", "account_unlinked_title": "Account Unlinked", - "account_unlinked_desc": "You must link a Minecraft character to claim or operate servers. Go to Account to bind now.", - "go_link": "Bind Now", + "account_unlinked_desc": "Link a Minecraft profile from Account before claiming or operating servers.", + "go_link": "Link account", "link_status_checking": "Checking whether a game character is linked…", "link_status_failed": "Link status unavailable", "system_specs": "Specs & Metrics", @@ -30,13 +30,13 @@ "spec_failed_ratio": "Failed Rate", "no_servers_monitor": "No active instances in the cluster", "images_count": "Whitelisted backend images available", - "images_security_note": ". All backend images undergo Trivy CVE vulnerability scanning before deployment and run in isolated security containers.", + "images_security_note": ". Image availability is controlled by the whitelist. Build scan results are available in build records.", "manage_game_character": "Manage Game Character", "instances_count": "{{count}} instances", "quick_start_guide": "Quick Start Guide:", - "quick_start_step1": "Claiming an unowned server automatically adds your Minecraft UUID to the access whitelist.", - "quick_start_step2": "When a server sits idle with no players online, the platform automatically sleeps it to conserve resources.", - "quick_start_step3": "All game traffic is routed through the Velocity gateway — simply connect to your dedicated subdomain.", + "quick_start_step1": "After a server is claimed, the system adds the corresponding Minecraft UUID to its access whitelist.", + "quick_start_step2": "When a server reaches its configured idle duration, it stops automatically and saves the world to release runtime resources.", + "quick_start_step3": "The Velocity gateway provides game access through each server’s dedicated subdomain.", "legend_title": "3D Node Monitor Legend", "legend_running": "Running", "legend_starting": "Starting", @@ -44,11 +44,11 @@ "legend_stopped": "Sleeping", "legend_failed": "Failed", "legend_unknown": "Unknown", - "welcome_title": "Welcome to Felis Lolicon", - "welcome_desc": "You have logged into the User Dashboard. Go to 'My Servers' to view and claim nodes allocated to you, or manage their sleep, wake, and console logs.", + "welcome_title": "Felis orchestration platform", + "welcome_desc": "Use My Servers to view, claim, and manage authorized servers, including startup, shutdown, and log inspection.", "go_to_servers_btn": "Manage My Servers", - "fleet_flat_webgl": "WebGL is unavailable in this browser, so the fleet is shown flat.", - "fleet_flat_error": "The 3D view failed to load, so the fleet is shown flat.", - "fleet_flat_empty": "No servers yet", - "staff_unlinked_desc": "No Minecraft role is linked yet. Panel management is available; choose an authentication source and link a role from Account when you are ready to play." + "fleet_flat_webgl": "WebGL is unavailable. The fleet is displayed in a two-dimensional view.", + "fleet_flat_error": "The 3D view failed to load. The fleet is displayed in a two-dimensional view.", + "fleet_flat_empty": "No servers available", + "staff_unlinked_desc": "No Minecraft profile is linked. Panel administration is available. Select an authentication source and link a profile from Account before joining the game." } diff --git a/panel/src/i18n/resources/en-US/errors.json b/panel/src/i18n/resources/en-US/errors.json index fc3a579..7b0095e 100644 --- a/panel/src/i18n/resources/en-US/errors.json +++ b/panel/src/i18n/resources/en-US/errors.json @@ -1,147 +1,147 @@ { - "local_auth_disabled": "Direct sign-in is turned off here — reach this console through your organization's secure access.", + "local_auth_disabled": "Local sign-in is disabled in this deployment. Access the console through the organization’s secure entry point.", "staff_account": "This is a staff account — use Operator sign-in instead.", "not_linked": "Link your Minecraft account before claiming (Account → Link).", - "invalid_code": "That code is invalid or expired — request a fresh one and try again.", + "invalid_code": "The binding code is invalid or expired. Obtain a new code.", "already_linked": "That Minecraft account is already linked to another user.", - "bad_mc_uuid": "That isn't a Minecraft UUID. It looks like 069a79f4-44e9-4726-a5be-fca90e38aaf5, with or without the dashes.", + "bad_mc_uuid": "Invalid Minecraft UUID format. Example: 069a79f4-44e9-4726-a5be-fca90e38aaf5. Hyphens are optional.", "quota_exceeded": "You have reached your server quota.", - "already_claimed": "Someone else just claimed this server.", + "already_claimed": "This server has been claimed by another account.", "image_not_whitelisted": "That image is not on the whitelist.", - "image_not_in_registry": "The internal registry doesn't hold that image tag — it was never pushed or has been deleted. Rebuild or push it, then try again.", - "registry_unavailable": "Can't reach the internal registry to look up which build that image is — try again shortly.", - "image_change_unconfirmed": "Changing the image opens the world with the new build's Minecraft version, and upgraded chunks can't be opened by the old one again. Back the world up, tick the confirmation, then save.", + "image_not_in_registry": "The image tag is absent from the internal registry. It may not have been pushed or may have been deleted. Rebuild or push the image before retrying.", + "registry_unavailable": "Unable to connect to the internal registry. The image build version is unconfirmed. Retry later.", + "image_change_unconfirmed": "After an image change, the next startup opens the world with the new build’s Minecraft version. Upgraded chunks are not compatible with older versions. Back up the world and select the confirmation before saving.", "subdomain_taken": "That subdomain is already in use.", "already_exists": "A server with that name already exists.", - "world_volume_exists": "An earlier server with that name left its world volume behind. Choose another name, or ask the operator to delete the old volume.", - "server_retiring": "This server has been given up or is being deleted, which happens at the next daily reclaim run. It can't be started or claimed unless that is cancelled.", - "confirm_mismatch": "The name you typed doesn't match the server's name.", - "system_server": "A system server is managed by the platform and can't be given up or deleted.", - "world_volume_orphaned": "This server's MinecraftServer was deleted by hand but its world volume is still there. Ask the operator to archive and remove that volume first, then delete the server.", - "cooldown": "Wake is cooling down — try again shortly.", - "not_running": "The server isn't running — wake it before managing access.", - "console_unavailable": "Can't reach the server console right now — try again shortly.", - "luckperms_missing": "LuckPerms isn't installed on this server, so permission and group changes have no effect. For the lobby, ask the operator to re-run the installer; any other server needs the LuckPerms plugin added first.", - "no_backup": "There's no restorable backup for this server yet.", - "backup_corrupt": "This backup failed its read-back check and can't be restored intact — pick another backup.", - "not_stopped": "Stop the server completely first — this changes its world volume, which the running server holds.", - "maintenance_in_progress": "This server's world is busy with a restore, backup, world export, file download, file change or idle reclaim. Try again once it finishes. A restore, backup or file change usually takes a minute or two; an export or a download lasts until the browser has all of it, and an idle reclaim can take longer on a large world.", - "no_world_volume": "This server has no world volume yet — start it once so it is created, then retry.", - "file_changed": "This file changed after you opened it (another manager saved it, or the server rewrote it on its last run), so your save was not written, to keep that change.", - "volume_full": "The server's volume is full, so the change was not written and the files there are unchanged. Delete files it no longer needs, or ask an admin to grow its volume.", - "backup_cooldown": "This server was backed up moments ago, and manual backups have a cooldown — try again in a few minutes.", + "world_volume_exists": "A world volume from a server of the same name still exists. Use a different name or request an operator to verify and process the existing volume.", + "server_retiring": "The server is pending release or deletion at the next daily reclaim run. Startup and claiming are unavailable until the request is cancelled.", + "confirm_mismatch": "The entered name does not match the server name.", + "system_server": "System servers are managed by the platform and cannot be released or deleted.", + "world_volume_orphaned": "The server’s MinecraftServer resource was manually deleted, but its world volume remains. An operator must archive and remove the remaining volume before deleting the server record.", + "cooldown": "The startup cooldown has not elapsed. Retry later.", + "not_running": "The server is not running. Start it before managing access permissions.", + "console_unavailable": "The server console is unavailable. Retry later.", + "luckperms_missing": "LuckPerms is not installed. Permission and group changes cannot be applied. An operator must rerun the installer for the lobby. Other servers require the LuckPerms plugin to be installed first.", + "no_backup": "No restorable backup is available for this server.", + "backup_corrupt": "The backup failed read-back verification and cannot be restored intact. Select another backup.", + "not_stopped": "This operation modifies the world volume, which is exclusively mounted by the running server. Stop the server completely first.", + "maintenance_in_progress": "The world is undergoing restoration, backup, export, file download, file modification, or reclamation. Retry after the operation completes. Exports and downloads remain active until the browser completes the transfer.", + "no_world_volume": "The server has no world volume. Start the server to create it, then retry.", + "file_changed": "The file was modified by another administrator or server process after it was opened. The save was not written to avoid overwriting existing changes.", + "volume_full": "The server volume is full. The change was not written and existing files remain unchanged. Remove unnecessary files or request a capacity increase from an administrator.", + "backup_cooldown": "The manual backup cooldown has not elapsed. Retry later.", "backup_store_full": "The backup store is full, so manual backups are paused — ask an administrator to free space.", - "restore_unavailable": "Restore isn't available right now — try again later.", - "export_busy": "Too many exports are being prepared: one at a time per person, two at a time across the platform, and six an hour per person. Try again in a few minutes.", - "export_expired": "This download has expired or was already used — start the export again.", - "export_not_ready": "The download isn't ready yet — wait a moment and try again.", - "export_unavailable": "Downloading worlds and backups isn't set up on this deployment — ask an administrator.", - "session_expired": "Your session expired — please sign in again.", - "forbidden": "You are not allowed to do that.", - "self_protected": "You can't do that to the account you're signed in with.", - "owner_protected": "The owner account can't be demoted, disabled or deleted from the panel. Only the host's break-glass console (sudo felis breakGlass) manages it.", - "session_not_found": "That session has already ended.", - "generic": "Something went wrong.", + "restore_unavailable": "Restoration is currently unavailable. Retry later.", + "export_busy": "Export limits have been reached: one concurrent request per account, two across the platform, and six per account per hour. Retry after existing tasks complete or rate limits reset.", + "export_expired": "The download link has expired or has already been used. Export the content again.", + "export_not_ready": "Download preparation is incomplete. Retry later.", + "export_unavailable": "World and backup downloads are not configured in this deployment. Contact an administrator.", + "session_expired": "The session has expired. Sign in again.", + "forbidden": "The current account is not authorized to perform this operation.", + "self_protected": "This operation is not permitted on the signed-in account.", + "owner_protected": "The Owner account cannot be demoted, disabled, or deleted from the panel. Manage it through the host recovery console (sudo felis breakGlass).", + "session_not_found": "The session has ended or does not exist.", + "generic": "The operation failed. Retry later.", "otp_resend_cooldown": "Verification code requested too frequently, please try again later.", "otp_locked": "Too many incorrect attempts, please request a new verification code.", "otp_account_locked": "Too many wrong codes in 24 hours — email codes for this account are paused. Try again later or use a passkey.", - "rate_limited": "Too many sign-in attempts from your network — wait a minute and try again.", - "mail_rate_limited": "This server is sending too much mail right now, so codes are paused. Try again shortly or use a passkey.", + "rate_limited": "Sign-in requests are rate-limited. Retry after the limit resets.", + "mail_rate_limited": "Mail delivery is rate-limited. Verification codes cannot currently be sent. Retry later or use a Passkey.", "passkey_challenge_invalid": "Passkey challenge is invalid or expired, please try again.", "invalid_attestation": "Could not verify this Passkey, please try again.", "passkey_already_bound": "This Passkey is already bound to another account.", - "passkey_unavailable": "Passkey subsystem is not available right now.", - "last_passkey": "This is your only passkey and your email is not verified, so it can't be removed. Verify an email or add another passkey first.", + "passkey_unavailable": "Passkey authentication is currently unavailable.", + "last_passkey": "The account has only one Passkey and no verified email address. Removal is not allowed. Verify an email address or register another Passkey first.", "passkey_not_allowed": "The operation was cancelled by the user or browser.", "passkey_aborted": "Passkey registration was aborted.", - "bad_name": "That server name is invalid — use 3–32 lowercase letters, digits or dashes, and avoid reserved names.", - "bad_subdomain": "That subdomain is invalid — use 3–32 lowercase letters, digits or dashes, and avoid reserved names.", - "bad_display_name": "That display name is invalid — use at most 64 characters, all visible ones or spaces, with no line breaks, tabs or invisible control characters.", - "at_capacity": "The cluster is at its running-server cap — try again once a server stops.", - "storage_immutable": "Storage size can't be changed online.", + "bad_name": "Invalid server name. Use 3–32 lowercase letters, digits, or hyphens, excluding reserved names.", + "bad_subdomain": "Invalid subdomain. Use 3–32 lowercase letters, digits, or hyphens, excluding reserved names.", + "bad_display_name": "Invalid display name. Use at most 64 visible characters or spaces, excluding line breaks, tabs, and control characters.", + "at_capacity": "The cluster’s running-server limit has been reached. Retry after another server stops.", + "storage_immutable": "Storage capacity cannot be changed online.", "bad_idle_stop": "Idle stop must be between 1 minute and 24 hours, or Never.", "email_taken": "That email is already verified on another account — sign in with it or use another address.", "mail_undeliverable": "The verification email could not be delivered — try again later, or ask the operator to check the mail relay.", - "mail_unavailable": "This server can't send email codes because no mail relay is set up. Sign in with a passkey, or ask the server operator to configure email.", - "bad_path": "That path is invalid — use a relative path inside the world directory.", - "too_large": "That is larger than the size limit.", + "mail_unavailable": "Mail delivery is not configured, so verification codes cannot be sent. Sign in with a Passkey or request mail configuration from an administrator.", + "bad_path": "Invalid path. Use a relative path within the world directory.", + "too_large": "The content exceeds the size limit.", "files_timeout": "The file operation timed out — try again shortly.", - "files_unavailable": "File editing isn't available right now.", - "file_exists": "Something with that name is already there. Pick another name, or rename or delete the one that is there first.", - "upload_staging_full": "The panel's upload space is nearly full right now, so the file was not passed on. Try again later, or ask an admin to free space on the uploads volume.", - "upload_incomplete": "The upload stopped before the whole file arrived, so nothing was changed. Try again.", - "length_required": "The upload did not say how large it is, so it was refused. Upload it again from the panel.", - "digest_mismatch": "The file was changed on its way to the server, so it was refused and nothing was written. Try again.", - "digest_required": "The request came without a checksum, so it was refused. Reload the panel and try again.", - "bad_digest": "The request's checksum was malformed, so it was refused. Reload the panel and try again.", - "file_unreadable": "The file could not be read: it was changed, moved or deleted after it was picked. Pick it again and upload.", - "read_damaged": "The file arrived damaged, so it was not opened: saving it would write the damage back. Open it again.", - "upload_not_found": "This upload is gone: it was cancelled, already landed, sat idle for 6 hours, or the panel service restarted. Upload the file again.", - "too_many_uploads": "You already have 4 large uploads in progress. Wait for one to finish, or cancel one, and try again.", - "op_lost": "The operation's progress can no longer be read. Refresh the list to see whether the file landed.", - "jobs_unavailable": "The background job service isn't available right now.", - "already_terminal": "This build already finished — there is nothing to cancel.", - "build_unavailable": "Image builds aren't available right now.", - "build_logs_unavailable": "Build logs aren't available right now.", - "scan_document_not_kept": "This build kept no copy of that scan file: it was too large to keep, or the step that writes it failed.", + "files_unavailable": "File management is currently unavailable.", + "file_exists": "A file or folder with this name already exists in the current directory. Use a different name, or rename or delete the existing entry.", + "upload_staging_full": "Upload staging space is insufficient. The file transfer was not completed. Retry later or request cleanup of the uploads volume from an administrator.", + "upload_incomplete": "The upload was interrupted before the complete file was transferred. This operation did not modify files. Upload the file again.", + "length_required": "The upload request omitted the file size and was rejected. Upload the file again through the panel.", + "digest_mismatch": "File transfer verification failed. The server rejected the transfer and the file was not written. Upload the file again.", + "digest_required": "The request omitted its checksum and was rejected. Reload the panel and retry.", + "bad_digest": "The request checksum format is invalid and the request was rejected. Reload the panel and retry.", + "file_unreadable": "The selected file cannot be read. It may have been modified, moved, or deleted. Select the file again before uploading.", + "read_damaged": "File transfer verification failed. The file was not opened to prevent subsequent saves from writing corrupted data. Read the file again.", + "upload_not_found": "The multipart upload record is unavailable. It may have been cancelled, completed, idle for over six hours, or invalidated by a service restart. Upload the file again.", + "too_many_uploads": "The account has reached its limit of four concurrent large-file uploads. Wait for a task to complete or cancel an existing upload before retrying.", + "op_lost": "Operation progress is unavailable. Refresh the file list to verify the write result.", + "jobs_unavailable": "The background task service is currently unavailable.", + "already_terminal": "The build has finished and cannot be cancelled.", + "build_unavailable": "Image building is currently unavailable.", + "build_logs_unavailable": "Build logs are currently unavailable.", + "scan_document_not_kept": "The scan document was not retained for this build. The file may have exceeded the size limit or its generation step may have failed.", "already_reviewed": "This submission has already been reviewed.", - "context_changed": "The submitter uploaded the build context again after you reviewed it. Download and review the new upload before approving.", - "submission_quota_exceeded": "Your submission quota is full: too many pending reviews, or your pending and rejected uploads fill your storage allowance. Withdraw a pending one, or wait: a rejected upload frees its space 7 days after review, and approved ones don't count.", + "context_changed": "The build context was uploaded again during review. Download and review the latest content before approval.", + "submission_quota_exceeded": "The submission quota has been reached for pending reviews or uploaded storage. Withdraw a pending submission if required. Rejected uploads release storage seven days after review; approved submissions do not count toward the quota.", "submission_cooldown": "Too many submission requests — try again shortly.", - "submissions_unavailable": "Submissions aren't available right now.", - "uploads_unavailable": "Uploads aren't available right now.", + "submissions_unavailable": "Submission processing is currently unavailable.", + "uploads_unavailable": "Uploads are currently unavailable.", "upload_busy": "Another part of this upload is still being sent — wait a moment and try again.", - "upload_offset_mismatch": "The upload fell out of step with the server — try again to pick up where it stopped.", - "uploads_store_unavailable": "The uploads store did not answer — try again in a moment; what was already sent is kept.", + "upload_offset_mismatch": "The upload offset differs from the server record. Retry to resume from the confirmed offset.", + "uploads_store_unavailable": "Upload storage did not respond. Received content is retained. Retry later.", "part_too_large": "A piece of the upload was larger than the server accepts.", - "backup_unavailable": "Backups aren't available right now.", + "backup_unavailable": "Backups are currently unavailable.", "account_retired": "This account has been retired — sign in with the account it was migrated to.", "no_migration": "There is no migration in progress.", - "not_confirmed": "Confirm it's you in this browser first. A confirmation lasts 10 minutes.", - "migrate_quota_exceeded": "The servers this migration brings over don't fit your quota. Ask an admin to raise it, then redeem the same code again before it expires.", - "invalid_quota": "A quota must be a whole number from 0 to 2147483647. Leave it empty for unlimited.", + "not_confirmed": "Verify account identity in the current browser before proceeding. Verification remains valid for ten minutes.", + "migrate_quota_exceeded": "The migration exceeds the current account’s server quota. Request a quota adjustment from an administrator and redeem the same code again before it expires.", + "invalid_quota": "Quota must be an integer from 0 to 2147483647. Leave the field blank for no limit.", "already_confirmed": "This migration has already been confirmed.", "invalid_target": "The migration target must be a different account.", "target_not_found": "No account matches that migration target.", - "target_unavailable": "That migration target isn't available.", - "no_passkey": "This account has no Passkey bound yet.", - "passkey_required": "Bind a Passkey before doing that.", - "no_step_up_factor": "This account has no verified email or Passkey to confirm with yet.", + "target_unavailable": "The migration target account is unavailable.", + "no_passkey": "No Passkey is registered to this account.", + "passkey_required": "Register a Passkey before proceeding.", + "no_step_up_factor": "The account has no verified email address or Passkey available for identity verification.", "passkey_login_failed": "Passkey verification failed — try again.", "passkey_login_invalid": "That Passkey sign-in request is invalid or expired — start it again.", - "too_many_challenges": "Too many verification attempts right now — try again shortly.", - "reauth_required": "Confirm it's you first: this change needs your Passkey or an email code from the last few minutes.", + "too_many_challenges": "Verification requests are rate-limited. Retry later.", + "reauth_required": "This operation requires recent identity verification. Verify identity again using a Passkey or email code.", "staff_reauth": "Operator accounts confirm with a Passkey or by signing in again.", - "no_session": "This only works in a browser signed in to Felis.", - "op_login_invalid": "This operator sign-in couldn't be completed — restart the sign-in.", - "op_login_not_found": "No pending operator sign-in with that id.", + "no_session": "This operation requires an active Felis browser session.", + "op_login_invalid": "Operator sign-in could not be completed. Restart the sign-in process.", + "op_login_not_found": "No pending operator sign-in request matches this ID.", "op_login_mismatch": "That operator sign-in is for a different account.", "too_many_streams": "Too many live streams are open — close some pages and try again.", - "protected_admin": "That name belongs to a linked administrator and can't be reclaimed.", - "auth_unavailable": "The sign-in service isn't available right now.", - "setup_token_invalid": "That setup token is invalid or expired.", - "network_error": "Can't reach Felis: the network is down, or your Cloudflare Access sign-in expired. Reload the page to sign in again.", - "upstream_unavailable": "The service is unavailable right now. Try again shortly.", + "protected_admin": "This name belongs to a linked administrator account and cannot be claimed.", + "auth_unavailable": "The sign-in service is currently unavailable.", + "setup_token_invalid": "The setup token is invalid or expired.", + "network_error": "Unable to connect to Felis. The network may be unavailable or the Cloudflare Access session may have expired. Check the network. If the Access session has expired, reload the page and sign in again.", + "upstream_unavailable": "The service is currently unavailable. Retry later.", "internal_error": "The operation failed because of an internal server error. Ask an admin to check the logs.", "request_id": "Request ID: {{id}}", - "bad_path_param": "The name in this link is not valid. Open it again from the list.", - "payload_too_large": "That is larger than the entry proxy accepts, so it was not sent.", - "passkey_no_credential": "The browser returned no passkey. Try again.", - "not_found": "That no longer exists. It may have been deleted; refresh and try again.", - "conflict": "Something changed in the meantime. Refresh and try again.", + "bad_path_param": "The name in the link is invalid. Reopen the target page from the list.", + "payload_too_large": "The request content exceeds the entry point size limit and could not be sent.", + "passkey_no_credential": "The browser returned no Passkey credential. Retry.", + "not_found": "The requested content does not exist or has been deleted. Refresh and retry.", + "conflict": "The state changed during the operation. Refresh and retry.", "bad_request": "The request was not accepted: {{detail}}", - "restore_in_progress": "Another backup is still being restored to this server's world. Try again once it finishes.", - "uploads_full": "The upload store is full. An admin has to delete reviewed submissions before new uploads fit.", + "restore_in_progress": "Another backup is being restored to the world. Retry after the current restoration completes.", + "uploads_full": "The upload store is full. An administrator must delete reviewed submissions before further uploads can be accepted.", "unsupported_media_type": "The request was sent in a format the server does not accept. Reload the page and try again.", - "schedules_unavailable": "Scheduled tasks aren't available right now.", - "schedule_limit": "This server already has as many scheduled tasks as it can hold. Delete one first.", - "schedule_running": "This task is running right now. Try again once the run finishes.", - "schedule_stale": "The server has a new owner since this task was saved. Save the task again before running it.", + "schedules_unavailable": "Scheduled tasks are currently unavailable.", + "schedule_limit": "The server’s scheduled-task limit has been reached. Remove an existing task before retrying.", + "schedule_running": "The task is running. Retry after the current execution completes.", + "schedule_stale": "The server owner changed after the task was saved. Save the task again before execution.", "bad_schedule": "The task was not saved: {{detail}}", "auth_source_unknown": "Select a configured authentication source.", "auth_source_unavailable": "The authentication source is unavailable or returned an invalid role. Try again later.", "auth_source_lookup_unsupported": "Role lookup is not configured for this authentication source.", - "minecraft_profile_not_found": "No role matched in this source. Check the role name or UUID.", + "minecraft_profile_not_found": "The profile was not found in the selected authentication source. Check the profile name or UUID.", "auth_sources_unavailable": "Authentication source management is unavailable.", "auth_sources_changed": "Authentication sources changed. Discard your edits and reload before saving.", "auth_source_tag_locked": "Saved source IDs cannot be renamed or removed. Disable the source instead." diff --git a/panel/src/i18n/resources/en-US/files.json b/panel/src/i18n/resources/en-US/files.json index 7b91954..6b3ea37 100644 --- a/panel/src/i18n/resources/en-US/files.json +++ b/panel/src/i18n/resources/en-US/files.json @@ -10,32 +10,32 @@ "col_size": "Size", "col_modified": "Modified", "empty_dir_title": "This folder is empty", - "empty_dir_hint": "Drop files here to upload them, or make a file or folder with the buttons above.", + "empty_dir_hint": "Upload files by dragging them into the list, or create files and folders from the toolbar.", "list_truncated": "The listing was truncated at the server's safety limit.", - "stopped_required_title": "Stop the server first", - "stopped_required_body": "Files can only be browsed or edited while the server is fully stopped — the world volume is single-attached to the running server. Stop it to continue.", - "not_yours_title": "Not your server", + "stopped_required_title": "Server must be stopped", + "stopped_required_body": "A running server exclusively mounts its world volume. Stop the server completely before browsing or editing files.", + "not_yours_title": "Server file access denied", "not_yours_body": "Only the owner or an admin can edit this server's files.", - "binary_hint": "Editing this file as text would damage it. Download it to open with the appropriate application, then upload your changes.", + "binary_hint": "Binary files do not support text editing. Download the file, process it with compatible software, and upload it again. Text editing may corrupt the file.", "saved": "Saved {{path}}", "saving": "Saving…", "save": "Save", "too_large": "Exceeds the {{limit}} editor limit.", - "conflict_title": "Someone else changed this file", - "conflict_body": "The file changed after you opened it (another manager saved it, or the server rewrote it on its last run). Your edits are still in the editor and have not been written.", + "conflict_title": "File content changed", + "conflict_body": "The file was modified by another administrator or server process after it was opened. The current edits have not been written. Load the latest content or overwrite the existing file.", "conflict_reload": "Load latest", - "conflict_reload_hint": "Discard your edits and show the file as it is now", + "conflict_reload_hint": "Discard current edits and load the latest file content", "conflict_overwrite": "Overwrite anyway", - "conflict_overwrite_hint": "Replace their change with your version", - "discard_prompt": "You have unsaved changes. Discard them?", + "conflict_overwrite_hint": "Overwrite the existing file with the current edits", + "discard_prompt": "Discard unsaved changes?", "discard": "Discard", "keep_editing": "Keep editing", - "status_refresh_failed": "Could not refresh the server's status, so what you see may be out of date: {{reason}}", + "status_refresh_failed": "Server status refresh failed. The displayed state may be outdated: {{reason}}", "col_actions": "Actions", "new_file": "New file", "new_folder": "New folder", "upload": "Upload", - "upload_hint": "Upload files into this folder, or drop them on the list. Zip a folder before uploading it", + "upload_hint": "Upload files to the current directory or drag them into the list. Compress folders as .zip archives before uploading.", "in_folder": "In {{dir}}", "name_label": "Name", "create": "Create", @@ -43,32 +43,32 @@ "rename_title": "Rename {{name}}", "rename_item": "Rename {{name}}", "delete_item": "Delete {{name}}", - "managed_no_rename": "Felis manages this one, so it keeps its name and place.", - "wait_for_uploads": "Wait for the uploads to finish first.", + "managed_no_rename": "This entry is managed by Felis. Its name and location must remain unchanged.", + "wait_for_uploads": "This operation is unavailable until uploads complete.", "name_required": "Enter a name.", - "name_slash": "A name cannot contain “/”.", + "name_slash": "Names must not contain “/”.", "name_dots": "“.” and “..” cannot be used as names.", - "name_too_long": "That name is too long.", - "name_taken": "Something here already has that name.", + "name_too_long": "The name exceeds the length limit.", + "name_taken": "A file or folder with this name already exists in the current directory.", "folder_created": "Created folder {{path}}", "renamed": "Renamed {{from}} to {{to}}", "deleted": "Deleted {{path}}", "delete": "Delete", "delete_file_title": "Delete {{name}}?", "delete_folder_title": "Delete the folder {{name}}?", - "delete_file_body": "The file is deleted for good. Take a backup first if you may want it back.", - "delete_folder_body": "The folder and everything in it are deleted for good. Take a backup first if you may want any of it back.", - "secret_config_unreadable": "config/paper-global.yml holds the proxy forwarding secret every server shares, so the editor does not open it.", + "delete_file_body": "The file will be permanently deleted. Back up any required content before deletion.", + "delete_folder_body": "The folder and all its contents will be permanently deleted. Back up any required content before deletion.", + "secret_config_unreadable": "config/paper-global.yml contains the shared proxy forwarding secret and cannot be read or edited online.", "drop_here": "Drop to upload into {{dir}}", - "upload_no_folders_one": "Folders cannot be uploaded, so one was skipped: loose files cut off halfway leave a broken save behind. Zip it, upload the .zip, then choose Extract here on it.", - "upload_no_folders_other": "Folders cannot be uploaded, so {{count}} were skipped: loose files cut off halfway leave a broken save behind. Zip them, upload the .zip, then choose Extract here on it.", + "upload_no_folders_one": "Direct folder upload is unsupported. Folders skipped: 1. To prevent incomplete saves after interrupted uploads, compress folders as .zip archives, upload them, and select Extract here.", + "upload_no_folders_other": "Direct folder upload is unsupported. Folders skipped: {{count}}. To prevent incomplete saves after interrupted uploads, compress folders as .zip archives, upload them, and select Extract here.", "uploads_label": "Uploads", "uploads_title": "Uploads · {{done}} of {{total}} done", "upload_queued": "Waiting", "upload_sending": "{{sent}} of {{total}} · {{percent}}%", "upload_landing": "Writing it to the server…", "upload_done": "Uploaded", - "upload_exists": "A file with this name is already here.", + "upload_exists": "A file with this name already exists in the current directory.", "upload_replace": "Replace", "upload_skip": "Skip", "upload_replace_all": "Replace all", @@ -78,33 +78,33 @@ "upload_dismiss": "Dismiss {{name}}", "upload_clear_done": "Clear finished", "upload_progress_label": "Uploading {{name}}", - "upload_folder_there": "A folder with this name is already here.", - "upload_name_too_long": "This file's name is longer than the 255 bytes a file name on the server can hold. Rename it shorter, then upload it.", - "upload_no_room": "The world volume has {{free}} free, not enough for this {{size}} file. Delete files you do not need, then retry.", + "upload_folder_there": "A folder with this name already exists in the current directory.", + "upload_name_too_long": "The filename exceeds 255 bytes. Shorten the filename and upload the file again.", + "upload_no_room": "The world volume has {{free}} available, which is insufficient for a {{size}} file. Free storage space and retry.", "upload_landing_progress": "Writing it to the server… {{percent}}%", - "wait_for_op": "Wait for the background operation to finish first.", - "wait_for_download": "Wait until the download is ready first.", - "wait_for_backup": "A backup of this world is running. Change files once it finishes.", - "wait_for_restore": "This world is being restored. Change files once the restore finishes.", - "wait_for_world_export": "This world is being exported. Change files once that download ends.", - "wait_for_file_download": "A download is reading this world. Change files once it has finished.", - "unzip_item": "Extract {{name}} here", + "wait_for_op": "Files cannot be modified until the background operation completes.", + "wait_for_download": "This operation is unavailable until download preparation completes.", + "wait_for_backup": "World backup is in progress. File changes are available after completion.", + "wait_for_restore": "World restoration is in progress. File changes are available after completion.", + "wait_for_world_export": "World export is in progress. File changes are available after the download ends.", + "wait_for_file_download": "A file download is reading world data. File changes are available after the transfer completes.", + "unzip_item": "Extract {{name}} to the current directory", "unzip_conflicts_title_one": "Extracting {{name}} replaces {{count}} file", "unzip_conflicts_title_other": "Extracting {{name}} replaces {{count}} files", - "unzip_conflicts_body": "These files in the archive are already here. Confirm to replace them with the archive's versions; everything else extracts as usual. Take a backup first if you may want the old versions back.", - "unzip_conflicts_more": "{{count}} more not listed.", + "unzip_conflicts_body": "Some archive entries conflict with files in the current directory. Confirmation overwrites conflicting files and extracts the remaining entries. Back up any required original files before proceeding.", + "unzip_conflicts_more": "Additional entries not listed: {{count}}.", "unzip_overwrite": "Replace and extract", "download_item": "Download {{name}}", - "download_folder_item": "Download the folder {{name}} as a .zip", + "download_folder_item": "Download folder {{name}} as a .zip archive", "download_preparing": "Preparing the download of {{name}}…", "download_started": "Downloading {{filename}}.", - "download_started_props": "Downloading {{filename}}. Its rcon.password is redacted.", - "download_started_config": "Downloading {{filename}}. paper-global.yml, the proxy forwarding secret every server shares, is left out.", - "download_failed": "The download could not be prepared.", - "download_failed_because": "The download could not be prepared: {{reason}}", - "download_out_of_memory": "This folder holds more files than the zipping task has memory to list, so the system stopped it and nothing was downloaded. Download its folders one by one instead.", - "download_busy": "Too many file downloads are being prepared: two at a time per person, four at a time across the platform, and thirty an hour per person. Try again in a few minutes.", - "secret_config_no_download": "config/paper-global.yml holds the proxy forwarding secret every server shares, so it cannot be downloaded.", + "download_started_props": "Download started for {{filename}}. The rcon.password value is redacted in the downloaded content.", + "download_started_config": "Download started for {{filename}}. paper-global.yml, which contains the shared proxy forwarding secret, is excluded.", + "download_failed": "Download preparation failed.", + "download_failed_because": "Download preparation failed: {{reason}}", + "download_out_of_memory": "The packaging task terminated because its file list exceeded available memory. The download did not start. Split the content into smaller folders and download them separately.", + "download_busy": "File download limits have been reached: two concurrent requests per account, four across the platform, and thirty per account per hour. Retry after existing tasks complete or rate limits reset.", + "secret_config_no_download": "config/paper-global.yml contains the shared proxy forwarding secret and cannot be downloaded.", "ops_label": "Background operations", "ops_refresh_failed": "Could not read the background operations: {{reason}}", "op_unzip": "Extract {{path}}", @@ -117,19 +117,19 @@ "op_upload_done": "Written.", "op_dismiss": "Dismiss the result for {{path}}", "op_conflicts": "Review conflicts", - "op_upload_exists": "A file with this name is already here, so nothing was replaced. Upload it again and choose Replace.", - "op_unzip_conflicts_one": "It would replace {{count}} file already here, so nothing was extracted yet.", - "op_unzip_conflicts_other": "It would replace {{count}} files already here, so nothing was extracted yet.", - "op_volume_full": "Not enough room on the world volume: {{need}} needed, {{avail}} free. Nothing was changed.", - "archive_invalid": "The archive is damaged, or not a zip file. Nothing was changed.", - "archive_invalid_entry": "{{entry}} in the archive is damaged (its size or checksum does not match). Nothing was changed.", - "archive_unsafe": "{{entry}} in the archive would land outside this folder, or is a device file. Nothing in the archive was extracted.", - "archive_symlink": "{{entry}} in the archive is a symbolic link. Nothing in the archive was extracted.", - "type_conflict": "{{entry}} is a file on one side and a folder on the other, which replacing cannot resolve. Rename or delete {{entry}} here, then extract again.", - "job_failed": "The background task ended without saying why. Refresh the list to check, then try again.", - "job_timed_out": "The background task did not finish within 2 hours and was stopped. Refresh the list to check, then try again.", - "job_out_of_memory": "The background task ran out of memory and the system stopped it. The files on the server were not changed. Try again.", - "job_out_of_memory_unzip": "The archive holds more files than the extraction task has memory to list, so the system stopped it. The files on the server were not changed. Split it into several smaller zips and extract each one.", + "op_upload_exists": "A file with this name already exists. The upload did not replace it. Select Replace when uploading again.", + "op_unzip_conflicts_one": "Extraction would overwrite {{count}} existing file. The operation has not been performed.", + "op_unzip_conflicts_other": "Extraction would overwrite {{count}} existing files. The operation has not been performed.", + "op_volume_full": "Insufficient world volume space: {{need}} required, {{avail}} available. This operation did not modify files.", + "archive_invalid": "The archive is damaged or is not in ZIP format. This operation did not modify files.", + "archive_invalid_entry": "The size or checksum of archive entry {{entry}} does not match. This operation did not modify files.", + "archive_unsafe": "Archive entry {{entry}} contains an out-of-directory path or a device file. The entire archive was rejected without extraction.", + "archive_symlink": "Archive entry {{entry}} is a symbolic link. The entire archive was rejected without extraction.", + "type_conflict": "Entry {{entry}} conflicts with an existing entry of the same name because one is a file and the other is a folder. Rename or delete the existing {{entry}} before extraction.", + "job_failed": "The background task ended without a failure reason. Refresh the file list to verify the result before retrying.", + "job_timed_out": "The background task exceeded its two-hour execution limit and was terminated. Refresh the file list to verify the result before retrying.", + "job_out_of_memory": "The background task terminated due to insufficient memory. Server files were not modified. Check task resources before retrying.", + "job_out_of_memory_unzip": "The extraction task terminated because its file list exceeded available memory. Server files were not modified. Split the content into smaller ZIP archives and upload and extract them separately.", "binary_title": "This is a binary file", "preview_large_title": "File exceeds the preview limit", "preview_large_hint": "The editor reads files up to {{limit}}. Download it to edit, then upload your changes.", diff --git a/panel/src/i18n/resources/en-US/lobby.json b/panel/src/i18n/resources/en-US/lobby.json index 2c22dc2..7f5a37c 100644 --- a/panel/src/i18n/resources/en-US/lobby.json +++ b/panel/src/i18n/resources/en-US/lobby.json @@ -4,8 +4,8 @@ "space_label": "Choose a space", "login": "Login space", "lobby": "Lobby", - "login_description": "Players authenticate here before entering the lobby. This Limbo scene uses a schematic, rather than a Paper world map.", - "lobby_description": "Players choose their server here. Replace maps, install plugins, customize the experience and grant builders access.", + "login_description": "Players enter the main lobby after authentication in the login space. Login scenes use Limbo schematics and do not directly support Paper world maps.", + "lobby_description": "The main lobby provides server selection and supports map, plugin, experience, and building-permission configuration.", "connection_version": "Installed game version: Java {{version}}", "unknown_version": "not recorded", "version_hint": "Client compatibility depends on proxy plugins. Version changes need matching login, lobby and proxy support; this is the installation version, while the running version comes from each image.", @@ -18,7 +18,7 @@ "restarting": "Restarting…", "restart_requested": "Restart requested. Follow the startup status above.", "restart_title": "Restart this space?", - "restart_hint": "Players here will be disconnected briefly. Saved settings take effect when this space starts again.", + "restart_hint": "Restarting disconnects players in this space. Saved settings take effect after startup.", "restart_required": "Settings saved; restart to apply", "save": "Save settings", "saving": "Saving…", @@ -57,7 +57,7 @@ "bookHelp": "Login instructions", "openBook": "Open the login book automatically", "loginTimeoutSeconds": "Login timeout (30–3600 seconds)", - "login_settings_hint": "The system still generates the code and login URL and sends the chat guidance. An explicit login timeout environment variable takes precedence over this setting.", + "login_settings_hint": "Binding codes and sign-in links are generated by the system. Chat prompts remain enabled. An explicitly configured login-timeout environment variable overrides this setting.", "lobby_settings_hint": "The welcome message includes a clickable server menu. Spawn comes from the map; use setworldspawn x y z in the lobby console to change it. Protection switches apply across the lobby. Builders keep their own game mode.", "ADVENTURE": "Adventure", "SURVIVAL": "Survival", @@ -80,13 +80,13 @@ "backups_title": "Map backups & restore", "backups_hint": "Back up before replacing maps, plugins or images; choose a saved backup to restore.", "builder_title": "Lobby builder access", - "builder_hint": "Grant felis.lobby.build to let a player build without opening building to everyone.", + "builder_hint": "Granting felis.lobby.build allows the specified player to build in the lobby without enabling building for all players.", "builder_player": "Minecraft player name", "builder_grant": "Allow building", "builder_revoke": "Deny building", "builder_running": "Start the lobby before changing builder access.", "discard_title": "Discard unsaved settings?", - "discard_hint": "Switching spaces discards your edits. Cancel to save them first.", + "discard_hint": "Switching spaces discards unsaved changes. Cancel the switch and save the configuration to retain them.", "discard": "Discard & switch", "appearance_description": "Set the welcome message and server menu players see.", "players_description": "Choose join behavior, game mode and player protection.", diff --git a/panel/src/i18n/resources/en-US/ops.json b/panel/src/i18n/resources/en-US/ops.json index b8e30b9..4e833b4 100644 --- a/panel/src/i18n/resources/en-US/ops.json +++ b/panel/src/i18n/resources/en-US/ops.json @@ -16,7 +16,7 @@ "fleet_sort": "Sort order", "fleet_sort_name": "Sort by name", "fleet_sort_status": "Sort by status", - "fleet_sort_players": "Most players first", + "fleet_sort_players": "Online players, descending", "fleet_count": "{{count}} servers", "fleet_count_filtered": "{{shown}} / {{total}} servers", "fleet_col_status": "Status", @@ -29,13 +29,13 @@ "fleet_unclaimed": "Unclaimed", "fleet_owner_you": "You", "fleet_owner_unknown": "Unknown", - "fleet_owner_unknown_hint": "Couldn't look up the owner just now; it shows again on the next refresh.", + "fleet_owner_unknown_hint": "Server ownership could not be read. It will be queried again on the next refresh.", "fleet_endpoint_idle": "Not running", "policy_owneronly": "Owner only", "policy_public": "Public", "policy_allowlist": "Wake list", "fleet_empty_title": "No servers", - "fleet_empty_hint": "No MinecraftServer exists in the cluster yet.", + "fleet_empty_hint": "The cluster contains no MinecraftServer resources.", "fleet_no_match_title": "No matches", "fleet_no_match_hint": "No server matches the current search or filter." } diff --git a/panel/src/i18n/resources/en-US/schedules.json b/panel/src/i18n/resources/en-US/schedules.json index f8c9915..e80d918 100644 --- a/panel/src/i18n/resources/en-US/schedules.json +++ b/panel/src/i18n/resources/en-US/schedules.json @@ -1,28 +1,28 @@ { "title": "Scheduled tasks", - "subtitle": "Restart, stop, start or back up this server, or send its console a command, at set times.", + "subtitle": "Schedule server startup, shutdown, restart, backup, or console commands.", "back_to_console": "Back to console", "not_yours_title": "No permission to manage scheduled tasks", "not_yours_body": "Only the owner or an admin can view and change this server's scheduled tasks.", "new_task": "New task", "count": "{{count}} of {{limit}} tasks", - "limit_reached": "A server holds at most {{limit}} tasks. Delete one to add another.", + "limit_reached": "The scheduled-task limit of {{limit}} has been reached. Remove an existing task before adding another.", "unavailable_title": "Scheduled tasks aren't available", - "unavailable_body": "This felis-api runs without a schedule store, so tasks can't be listed or saved here. Ask an administrator to check the felis-api setup.", - "empty_title": "No scheduled tasks yet", - "empty_hint": "Start from a common one, or build your own with New task.", + "unavailable_body": "felis-api has no schedule storage configured. Tasks cannot be read or saved. Contact an administrator to check the configuration.", + "empty_title": "No scheduled tasks", + "empty_hint": "Select a task template or select New task to configure a custom task.", "template_restart": "Restart every night at 04:00", "template_backup": "Back up every night at 05:00", "template_announce": "Announce in chat every 30 minutes", "template_announce_command": "say Welcome to the server!", "action_command": "Console command", - "action_command_desc": "Send one command to the server console, as if typed there. Runs only while the server is running.", + "action_command_desc": "Send a command to the server console with the same behavior as direct console input. Execution requires a running server.", "action_restart": "Restart", "action_restart_desc": "Stop the server and start it again. Online players are disconnected. Skipped while it is stopped.", "action_stop": "Stop", "action_stop_desc": "Stop the server. Online players are disconnected.", "action_start": "Start", - "action_start_desc": "Start the server when it is stopped.", + "action_start_desc": "Start the server only when it is stopped.", "action_backup": "Backup", "action_backup_desc": "Back up the world. A running server is stopped first and started again afterwards.", "days_all": "Every day", @@ -63,30 +63,30 @@ "opt_hours_other": "{{count}} hours", "create_title": "New scheduled task", "edit_title": "Edit scheduled task", - "dialog_desc": "Choose what runs and when.", + "dialog_desc": "Configure the task type, execution time, and runtime parameters.", "create": "Create", "save": "Save", - "field_action": "What to do", + "field_action": "Task type", "field_command": "Command", "command_placeholder": "say The server restarts at 04:00", - "command_hint": "One console command. The leading / is optional.", - "field_when": "When", + "command_hint": "Enter one console command. The leading / is optional.", + "field_when": "Execution mode", "mode_daily": "Once a day", "mode_interval": "Repeat", "field_time": "Time", "field_every": "Repeat every", "interval_preview": "Counted from 00:00 each day: {{times}}", "interval_hourly_limit": "A restart, stop, start or backup repeats at most once an hour.", - "field_days": "Days", + "field_days": "Execution days", "field_timezone": "Time zone", - "timezone_hint": "Times are read in this zone. Your browser is in {{zone}}.", + "timezone_hint": "Execution times use the selected timezone. Browser timezone: {{zone}}.", "timezone_use_browser": "Use the browser's zone", "field_warn": "Warn players", "warn_none": "No warning", "warn_option_one": "{{count}} minute before", "warn_option_other": "{{count}} minutes before", "warn_hint": "Posts a message in game chat ahead of the stop, in Chinese and English, while the server is running.", - "backup_note": "A backup of a running server stops it (online players are disconnected), backs up the world and starts it again, which usually takes a few minutes. A stopped server is backed up and stays stopped.", + "backup_note": "Backing up a running server first stops it and disconnects online players, then starts it after the backup completes. A stopped server remains stopped after backup.", "field_label": "Name (optional)", "label_placeholder": "Nightly restart", "field_enabled": "Enabled", @@ -99,8 +99,8 @@ "err_timezone": "Unknown time zone. Use a name such as Asia/Shanghai.", "err_label_long": "The name is longer than {{max}} characters.", "next_run": "Next run {{when}}", - "next_run_off": "Off: it runs only when you press Run now", - "never_run": "Hasn't run yet", + "next_run_off": "Disabled; runs only when Run now is selected", + "never_run": "Not executed", "last_run": "Last run {{when}}", "run_started_at": "Started {{when}}", "result_ok": "Succeeded", @@ -114,20 +114,20 @@ "run_state_starting": "Starting the server…", "warn_badge": "Warns {{count}} min before", "off_badge": "Off", - "tz_differs": "Times are in {{zone}}; your browser is in {{browser}}.", + "tz_differs": "Task timezone: {{zone}}. Browser timezone: {{browser}}.", "toggle_label": "Run {{name}} on schedule", "run_now": "Run now", "edit": "Edit", "delete": "Delete", - "busy_hint": "This task is running. Wait for the run to finish.", - "owner_changed": "The server changed owner, so this task switched itself off. Check it, then save it or switch it on to use it again.", + "busy_hint": "The task is running. Wait for the current execution to complete.", + "owner_changed": "The server owner has changed and the task was disabled automatically. Verify the configuration before saving or enabling it again.", "detail_not_running": "The server was not running.", "detail_already_stopped": "The server was already stopped.", "detail_already_running": "The server was already running.", "detail_gone": "The server no longer exists.", - "detail_no_world": "The server has no world yet.", + "detail_no_world": "The server has no world save.", "detail_missed": "felis-api was not running at the scheduled time.", - "detail_started_meanwhile": "Someone started the server before the run finished.", + "detail_started_meanwhile": "Another operation started the server before the task completed.", "detail_interrupted": "felis-api stopped in the middle of this run.", "detail_store_full": "The backup store is full. Ask an administrator to free space.", "detail_console": "The server console could not be reached.", @@ -135,15 +135,15 @@ "detail_retiring": "The server is being given up or deleted.", "run_title": "Run “{{name}}” now?", "run_confirm": "Run now", - "run_body_command": "Sends this command to the server console now (skipped while the server is stopped):", + "run_body_command": "Send the following command to the server console. Execution is skipped if the server is stopped:", "run_body_restart": "Stops the server now and starts it again.", "run_body_stop": "Stops the server now.", - "run_body_start": "Starts the server now if it is stopped.", + "run_body_start": "Start the server immediately if it is stopped.", "run_body_backup": "Backs up the world now. A running server is stopped, backed up and started again; a stopped server stays stopped.", "run_kick": "Online players are disconnected right away: a run started by hand skips the players' warning.", "run_keeps_next": "The next scheduled run stays as planned.", "delete_title": "Delete “{{name}}”?", - "delete_body": "The task is removed and won't run again.", + "delete_body": "The task will be deleted and will no longer execute. Its configuration will not be retained.", "delete_confirm": "Delete", "created": "Created “{{name}}”.", "saved": "Saved “{{name}}”.", @@ -154,6 +154,6 @@ "notes_title": "How scheduled tasks run", "note_timing": "felis-api checks every 15 seconds, so a task starts within about 15 seconds of its time. A run more than 10 minutes late (felis-api was down) is recorded as missed, and the next one runs as usual.", "note_state": "A command, restart or stop runs only while the server is running, and a start only while it is stopped; otherwise the run is skipped.", - "note_owner": "When the server changes owner, its tasks switch off. Saving a task hands it to the current owner.", + "note_owner": "Tasks are disabled automatically when server ownership changes. Saving a task again assigns it to the current owner.", "note_run_now": "Run now also works on a task that is off. It skips the players' warning and keeps the next scheduled run." } diff --git a/panel/src/i18n/resources/en-US/servers.json b/panel/src/i18n/resources/en-US/servers.json index a43418f..5753609 100644 --- a/panel/src/i18n/resources/en-US/servers.json +++ b/panel/src/i18n/resources/en-US/servers.json @@ -1,12 +1,12 @@ { "my_servers_title": "My servers", - "my_servers_subtitle": "Wake, stop, or claim the servers you can manage.", + "my_servers_subtitle": "Manage servers owned by or accessible to the current account.", "search_placeholder": "Search server name or address…", "filter_status_all": "All statuses", "search_no_match": "No matching servers found.", "search_clear_btn": "Clear filters", "no_servers_linked": "No servers linked to you", - "no_servers_hint": "Claim an unowned server you have access to, or ask an admin to provision one.", + "no_servers_hint": "Claim an authorized server or request allocation from an administrator.", "phase_running": "Running", "phase_starting": "Starting", "phase_stopping": "Stopping", @@ -18,7 +18,7 @@ "console": "Console", "claim": "Claim", "claim_server_title": "Claim Server", - "claim_server_desc": "Are you sure you want to claim the server \"{{name}}\"? Once claimed, you will have access to its console, player management, and backups.", + "claim_server_desc": "Claiming “{{name}}” assigns ownership to the current account and grants console, whitelist, and backup management permissions.", "claiming": "Claiming…", "stop": "Stop", "stopping": "Stopping…", @@ -66,7 +66,7 @@ "system_service": "System service", "system_service_hint": "Staff manage system services in Login & lobby.", "players_back_to_console": "Back to console", - "players_not_yours_title": "Not your server", + "players_not_yours_title": "Server management access denied", "players_not_yours_body": "Only the owner or an admin can manage this server's players.", "players_not_running_title": "Server is asleep", "players_not_running_body": "Player management runs over a live connection to the server. Wake it to manage the whitelist, online players, and bans.", @@ -83,19 +83,19 @@ "access_whitelist_title": "Whitelist", "access_whitelist_desc": "When the whitelist is on, only listed players can join.", "access_whitelist_add": "Add", - "access_whitelist_empty": "No players on the whitelist yet.", - "access_whitelist_empty_hint": "Add a player above to let them join.", + "access_whitelist_empty": "The whitelist contains no players.", + "access_whitelist_empty_hint": "Players added to the whitelist are permitted to join the server.", "access_whitelist_remove": "Remove {{player}} from the whitelist", "access_remove": "Remove", - "access_whitelist_load_error": "Couldn't load the whitelist.", + "access_whitelist_load_error": "Unable to load the whitelist.", "access_whitelist_added": "Added {{player}} to the whitelist.", "access_whitelist_removed": "Removed {{player}} from the whitelist.", "access_whitelist_raw": "View raw server reply", "access_online_title": "Online players", - "access_online_desc": "Players on the server right now.", - "access_online_load_error": "Couldn't load online players.", - "access_online_empty": "No one is online right now.", - "access_online_names_unavailable": "{{count}} online, but the names couldn't be read.", + "access_online_desc": "Players currently connected to the server.", + "access_online_load_error": "Unable to load online players.", + "access_online_empty": "No players online.", + "access_online_names_unavailable": "{{count}} online, but the names could not be read.", "access_online_names_unavailable_hint": "See the raw server reply below for names.", "access_online_raw": "View raw server reply", "access_updated_at": "Updated {{time}}", @@ -108,19 +108,19 @@ "access_ban_confirm": "Ban {{player}}? They'll be kicked and blocked from rejoining.", "access_ban_confirm_yes": "Ban", "access_ban_empty": "No banned players.", - "access_ban_empty_hint": "Banned players show up here, ready to pardon in one tap.", - "access_ban_load_error": "Couldn't load the ban list.", + "access_ban_empty_hint": "The ban list displays banned players and provides pardon actions.", + "access_ban_load_error": "Unable to load the ban list.", "access_ban_raw": "View raw server reply", "access_cancel": "Cancel", "access_banned": "Banned {{player}}.", "access_pardoned": "Pardoned {{player}}.", "wake_list_title": "Wake list", - "wake_list_desc": "With the autostart policy set to Wake list, a sleeping server can be started only by you, admins, and the players here who may wake it. A player is added on their first join. Once you take someone's wake right away, rejoining does not give it back; you can allow them again here. The list is emptied when the server changes owner.", - "wake_list_inactive_owner": "The autostart policy is Owner only, so only you and admins can start it while it sleeps and this list has no effect. Switch it to Wake list under Edit server configuration on the console to use it.", - "wake_list_inactive_public": "The autostart policy is Public, so any player's join starts it and this list has no effect. Switch it to Wake list under Edit server configuration on the console so only the players here can start it.", - "wake_list_load_error": "Couldn't load the wake list.", - "wake_list_empty": "No player has joined this server yet.", - "wake_list_empty_hint": "Players show up here after their first join.", + "wake_list_desc": "With the Wake list autostart policy, only the server owner, administrators, and players with wake permission can start a sleeping server. Players are added on first join. Rejoining does not restore revoked permission; it must be granted manually. The list is cleared when server ownership changes.", + "wake_list_inactive_owner": "The current Owner only policy permits only the server owner and administrators to start a sleeping server. The wake list is inactive. Change the policy to Wake list in Edit Server Config to enable it.", + "wake_list_inactive_public": "The current Public policy allows any player’s join to trigger startup. The wake list is inactive. The Wake list policy restricts startup to the server owner, administrators, and players with wake permission.", + "wake_list_load_error": "Unable to load the wake list.", + "wake_list_empty": "No player join records available.", + "wake_list_empty_hint": "Players are added to the list after their first join.", "wake_list_unlinked": "Player without a linked account", "wake_list_joined": "first joined {{when}}", "wake_list_revoked_badge": "Revoked", @@ -132,12 +132,12 @@ "wake_list_restored": "{{player}} can wake the server again.", "create_server_btn": "New server", "create_server_title": "Create a server", - "create_server_desc": "Pick from whitelisted images and sizes — the platform provisions the rest. No raw cluster config is exposed here.", + "create_server_desc": "Select a whitelisted image and resource specification. The platform configures the server automatically. Raw cluster configuration editing is not provided.", "create_server_name": "Name", "create_server_name_placeholder": "survival", "create_server_name_hint": "3–32 lowercase letters, digits or hyphens.", "create_server_name_invalid": "Use 3–32 lowercase letters, digits or hyphens, with no hyphen at either end.", - "create_server_name_reserved": "“{{name}}” is kept for the platform. Pick another.", + "create_server_name_reserved": "“{{name}}” is reserved by the platform. Use a different name.", "create_server_subdomain": "Subdomain", "create_server_subdomain_placeholder": "survival", "create_server_subdomain_hint": "Will be reachable at {{host}}", @@ -146,15 +146,15 @@ "create_server_image": "Image", "create_server_image_loading": "Loading images…", "create_server_image_choose": "Choose an image", - "create_server_image_unavailable": "Image list not loaded", - "create_server_image_failed": "Couldn't load the image list: {{reason}}", + "create_server_image_unavailable": "Image list unavailable", + "create_server_image_failed": "Unable to load the image list: {{reason}}", "create_server_image_none": "No whitelisted images", "create_server_memory": "Memory", "create_server_storage": "Storage", "create_server_policy": "Autostart policy", "create_server_policy_owner": "Owner only — wake from the panel", "create_server_policy_public": "Public — any player join wakes it", - "create_server_policy_allowlist": "Wake list — players who have joined wake it", + "create_server_policy_allowlist": "Wake list — previous players with wake permission can start the server", "create_server_cancel": "Cancel", "create_server_submit": "Create", "edit_server_title": "Edit Server Config", @@ -162,8 +162,8 @@ "edit_server_desc_long": "Updating server spec. Fields left unchanged will retain their current values.", "edit_server_submit": "Save Config", "edit_server_image_build": "build {{id}}", - "edit_server_image_pinned_hint": "Locked to build {{id}}: the tag moving to a newer build (an installer re-run, say) leaves this server alone; only changing the image here moves it.", - "edit_server_image_warning": "After saving, the server runs the build this tag points to right now — re-picking the current tag also gets its newest build. If the Minecraft version changes, the world is upgraded on its next start, and upgraded chunks can't be opened by the old version again.", + "edit_server_image_pinned_hint": "The server is pinned to build {{id}}. Changes to the image tag do not update this server automatically. A new build is used only after the image configuration is changed.", + "edit_server_image_warning": "Saving selects the build currently referenced by the image tag. Selecting the same tag again also selects its latest build. A Minecraft version change may upgrade the world on the next startup. Upgraded chunks are not compatible with older versions.", "edit_server_image_confirm": "I've backed up the world — change the image", "luckperms_group_name": "Group Name", "luckperms_node": "Permission Node", @@ -183,9 +183,9 @@ "luckperms_remove_group_btn": "Remove Parent Group", "luckperms_remove_perm_btn": "Remove Permission", "luckperms_perm_form_title": "Add or remove a permission", - "luckperms_remove_perm_hint": "Remove clears the node in the given world context whether it was granted or denied; Value applies to Add only.", - "luckperms_groups_unread": "Couldn't read this player's groups. To remove one, type its name below and press Remove Parent Group.", - "luckperms_perms_unread": "Couldn't read this player's permission nodes. To remove one, type the node (and world, if it was set per world) below and press Remove Permission.", + "luckperms_remove_perm_hint": "Removal deletes the permission node in the specified world context regardless of its allow or deny value. Permission values apply only to additions.", + "luckperms_groups_unread": "The player’s current groups could not be read. Enter a group name and select Remove Parent Group.", + "luckperms_perms_unread": "The player’s current permission nodes could not be read. Enter the node name and applicable world context, then select Remove Permission.", "luckperms_groups_title": "Parent Groups", "luckperms_permissions_title": "Permission Nodes", "luckperms_node_column": "Permission Node", @@ -198,16 +198,16 @@ "luckperms_recent_actions": "Recent Actions History", "luckperms_no_recent_actions": "No recent actions.", "luckperms_revert": "Revert", - "luckperms_revert_to_set": "Revert sets {{node}} back to {{value}}", + "luckperms_revert_to_set": "Reverting restores {{node}} to {{value}}", "luckperms_revert_to_unset": "Revert removes {{node}}", - "luckperms_revert_unknown": "Can't revert: value unknown", + "luckperms_revert_unknown": "Cannot revert: previous value unknown", "luckperms_remove_group": "Remove group {{group}}", "luckperms_remove_perm": "Remove permission {{node}}", - "luckperms_revert_failed": "Couldn't revert: {{reason}}", - "luckperms_revert_success": "Action reverted successfully!", + "luckperms_revert_failed": "Unable to revert: {{reason}}", + "luckperms_revert_success": "Operation reverted.", "luckperms_presets": "Presets", "luckperms_no_parent_groups": "No parent groups assigned", - "luckperms_no_reply": "LuckPerms doesn't answer over RCON on this server, so the player's current groups and permission nodes can't be shown. Adding and removing still go through: type the group or node name below. Name-based changes only bind for players who have joined since LuckPerms was installed, so changes against a name that never joined may silently do nothing.", + "luckperms_no_reply": "LuckPerms did not return query results through RCON, so current groups and permission nodes cannot be displayed. Additions and removals remain available. Name-based changes are reliable only for players who have joined since LuckPerms was installed. Changes for other names may not apply and may return no error.", "luckperms_no_output": "(the server returned no output — result unknown)", "luckperms_global": "global", "luckperms_no_perms": "No explicit permission nodes assigned", @@ -215,43 +215,43 @@ "luckperms_clear_history": "Clear history", "edit_server_cpu": "CPU Limit", "edit_server_cpu_placeholder": "e.g. 1, 1.5, 500m — empty for no limit", - "edit_server_cpu_invalid": "Enter cores (1, 1.5) or millicores (500m), above zero.", + "edit_server_cpu_invalid": "The CPU limit must be greater than zero, expressed as cores (for example, 1 or 1.5) or millicores (for example, 500m).", "owned_filter_mine": "me", "edit_server_idle": "Idle auto-stop", - "edit_server_idle_hint": "Stops the server after this long with nobody online, freeing memory and CPU. The next player to join wakes it.", - "edit_server_idle_unknown": "The player count can’t be read right now, so idle stop is paused until it can.", + "edit_server_idle_hint": "When the online count remains zero for the configured duration, the server stops automatically to release memory and CPU. Subsequent joins are handled according to the autostart policy.", + "edit_server_idle_unknown": "The online player count could not be read. Idle shutdown is paused until player count retrieval resumes.", "idle_stop_never": "Never", "idle_stop_seconds": "{{count}} s", "idle_stop_minutes": "{{count}} min", "idle_stop_hours": "{{count}} h", - "stop_confirm_players_one": "1 player is online. They get a 30-second warning in game, then are disconnected. Stop anyway?", - "stop_confirm_players_other": "{{count}} players are online. They get a 30-second warning in game, then are disconnected. Stop anyway?", - "stop_confirm_unknown": "The player count can't be read, so players may be online. Stop anyway?", + "stop_confirm_players_one": "One player is online. Shutdown sends an in-game notice and disconnects the player after 30 seconds. Confirm shutdown?", + "stop_confirm_players_other": "There are {{count}} players online. Shutdown sends an in-game notice and disconnects players after 30 seconds. Confirm shutdown?", + "stop_confirm_unknown": "The online player count could not be read. Players may still be connected. Confirm shutdown?", "retire_card_title": "Give up server", "retire_card_title_admin": "Give up or delete server", - "retire_card_desc": "Hand it back when you no longer need it: the world is archived, then the server is freed for someone else to claim.", + "retire_card_desc": "Releasing the server archives its world and makes the server available for other accounts to claim.", "retire_card_desc_admin": "Giving up archives the world and frees the server; deleting archives the world and removes the server, freeing its name and subdomain.", "retire_release": "Give up", "retire_delete": "Delete", "retire_release_title": "Give up \"{{name}}\"?", "retire_delete_title": "Delete \"{{name}}\"?", - "retire_step_stop": "The server stops now and can't be started until this is done.", + "retire_step_stop": "The server enters the shutdown process. Startup is unavailable until reclamation completes or the request is cancelled.", "retire_step_release": "At the next daily reclaim run the world is archived (kept 90 days by default), its storage deleted, and the server freed for someone else to claim.", - "retire_step_delete": "At the next daily reclaim run the world is archived (kept 90 days by default), then its storage and this server are deleted, freeing the name and subdomain.", - "retire_step_cancel_owner": "You can cancel from the console until the run; until then it still counts toward your quota.", + "retire_step_delete": "The next daily reclaim run archives the world (retained for 90 days by default), then deletes world storage and the server record, releasing the name and subdomain.", + "retire_step_cancel_owner": "The request can be cancelled from the console before reclamation runs. The server counts toward the account quota until reclamation completes.", "retire_step_cancel_admin": "An administrator can cancel from the console until the run.", "retire_confirm_label": "Type the server name to confirm:", "retire_confirm_release": "Give up server", "retire_confirm_delete": "Delete server", - "retiring_title_release": "This server has been given up", - "retiring_title_delete": "This server is about to be deleted", - "retiring_body_release": "Requested {{when}}. At the next daily reclaim run the world is archived, then its storage is deleted and the server freed. Until then it stays stopped, can't be started and still counts toward the quota.", - "retiring_body_delete": "Requested {{when}}. At the next daily reclaim run the world is archived, then its storage and this server are deleted. Until then it stays stopped and can't be started.", + "retiring_title_release": "Server pending reclamation", + "retiring_title_delete": "Server pending deletion", + "retiring_body_release": "Requested {{when}}. The next daily reclaim run archives the world, deletes world storage, and releases the server. Until completion, the server remains stopped, cannot be started, and counts toward the account quota.", + "retiring_body_delete": "Requested {{when}}. The next daily reclaim run archives the world and deletes world storage and the server record. Until completion, the server remains stopped and cannot be started.", "retiring_cancel": "Cancel request", "retiring_cancel_admin_only": "An administrator asked for the deletion, so only an administrator can cancel it.", "retiring_badge_release": "Given up", "retiring_badge_delete": "Deleting", - "retiring_badge_hint": "Given up or being deleted; handled at the next daily reclaim run, and it can't be started until then.", + "retiring_badge_hint": "Release or deletion is pending until the next daily reclaim run. Startup is unavailable until completion or cancellation.", "nodes": "Execution nodes", "node": "Execution node", "node_choose": "Choose an approved node", @@ -278,21 +278,21 @@ "startup_preparing": "Preparing files and pulling images", "startup_booting": "Starting Minecraft and checking readiness", "startup_elapsed": "Startup requested {{seconds}} seconds ago", - "startup_memory": "Not enough schedulable memory. Reduce this server’s memory allocation, stop another server, or add node capacity. Java has not started yet.", - "startup_hint": "Startup status refreshes automatically. Scheduling, file preparation and Minecraft startup are separate stages; no log output does not mean the process has hung.", + "startup_memory": "Insufficient schedulable memory. Reduce this server’s memory allocation, stop other servers, or increase node capacity. Java has not started.", + "startup_hint": "Startup includes resource scheduling, file preparation, and game initialization. Status updates automatically. When logs are unavailable, use the current stage and diagnostics to assess progress.", "status_stale": "Status unavailable", "status_stale_detail": "Cannot confirm the current server state. Last successful read: {{time}}. Retrying automatically. {{error}}", "startup_failed": "Startup blocked or container failed", - "logs_unavailable_title": "Logs are not available yet", + "logs_unavailable_title": "Logs unavailable", "logs_unavailable_body": "The container has not started producing logs. Check the startup diagnostics above; logs attach automatically once available.", "startup_problem": "Startup issue", "startup_waiting_resources": "Waiting for resources", "startup_failed_hint": "Check the image, configuration and memory using the reported error. Correct the problem before restarting the server.", "emergency_stop": "Emergency stop", - "emergency_stop_warning": "Use when normal stop is unresponsive. Scale the workload to zero independently of the game process and Operator, retaining normal shutdown grace. Worlds are not deleted, but unsaved changes may be lost. Fresh authentication is required.", - "emergency_stop_accepted": "Stop intent saved and workload scaled to zero. Wait for the Pod to exit; status will continue checking. Accepted does not mean stopped.", + "emergency_stop_warning": "When normal shutdown is unresponsive, set workload replicas directly to zero, bypassing the game process and Operator while retaining the normal termination grace period. World data is not deleted, but unsaved changes may be lost. Identity verification is required before execution.", + "emergency_stop_accepted": "Stop intent has been saved and workload replicas set to zero. The system will continue checking Pod termination. Request acceptance does not confirm process termination.", "emergency_stop_unknown": "The result is unconfirmed. Stop intent may already be saved. Refresh status; if the control API is unreachable, use the host recovery steps.", - "host_recovery": "Control API unreachable? Show host recovery steps", - "host_recovery_hint": "The panel cannot bypass an offline control API. Run these commands on the deployment host: save stop intent, scale to zero, then check game Pods.", - "host_recovery_verify": "A terminating Pod or unreachable node does not confirm that the process stopped. Check the node and container runtime; force-deleting a Pod is not proof of shutdown." + "host_recovery": "Host emergency shutdown procedure", + "host_recovery_hint": "When the control API is offline, the panel cannot perform cluster operations. Run these commands on the deployment host to save stop intent, set replicas to zero, and inspect game Pods. Replace minecraft if the deployment uses a different namespace.", + "host_recovery_verify": "If a Pod is terminating or its node is unreachable, process termination cannot be confirmed. Inspect the node and container runtime. Force-deleting a Pod is not evidence of process termination." } diff --git a/panel/src/i18n/resources/en-US/submissions.json b/panel/src/i18n/resources/en-US/submissions.json index f0cb0ce..a3282e4 100644 --- a/panel/src/i18n/resources/en-US/submissions.json +++ b/panel/src/i18n/resources/en-US/submissions.json @@ -6,7 +6,7 @@ "display_name_label": "Display Name", "display_name_placeholder": "e.g., Pixelmon Adventure Pack", "file_label": "Build Context (.tar.gz)", - "file_drag_hint": "Drag and drop a .tar.gz file here, or click to browse", + "file_drag_hint": "Drag a .tar.gz file into the upload area or select a file.", "submit_btn": "Submit", "submitting_create": "Creating submission...", "submitting_upload": "Uploading build context...", @@ -27,7 +27,7 @@ "build_status_failed": "Failed", "build_status_cancelled": "Cancelled", "no_submissions_title": "No Submissions Found", - "no_submissions_hint": "You haven't submitted any modpacks yet.", + "no_submissions_hint": "The current account has no modpack submission records.", "search_no_results": "No matches", "search_no_results_hint": "Try a different search term or filter.", "search_placeholder": "Search submissions...", @@ -38,17 +38,17 @@ "field_context_ref": "Context Reference", "field_image_ref": "Image Reference", "field_context_sha256": "Context SHA-256", - "field_context_sha256_hint": "The digest of the file the platform received; compare it with sha256sum of your local file. Approval binds exactly this content.", + "field_context_sha256_hint": "Digest of the content received by the server. Use sha256sum to compare it with the local file. Approval pins this content.", "clear_btn": "Clear", "file_hint": "Supports .tar.gz, up to {{limit}}", "withdraw_btn": "Withdraw", - "withdraw_hint": "Withdrawing deletes this submission and its uploaded context, freeing your pending slot and storage budget.", + "withdraw_hint": "Withdrawal deletes the submission and its uploaded build context, releasing the associated pending-review and storage quota.", "withdraw_confirm": "Withdraw", "withdraw_cancel": "Cancel", "upload_progress_label": "Upload progress", "upload_progress": "{{sent}} of {{total}}", "pause_btn": "Pause", "resume_btn": "Continue upload", - "resume_hint": "“{{name}}” is saved and {{sent}} of its build context is on the server. Submit again to continue from there.", - "restart_hint": "“{{name}}” is saved. The file you picked uploads from the start." + "resume_hint": "“{{name}}” is saved and the server has received {{sent}} of its build context. Resubmission resumes from the confirmed offset.", + "restart_hint": "“{{name}}” is saved. A newly selected file is uploaded from the beginning." } diff --git a/panel/src/i18n/resources/zh-CN/account.json b/panel/src/i18n/resources/zh-CN/account.json index 11b5956..121e82b 100644 --- a/panel/src/i18n/resources/zh-CN/account.json +++ b/panel/src/i18n/resources/zh-CN/account.json @@ -2,8 +2,8 @@ "title": "账户", "subtitle": "身份验证与 Minecraft 关联。", "sessions_title": "已登录的设备", - "sessions_desc": "所有登录了你账号的浏览器。发现不认识的设备,请立即让它退出。", - "sessions_staff_idle": "运维账号的会话连续 30 分钟没有操作会自动退出。", + "sessions_desc": "查看当前账户已登录的浏览器会话。发现未知设备时,请撤销对应会话。", + "sessions_staff_idle": "运维账户会话连续 30 分钟无操作后将自动退出。", "loading_sessions": "正在加载设备…", "sessions_empty": "没有设备以面板会话登录。", "session_this_device": "本设备", @@ -12,36 +12,36 @@ "session_active_now": "正在使用", "session_active": "{{when}}活跃", "session_signed_in": "{{when}}登录", - "session_sign_out_aria": "让 {{device}} 退出登录", + "session_sign_out_aria": "退出 {{device}} 的登录会话", "session_signed_out_device": "{{device}} 已退出登录。", "sessions_sign_out_others": "退出其它设备", - "sessions_sign_out_others_title": "让其它所有设备退出登录?", - "sessions_sign_out_others_desc": "除本设备外,其它设备都会退出登录,需要重新登录才能继续使用。", - "sessions_signed_out_others_other": "已让 {{count}} 台其它设备退出登录。", + "sessions_sign_out_others_title": "退出所有其他设备的登录会话?", + "sessions_sign_out_others_desc": "除当前设备外,所有设备的登录会话将失效,需要重新登录。", + "sessions_signed_out_others_other": "已退出 {{count}} 台其他设备的登录会话。", "sign_out": "退出登录", "signing_out": "退出中…", "minecraft_link": "Minecraft 关联", "checking_link": "正在检查关联状态…", "linked_title": "Minecraft 账户已关联。", - "linked_desc": "关联后可认领及管理服务器——所有权相关操作(认领、启动、停止)已解锁。", + "linked_desc": "Minecraft 账户已关联,可执行服务器认领、启动、停止及其他授权管理操作。", "uuid_label": "UUID", "step1_title": "在游戏中获取绑定码", - "step1_desc_prefix": "加入任意服务器,在聊天框输入 ", - "step1_desc_suffix": " 。服务器已确认你的身份,会提供一个一次性绑定码(约 10 分钟内有效)。", - "step2_title": "在此输入", + "step1_desc_prefix": "连接服务器,在聊天栏执行 ", + "step1_desc_suffix": "。身份验证通过后,服务器将提供有效期约为 10 分钟的一次性绑定码。", + "step2_title": "输入绑定码", "link_code": "绑定码", "link_code_placeholder": "ABCD2345", "verify_btn": "关联", "verifying": "验证中…", "email_verification": "邮箱验证", - "email_desc": "验证你的电子邮箱以确保账号安全。", + "email_desc": "验证绑定邮箱,用于账户身份验证。", "email_verified": "已验证", "send_code": "获取验证码", "sending_code": "发送中…", "email_step1": "输入电子邮箱", - "email_step1_desc": "输入你想要绑定的电子邮箱地址以获取验证码。", + "email_step1_desc": "输入待绑定的电子邮箱地址以获取验证码。", "email_step2": "输入验证码", - "email_step2_desc": "输入发送至你邮箱的 6 位数字验证码(10 分钟内有效)。", + "email_step2_desc": "输入邮件中的 6 位数字验证码,有效期为 10 分钟。", "email_verify_btn": "验证", "email_verifying": "验证中…", "email_otp_sent": "验证码已发送。", @@ -49,7 +49,7 @@ "change_email": "修改邮箱", "continue_btn": "继续", "passkeys": "Passkey 注册管理", - "passkeys_desc": "Passkey 允许你使用指纹、面容或设备 PIN 码安全登录面板。", + "passkeys_desc": "使用指纹、面容或设备 PIN,通过 Passkey 登录面板。", "no_passkeys": "未绑定任何 Passkey。", "loading_passkeys": "加载 Passkey 列表中…", "add_passkey": "注册新 Passkey", @@ -60,24 +60,24 @@ "last_used": "上次使用:", "never": "从未", "passkey_delete_aria": "删除 Passkey「{{name}}」", - "passkey_delete_title": "删除这个 Passkey?", - "passkey_delete_desc": "「{{name}}」(注册于 {{created}})删除后无法再用它登录,需要时可以重新注册。其它设备上的登录也会一并退出。", + "passkey_delete_title": "删除此 Passkey?", + "passkey_delete_desc": "删除「{{name}}」(注册于 {{created}})后,该密钥将无法用于登录。其他设备的登录会话将退出。需要时可重新注册。", "passkey_delete_confirm": "删除", - "passkey_last_hint": "这是你唯一的 Passkey,邮箱也还没验证,删掉就没法登录了。先验证邮箱或再注册一个 Passkey,才能删除它。", + "passkey_last_hint": "当前账户仅有一个 Passkey,且邮箱未验证,删除后将失去可用的登录方式。请先验证邮箱或注册其他 Passkey。", "migration": "账户迁移", - "migration_desc": "将被弃用账户名下的所有服务器转移到本账户。迁移在游戏内发起,在此完成。", + "migration_desc": "将退役账户名下的全部服务器转移至当前账户。迁移请求在游戏内发起,在面板中完成。", "account_id": "账户 ID", "migration_checking": "正在检查迁移状态…", "migration_none_prefix": "当前没有进行中的迁移。若要将本账户的服务器转移到其他账户,请用被弃用的账户在游戏内输入 ", "migration_none_suffix": " 。", - "migration_confirm_title": "确认身份", + "migration_confirm_title": "账户身份验证", "migration_confirm_desc": "迁移会停用本账户,因此需要先完成一次身份核验。", "migration_confirming": "确认中…", "migration_confirm_passkey_btn": "使用 Passkey 确认", "migration_confirm_otp_btn": "发送验证码到我的邮箱", "migration_issue_title": "指定目标账户", - "migration_issue_desc": "将目标账户本页面显示的「账户 ID」粘贴到此处,然后签发一次性转移码。", - "migration_issue_deadline": "请在 {{time}} 前签发。确认只在当前浏览器有效,过时需要重新确认。签发后,已验证的邮箱会收到一封通知。", + "migration_issue_desc": "填写目标账户页面显示的「账户 ID」,然后签发一次性转移码。", + "migration_issue_deadline": "须在 {{time}} 前签发。身份确认仅在当前浏览器有效,过期后须重新验证。签发后将向已验证的邮箱发送通知。", "migration_target_placeholder": "目标账户 ID", "migration_issuing": "签发中…", "migration_issue_btn": "签发转移码", @@ -85,13 +85,13 @@ "migration_code_desc": "请在过期前登录目标账户并在其页面兑换此码", "migration_code_pending": "转移码已签发。请在过期前用目标账户完成兑换", "migration_redeem_title": "兑换转移码", - "migration_redeem_desc": "收到了被弃用账户的转移码?在此兑换即可接管其服务器。", + "migration_redeem_desc": "兑换来源账户签发的转移码,将其服务器转移至当前账户。", "migration_redeem_placeholder": "转移码", "migration_redeeming": "兑换中…", "migration_redeem_btn": "兑换", "migration_redeemed": "迁移完成——已有 {{count}} 台服务器转移至本账户。", - "reauth_title": "确认是你本人", - "reauth_desc": "添加或删除 Passkey、修改邮箱前,需要用你已有的登录方式再验证一次,验证后 5 分钟内有效。", + "reauth_title": "账户身份验证", + "reauth_desc": "此操作需要重新验证身份。请使用已注册的 Passkey 或已验证邮箱完成验证,验证结果有效期为 5 分钟。", "reauth_checking": "正在查看可用的验证方式…", "reauth_passkey_btn": "用 Passkey 验证", "reauth_passkey_waiting": "等待 Passkey…", @@ -103,24 +103,24 @@ "reauth_resend": "重新发送验证码", "reauth_sign_in_desc": "管理员账户也可以退出后重新登录,重新登录后 5 分钟内视为已验证。", "reauth_sign_in_btn": "退出并重新登录", - "reauth_done_continue": "已确认。点“继续”添加 Passkey。", - "email_change_desc": "输入新邮箱,我们会向新地址发送验证码。验证通过后登录验证码改发到新邮箱,旧邮箱会收到通知,其它设备会退出登录。", - "staff_linked_desc": "此角色已关联到你的管理员账户,进服时将识别你的管理身份。", - "staff_link_desc": "关联游戏角色后,进服时可识别你的管理身份。你现在已能使用全部面板管理功能,可以稍后再关联。", + "reauth_done_continue": "身份验证已完成。请选择「继续」以添加 Passkey。", + "email_change_desc": "输入新邮箱以接收验证码。验证通过后,登录验证码将发送至新邮箱,旧邮箱将收到变更通知,其他设备的登录会话将退出。", + "staff_linked_desc": "此角色已关联至当前管理员账户,加入游戏时将识别对应的管理身份。", + "staff_link_desc": "关联 Minecraft 角色后,加入游戏时将识别对应的管理身份。面板管理功能不依赖此关联,可稍后配置。", "staff_sources_loading": "正在读取认证源…", "staff_source_mojang": "Minecraft 正版", "staff_source": "认证源", "staff_profile": "Minecraft 角色名或 UUID", "staff_lookup": "查询角色", "staff_working": "处理中…", - "staff_lookup_unsupported": "此认证源需要配置 api_url 才能查询角色。你仍可继续管理面板。", - "staff_confirm_desc": "确认将这个角色关联到你的管理员账户,进服时赋予对应的管理员身份。", + "staff_lookup_unsupported": "此认证源须配置 api_url 才能查询角色。面板管理功能不受影响。", + "staff_confirm_desc": "将此角色关联至当前管理员账户,加入游戏时将识别对应的管理身份。", "staff_confirm": "确认关联此角色", "staff_source_help": "“世界树”(Yggdrasil)是 Minecraft 的账号认证协议。正版与第三方认证站都通过它提供角色信息;请选择实际登录游戏所用的认证源,输入的是角色名或 UUID,不是邮箱。", "game_guide": "准备进入 Minecraft", - "game_version": "使用 Minecraft Java 版 {{version}} 连接登录服和大厅。目标服务器可能需要不同版本或模组,请查看对应服务器说明。", - "game_version_unknown": "登录服的客户端版本尚未配置,请由运维确认后填写 game_version;不要按面板版本猜测。", - "game_lobby": "连接后先经过登录服验证身份,随后进入大厅。大厅是选择目标服务器的中转站,可用 /menu 选择服务器并等待它启动;登录服或大厅未就绪时,可在服务列表查看状态。", + "game_version": "使用 Minecraft Java 版 {{version}} 连接登录服务和大厅。目标服务器可能需要其他版本或模组,请查看对应说明。", + "game_version_unknown": "登录服务的客户端版本未配置。请由运维确认并填写 game_version;面板版本不代表游戏版本。", + "game_lobby": "玩家经登录服务完成身份验证后进入大厅。大厅用于选择目标服务器,可执行 /menu 打开服务器菜单并等待启动。登录服务或大厅未就绪时,可查看服务列表中的运行状态。", "game_thirdparty": "使用第三方认证源时,启动器也须配置同一个认证站。角色查询只能读取身份,进服时仍需通过该站的登录验证。", - "staff_code_alternative": "认证站不支持查询?也可以使用游戏内链接码" + "staff_code_alternative": "角色查询不可用时,可使用游戏内绑定码" } diff --git a/panel/src/i18n/resources/zh-CN/admin.json b/panel/src/i18n/resources/zh-CN/admin.json index f0681a4..aee9c96 100644 --- a/panel/src/i18n/resources/zh-CN/admin.json +++ b/panel/src/i18n/resources/zh-CN/admin.json @@ -12,10 +12,10 @@ "image_source_built": "平台构建", "image_source_external": "外部", "image_source_recommended": "推荐", - "delete_image_title": "从白名单移除这个镜像?", + "delete_image_title": "从白名单移除此镜像?", "delete_image_desc": "移除后,新服务器将不能再选用", "delete_image_confirm": "移除镜像", - "cancel_build_title": "停止这次构建?", + "cancel_build_title": "停止此构建?", "cancel_build_desc": "构建会立即停止,不会推送任何镜像:", "cancel_build_keep": "继续构建", "cancel_build_confirm": "停止构建", @@ -32,10 +32,10 @@ "download_context_btn": "下载上下文", "download_context_hint": "下载上传的构建上下文 (.tar.gz)——其中包含将被实际执行的 Dockerfile。", "context_sha256_label": "上下文 SHA-256", - "context_sha256_hint": "通过会绑定这个摘要:提交者之后再上传,构建会拒绝执行。离线审阅时请用 sha256sum 核对下载的文件。", + "context_sha256_hint": "审核通过后将固定此摘要。后续上传与该摘要不一致时,构建将拒绝执行。离线审核时请使用 sha256sum 校验下载文件。", "context_sha256_missing": "尚未上传构建上下文,或上传早于摘要记录,需要提交者重新上传。", - "context_sha256_downloaded": "与你刚下载的文件一致,通过会绑定这份内容。", - "context_sha256_stale": "你下载的版本({{digest}}…)已被新的上传替换,请重新下载审阅。", + "context_sha256_downloaded": "摘要与已下载文件一致,审核通过后将固定此内容。", + "context_sha256_stale": "已下载版本({{digest}}…)已被新上传替换。请重新下载并审核。", "approve_needs_context": "没有可通过的上下文:提交者尚未上传或需要重新上传", "base_image_label": "基础镜像", "base_image_placeholder": "例如: library/postgres:15", @@ -44,11 +44,11 @@ "build_status_succeeded": "成功", "build_status_failed": "失败", "build_status_cancelled": "已取消", - "build_queued_hint": "同时运行的构建已达上限([registry] max_concurrent_builds),这个构建会在前面的构建结束后自动开始。", + "build_queued_hint": "并发构建数量已达上限([registry] max_concurrent_builds)。任务将在运行中的构建结束后自动开始。", "view_logs_btn": "扫描与日志", "cancel_build_btn": "取消", "no_builds_title": "暂无构建任务", - "no_builds_hint": "您可以使用右上角表单触发第一个镜像构建任务。", + "no_builds_hint": "可使用构建表单提交镜像构建任务。", "no_images_title": "无白名单镜像", "no_images_hint": "白名单为空——平台管理员需通过 CLI 添加镜像。", "enabled": "已启用", @@ -112,7 +112,7 @@ "updates_stat_end": "维护结束时间", "updates_stat_timezone": "本地时区", "dbbackup_title": "控制面数据库备份", - "dbbackup_subtitle": "用户、Passkey、服务器归属、配额、审计日志和世界存档索引都在这个数据库里。主机上的 felis-db-backup.timer 每天备份一次,每次升级迁移前也会先快照。", + "dbbackup_subtitle": "控制面数据库存储用户、Passkey、服务器归属、配额、审计日志及世界存档索引。felis-db-backup.timer 每日执行备份,升级迁移前另行创建快照。", "dbbackup_status_ok": "正常", "dbbackup_status_stale": "已过期", "dbbackup_status_never": "从未备份", @@ -129,18 +129,18 @@ "dbbackup_label_pre_restore": "恢复前快照", "dbbackup_label_offsite": "异地复制快照", "dbbackup_label_manual": "手动", - "dbbackup_never_title": "还没有记录到任何数据库备份", + "dbbackup_never_title": "暂无数据库备份记录", "dbbackup_stale_title": "最近一次备份已超过 {{hours}} 小时", "dbbackup_daily_stale_title": "每日定时备份最近一次完成是{{when}},已超过 {{hours}} 小时", - "dbbackup_daily_never_title": "每日定时任务还没有完成过一次备份", - "dbbackup_daily_fix_hint": "上面这份更新的备份来自手动或其他任务,可以用来恢复,但定时任务修好之前它会一天天变旧。在主机上立即运行一次每日备份,再查看日志找出它停下的原因:", + "dbbackup_daily_never_title": "每日定时任务尚未完成备份", + "dbbackup_daily_fix_hint": "较新的备份由手动操作或其他任务生成,可用于恢复,但不代表每日定时任务正常。请在主机执行每日备份,并检查定时任务日志:", "dbbackup_servers_title": "最近一次备份缺少服务器定义", - "dbbackup_servers_hint": "备份时集群没有响应。用它恢复能找回账号和数据库,但一台服务器都不会有。等集群恢复响应后,在主机上重新备份一次:", - "dbbackup_fix_hint": "此时主机出故障,账号、服务器归属和存档索引都无法恢复。在主机上立即运行一次每日备份,再查看它的日志找出定时任务错过的原因:", + "dbbackup_servers_hint": "备份期间集群未响应,归档缺少服务器定义。恢复数据库无法重建服务器。请在集群恢复后重新执行完整备份:", + "dbbackup_fix_hint": "缺少有效数据库备份时,主机故障可能导致账户、服务器归属及存档索引无法恢复。请在主机立即执行每日备份,并检查定时任务日志:", "dbbackup_copy": "复制命令", - "dbbackup_offsite_note": "备份只保存在这台主机上,硬盘损坏或主机丢失时会一起丢失。请定期把备份目录复制到另一台机器;恢复与灾备步骤见故障排查文档 §16。", + "dbbackup_offsite_note": "备份仅保存在当前主机。磁盘或主机故障可能导致备份与原始数据同时丢失,请定期复制备份至独立主机。恢复与灾备步骤见故障排查文档 §16。", "versions_title": "组件版本", - "versions_subtitle": "felis-update-check.timer 每天把本机运行的版本与上游最新发行版比对。Felis 不会自行应用任何更新。", + "versions_subtitle": "felis-update-check.timer 每日比对本机组件版本与上游发行版本。Felis 不自动应用更新。", "versions_status_available_other": "{{count}} 个可用更新", "versions_status_current": "已是最新", "versions_status_stale": "检查已逾期", @@ -156,19 +156,19 @@ "versions_state_available": "有可用更新", "versions_state_current": "已是最新", "versions_state_unknown": "无法访问发行源", - "versions_state_unreadable": "读不到版本", + "versions_state_unreadable": "版本读取失败", "versions_state_pinned": "已固定", - "versions_apply_hint": "在宿主机执行下面的只读检查,查看更新目标、范围和确切的 `--apply` 命令。确认后,在维护窗口内执行该命令:", - "versions_never_title": "还没有记录过版本检查", + "versions_apply_hint": "在宿主机执行以下只读检查,确认更新目标、范围及对应的 `--apply` 命令,然后在维护窗口内执行:", + "versions_never_title": "暂无版本检查记录", "versions_stale_title": "最近一次版本检查已超过 {{hours}} 小时", - "versions_fix_hint": "下面的版本可能已过时。请先在主机上立即检查一次,再查看定时器日志找出它没有运行的原因:", + "versions_fix_hint": "当前显示的版本信息可能已过期。请在主机重新执行检查,并查看定时器日志以确认未执行原因:", "build_import_submission_label": "从已有的审核提交导入参数", "build_import_submission_placeholder": "选择一个用户提交...", - "build_import_submission_none": "还没有可导入的提交", + "build_import_submission_none": "暂无可导入的提交", "build_import_submission_no_match": "没有匹配“{{query}}”的提交", - "build_import_submission_unavailable": "提交列表未读到", + "build_import_submission_unavailable": "提交列表不可用", "build_import_submission_failed": "提交列表读取失败:{{reason}}", - "build_import_submission_more": "列出最新的 {{shown}} 条,共 {{total}} 条;更早的提交用上面的搜索找。", + "build_import_submission_more": "当前显示最新 {{shown}} 条,共 {{total}} 条。可通过搜索查询更早的提交。", "build_import_search_placeholder": "按名称、ID 或提交人搜索提交", "build_import_submission_hint": "选择提交将自动填充镜像引用、构建上下文引用和对应的 Dockerfile 审计头。", "build_import_submission_warning_title": "警告:该提交状态为「{{status}}」", @@ -200,7 +200,7 @@ "users_col_created": "创建时间", "users_view_detail": "详情", "user_not_found_title": "用户不存在", - "user_not_found_body": "这个账户可能已被删除,请回到用户列表重新查找。", + "user_not_found_body": "账户不存在或已被删除。请返回用户列表重新查询。", "users_empty_title": "未找到用户", "users_empty_hint": "没有匹配当前筛选条件的用户。", "users_back_to_list": "返回用户列表", @@ -208,8 +208,8 @@ "users_field_username": "用户名", "users_field_email": "邮箱", "users_field_role": "角色", - "users_role_locked_self": "不能修改自己的角色。", - "users_role_locked_owner": "所有者的角色是固定的,只能在主机的应急控制台(sudo felis breakGlass)上管理。", + "users_role_locked_self": "不允许修改当前账户的角色。", + "users_role_locked_owner": "Owner 角色不可修改。请使用主机应急控制台(sudo felis breakGlass)管理。", "users_save_btn": "保存更改", "users_save_ok": "更改保存成功。", "users_linked_accounts": "已关联的 Minecraft 账号", @@ -229,7 +229,7 @@ "users_quota_memory": "最大内存(MiB)", "users_quota_storage": "最大存储(GiB)", "users_quota_unlimited": "无限制", - "users_quota_hint": "留空为不限。填 0 表示这一项不给额度,该用户需要它的认领都会被拒绝。调低到已用量以下只挡新的认领,已有的服务器不受影响。", + "users_quota_hint": "留空表示不限制,0 表示不分配该项资源。认领所需资源超出配额时将被拒绝。降低配额仅限制后续认领,不影响已有服务器。", "users_sessions": "活跃会话", "users_no_sessions": "无活跃会话。", "users_session_expires": "过期时间", @@ -242,8 +242,8 @@ "users_session_revoke_all_dlg_desc": "确定撤销所有活跃会话吗?用户将从所有设备登出。", "users_session_revoke_confirm": "撤销", "users_danger_zone": "危险操作区", - "users_protected_self": "不能禁用或删除当前登录的账号。", - "users_protected_owner": "所有者账号不能在面板里禁用或删除,只能在主机的应急控制台(sudo felis breakGlass)上管理。", + "users_protected_self": "不允许禁用或删除当前登录账户。", + "users_protected_owner": "面板不允许禁用或删除 Owner 账户。请使用主机应急控制台(sudo felis breakGlass)管理。", "users_danger_disable": "禁用用户", "users_danger_disable_desc": "阻止此用户登录。所有活跃会话将被立即撤销。", "users_danger_disable_btn": "禁用用户", @@ -259,12 +259,12 @@ "users_danger_delete": "删除用户", "users_danger_delete_desc": "软删除此用户。其拥有的服务器将被释放,所有会话将被撤销,账号将被永久禁用。此操作无法通过面板撤销。", "users_danger_delete_btn": "删除用户", - "users_danger_delete_yes": "是的,永久删除", + "users_danger_delete_yes": "确认永久删除", "users_danger_passkeys": "解绑通行密钥", - "users_danger_passkeys_desc": "解除该账号持有的全部通行密钥——密钥丢失或被盗用时的补救手段。其他登录方式(邮箱验证码、游戏内审批)不受影响。", + "users_danger_passkeys_desc": "移除此账户的全部通行密钥,用于处理密钥丢失或泄露。邮箱验证码及游戏内审批等其他登录方式不受影响。", "users_danger_passkeys_btn": "解绑通行密钥", "users_danger_passkeys_dlg_title": "解绑通行密钥", - "users_danger_passkeys_dlg_desc": "该账号的全部通行密钥将被永久移除。这不会锁死账号:用户可用其他方式登录后,在账户页重新注册密钥。", + "users_danger_passkeys_dlg_desc": "此账户的全部通行密钥将被永久移除。其他登录方式仍可使用;登录后可在账户页面重新注册密钥。", "users_danger_passkeys_yes": "确认解绑", "add_image_desc": "将外部 Docker 镜像引用录入白名单,供后续创建服务器使用。", "images_search_placeholder": "搜索镜像名称或来源...", @@ -279,14 +279,14 @@ "scan_blocked": "已拦截", "scan_passed": "已通过", "scan_loading": "正在读取安全扫描…", - "scan_none": "这次构建没有留存安全扫描:它在扫描步骤前就结束了,或者早于开始留存扫描的版本。", + "scan_none": "此构建未留存安全扫描记录,可能在扫描前结束,或创建于扫描留存功能启用前。", "scan_load_failed": "读取安全扫描失败:{{reason}}", "scan_meta_other": "扫描于 {{when}} · {{count}} 个软件包", "scan_policy": "拦截级别 {{severities}};尚无修复版本的漏洞只列出", "scan_policy_unfixed": "拦截级别 {{severities}},尚无修复版本的漏洞同样拦截", "scan_counts_label": "按严重度统计的发现", "scan_blocking_count_other": "其中 {{count}} 项拦截镜像", - "scan_clean": "Trivy 在这个镜像里没有发现任何问题。", + "scan_clean": "Trivy 未在此镜像中检测到问题。", "scan_showing": "显示 {{total}} 项中的 {{shown}} 项,完整报告列出全部发现。", "scan_col_id": "编号", "scan_col_severity": "严重度", @@ -301,7 +301,7 @@ "scan_secret": "密钥:{{title}}", "scan_download_report": "Trivy 报告", "scan_download_sbom": "SBOM", - "scan_not_kept": "这次构建未留存该文件:文件过大,或生成它的步骤失败。", + "scan_not_kept": "构建未留存此文件,可能由于文件过大或生成步骤失败。", "scan_download_failed": "下载失败:{{reason}}", "platform_title": "平台设置", "platform_subtitle": "统一管理服务器启动与唤醒的全局策略。", diff --git a/panel/src/i18n/resources/zh-CN/auth.json b/panel/src/i18n/resources/zh-CN/auth.json index a331066..ee78f29 100644 --- a/panel/src/i18n/resources/zh-CN/auth.json +++ b/panel/src/i18n/resources/zh-CN/auth.json @@ -13,56 +13,56 @@ "otp_placeholder": "请输入 6 位验证码", "send_otp": "发送验证码", "sending_otp": "发送中…", - "otp_sent": "验证码已发送至您的邮箱,请查收。收到多封时,最近几分钟内的任一验证码都可用。", + "otp_sent": "验证码已发送至绑定邮箱。同一有效期内收到的验证码均可使用。", "otp_btn": "验证并登录", "resend_in": "秒后重试", "passkey_btn": "使用 Passkey 登录", - "passkey_email_hint": "使用 Passkey 请在上方输入绑定邮箱,或留空直接免密登录。", + "passkey_email_hint": "使用 Passkey 登录时,可填写绑定邮箱,或留空进行免密登录。", "bind_code": "绑定码", "bind_code_placeholder": "例如:ABCD2345", - "bind_hint": "用 Minecraft Java 版加入下面的地址。第一次进入时登录服会给出一次性绑定码,之后在游戏内输入 /link 获取新的。", - "bind_hint_no_address": "用 Minecraft Java 版加入本服务器。第一次进入时登录服会给出一次性绑定码,之后在游戏内输入 /link 获取新的。", - "no_owner_title": "还没有 Owner,暂时无法登录", - "no_owner_subtitle": "Owner 是这台服务器的所有者账号", - "no_owner_intro": "这台服务器尚未创建 Owner 登录。主机管理员完成以下步骤后即可使用面板,无需启动 Minecraft。", + "bind_hint": "使用 Minecraft Java 版连接下方地址。首次加入时,登录服务将提供一次性绑定码;后续可在游戏内执行 /link 获取新的绑定码。", + "bind_hint_no_address": "使用 Minecraft Java 版连接本服务器。首次加入时,登录服务将提供一次性绑定码;后续可在游戏内执行 /link 获取新的绑定码。", + "no_owner_title": "Owner 账户未初始化,登录不可用", + "no_owner_subtitle": "Owner 为平台所有者账户", + "no_owner_intro": "平台尚未初始化 Owner 登录方式。主机管理员须完成以下设置,无需启动 Minecraft。", "no_owner_step_setup": "在服务器终端运行下面的命令,完成部署与面板访问设置:", - "no_owner_step_link": "用浏览器打开终端给出的一次性设置链接,登记邮箱并创建通行密钥。之后可在账户页关联 Minecraft 角色。", + "no_owner_step_link": "在浏览器中打开终端提供的一次性设置链接,登记邮箱并创建通行密钥。Minecraft 角色可在账户页面关联。", "no_owner_recheck": "已完成设置,重新检查", "no_owner_rechecking": "检查中…", - "no_owner_still_unbound": "还没有检测到 Owner。请确认终端已经显示设置链接。", + "no_owner_still_unbound": "未检测到已初始化的 Owner 账户。请确认终端已生成设置链接并完成账户初始化。", "bind_btn": "验证并登录", "binding": "验证中…", "op_hint": "仅限管理员:验证码将发送至您的邮箱,且需要一位在线管理员在游戏内批准此次登录。", "op_start_btn": "发起管理员登录", - "op_approve_hint": "验证码已发送至您的邮箱(收到多封时,最近的任一封都可用)。请让一位在线管理员在游戏内批准此次请求:", + "op_approve_hint": "验证码已发送至绑定邮箱,有效期内收到的验证码均可使用。此次请求须由在线管理员在游戏内批准:", "op_waiting": "等待游戏内批准…", - "op_approved": "已批准——请输入邮件中的验证码。", + "op_approved": "请求已批准。请输入邮件中的验证码。", "op_restart": "重新开始", "op_expires_in": "此请求将在 {{time}} 后过期。", "op_expired": "此请求在使用前已过期,请重新发起登录。", "op_request_again": "重新发起", - "op_poll_retrying": "暂时连不上 Felis,仍在等待,查询会放慢…", + "op_poll_retrying": "无法连接 Felis。系统将降低查询频率并继续等待响应。", "saving": "保存中…", - "setup_title": "初始化你的账户", + "setup_title": "账户初始化", "setup_welcome": "欢迎,{{name}}", "setup_preparing": "正在准备初始化…", "setup_entering": "正在进入控制台…", - "setup_invalid_title": "无法开始设置", - "setup_invalid_subtitle": "这个设置链接无效或已被使用", + "setup_invalid_title": "无法启动初始化", + "setup_invalid_subtitle": "设置链接无效或已被使用", "setup_invalid_hint_prefix": "请在服务器上重新运行 ", "setup_invalid_hint_suffix": " 获取新的设置链接,或直接前往登录页。", - "setup_failed_title": "设置没能开始", - "setup_failed_subtitle": "服务器暂时没能让你登录。设置链接仍然有效,可以再试一次。", + "setup_failed_title": "初始化启动失败", + "setup_failed_subtitle": "服务器未能完成登录。设置链接仍有效,请重试。", "setup_retry": "重试", "setup_goto_login": "前往登录", "setup_email_step": "第一步 · 填写邮箱", - "setup_email_desc": "我们会为你的账户保存这个邮箱地址。发信服务(SMTP)和邮箱验证可稍后在设置中配置——完成初始化并不需要它。", + "setup_email_desc": "邮箱地址将保存至当前账户。邮件服务(SMTP)及邮箱验证可稍后配置,不影响账户初始化。", "setup_email_save": "保存并继续", "setup_passkey_step": "第二步 · 注册通行密钥", - "setup_passkey_desc": "使用指纹、面容或设备 PIN 创建一个通行密钥,这将是你登录控制台的方式——完成初始化必须注册。", + "setup_passkey_desc": "使用指纹、面容或设备 PIN 创建通行密钥,用于登录控制台。完成初始化前必须注册通行密钥。", "setup_create_passkey": "创建通行密钥", "setup_registering": "注册中…", "setup_default_passkey_name": "默认通行密钥", - "session_ended_notice": "你的登录已过期或已被撤销,请重新登录。登录后会回到刚才的页面。", - "setup_game_optional": "完成登录设置即可进入管理面板。Minecraft 角色可以稍后在账户页关联。" + "session_ended_notice": "登录会话已过期或被撤销。请重新登录,完成后将返回原访问页面。", + "setup_game_optional": "完成登录设置后可进入管理面板。Minecraft 角色可稍后在账户页面关联。" } diff --git a/panel/src/i18n/resources/zh-CN/backups.json b/panel/src/i18n/resources/zh-CN/backups.json index 0fd1d6c..3483a46 100644 --- a/panel/src/i18n/resources/zh-CN/backups.json +++ b/panel/src/i18n/resources/zh-CN/backups.json @@ -1,6 +1,6 @@ { "title": "备份与恢复", - "subtitle": "玩过的世界会在服务器停止后每天自动备份一次,长期闲置被自动回收前也会自动备份。可以使用备份随时恢复世界,恢复前需要先停止服务器。", + "subtitle": "已运行且停止的服务器每日自动备份,长期闲置回收前也会备份。恢复世界前须停止服务器。", "back_to_console": "返回控制台", "not_yours_title": "无权访问备份", "not_yours_body": "只有所有者或管理员才能查看、恢复和删除该服务器的备份。", @@ -14,17 +14,17 @@ "reason_label": "原因:{{reason}}", "backup_now": "立即备份", "backup_in_progress": "正在启动备份……", - "backup_started": "已开始备份——进度可在下方“最近操作”中查看。", - "backup_world_busy": "已有备份、恢复或世界导出在进行,等它结束再备份。", + "backup_started": "备份任务已启动,可在「最近操作」中查看进度。", + "backup_world_busy": "备份、恢复或世界导出任务进行中。请等待当前操作结束后再备份。", "backup_requires_stopped": "备份前需要先停止服务器。", "export_world": "导出世界", - "export_world_hint": "把服务器当前的世界打包成 .tar.gz 下载。", + "export_world_hint": "将当前世界打包为 .tar.gz 文件并下载。", "export_preparing": "准备中……", "export_requires_stopped": "导出世界前需要先停止服务器。", - "export_world_busy": "这个世界正在备份、恢复或导出,等它结束再导出。", - "export_another": "另一个下载正在准备,等它开始后再试。", - "export_progress_world": "正在打包世界——准备好后会自动开始下载。请保持此页面打开;下载结束前服务器无法启动。", - "export_progress_backup": "正在准备备份——稍后会自动开始下载。请保持此页面打开。", + "export_world_busy": "世界正在执行备份、恢复或导出。请等待当前操作结束后再导出。", + "export_another": "其他下载正在准备。请等待准备完成后重试。", + "export_progress_world": "正在归档世界,完成后将自动开始下载。请保持页面打开,下载结束前服务器无法启动。", + "export_progress_backup": "正在准备备份下载,完成后将自动开始传输。请保持页面打开。", "export_started_world": "已开始下载:浏览器正在保存 {{filename}}。下载结束后服务器即可再次启动。", "export_started_backup": "已开始下载:浏览器正在保存 {{filename}}。", "export_failed": "导出失败,没有下载任何内容。", @@ -34,7 +34,7 @@ "expired": "已过期", "former_owner": "原世界所有者:{{owner}}", "empty_title": "暂无备份", - "empty_hint": "服务器运行过并停止后会自动生成定时备份,也可以点“立即备份”马上创建。", + "empty_hint": "已运行且停止的服务器将自动生成定时备份,也可执行「立即备份」手动创建。", "jobs_title": "最近操作", "jobs_empty": "暂无备份、恢复或导出操作记录。", "job_backup": "备份", @@ -60,37 +60,37 @@ "restore_btn": "恢复此备份", "restore_btn_short": "恢复", "restore_confirm": "恢复备份将停止服务器(所有在线玩家将被断开),并用该备份完全覆盖当前世界的全部数据。未勾选安全快照时此操作不可逆,请确认是否继续?", - "restore_confirm_safe": "恢复备份将停止服务器(所有在线玩家将被断开),先为当前世界做一份安全快照,快照成功后再用该备份覆盖世界。选错了备份,可以用这份“恢复前快照”恢复回来。确认继续?", + "restore_confirm_safe": "恢复将停止服务器并断开在线玩家,先创建当前世界的安全快照,快照成功后再用所选备份覆盖世界。需要撤销恢复结果时,可恢复「恢复前快照」。确认继续?", "safety_snapshot_label": "先为当前世界做安全快照(推荐)", "safety_snapshot_hint": "快照成功后自动开始恢复;快照失败则不恢复,世界保持原样。", - "safety_snapshot_off_hint": "不做快照,立即覆盖当前世界,之后无法找回。", + "safety_snapshot_off_hint": "不创建安全快照,直接覆盖当前世界。未备份的现有数据将无法恢复。", "restore_confirm_yes": "确认恢复", "restore_confirm_yes_unsafe": "直接覆盖并恢复", "cancel": "取消", "restore_started_short": "已启动恢复", "restore_snapshot_started_short": "快照中,随后恢复", - "stopping": "正在停止服务器(有玩家在线时先在游戏里预告 30 秒)……", + "stopping": "正在停止服务器;有玩家在线时将提前 30 秒发送游戏内通知…", "restoring": "正在恢复备份……", "stop_timeout": "服务器停止超时。请关闭此窗口,稍后重试。", "corrupt_badge": "已损坏", - "corrupt_hint": "回读校验发现这份归档与写入时不一致,已无法完整恢复。它会保留到过期,方便排查,不需要时也可以直接删除。", + "corrupt_hint": "归档回读校验失败,无法完整恢复。归档将保留至过期,以便排查;不再需要时可手动删除。", "corrupt_short": "已损坏", "verified_at": "{{when}}已校验", "skipped_entries": "{{count}} 个条目未归档", - "skipped_entries_hint": "世界目录里有符号链接等非普通文件,归档时跳过了它们,恢复后这些条目不会存在。", + "skipped_entries_hint": "归档已跳过符号链接及其他非普通文件条目,恢复时不包含这些条目。", "col_created": "创建时间", "col_size": "大小", "col_expires": "过期时间", "col_owner": "原所有者", "col_actions": "操作", "delete_btn": "删除备份", - "delete_title": "删除这份备份?", - "delete_confirm": "{{relative}}({{absolute}})的这份备份({{size}})会立刻从列表中移除,之后不能再用它恢复,也无法撤销。", + "delete_title": "删除此备份?", + "delete_confirm": "备份 {{relative}}({{absolute}}),大小 {{size}},将立即从列表移除并停止提供恢复。此操作不可撤销。", "delete_cleanup_note": "归档文件在下一次每日清理时删除,异地副本在下一次同步时删除。", - "delete_archive_warning": "这份备份是世界被回收或放弃时存下的归档。服务器上的世界如果还没从它恢复回来,删掉它就再也找不回这个世界了。", - "delete_only_warning": "这是这台服务器目前唯一的一份备份。", + "delete_archive_warning": "此备份为世界回收或服务器放弃时生成的归档。删除后,缺少其他可用副本的世界数据将无法恢复。", + "delete_only_warning": "此备份为服务器当前唯一的备份。", "delete_confirm_yes": "删除备份", - "delete_restore_busy": "有一次恢复可能正在读取这份备份,等恢复完成后再删除。", + "delete_restore_busy": "恢复任务可能正在读取此备份。请等待恢复完成后再删除。", "deleted": "备份已删除。", - "delete_gone": "这份备份已经不在了,列表已刷新。" + "delete_gone": "备份已不存在,列表已刷新。" } diff --git a/panel/src/i18n/resources/zh-CN/common.json b/panel/src/i18n/resources/zh-CN/common.json index 43eb832..d49d7b2 100644 --- a/panel/src/i18n/resources/zh-CN/common.json +++ b/panel/src/i18n/resources/zh-CN/common.json @@ -7,13 +7,13 @@ "creating": "创建中…", "close_sr": "关闭", "not_authorized_title": "无权访问", - "not_authorized_body": "此区域仅供平台管理员使用。若通过直接输入 URL 到达此页面,说明你的账号无管理员权限,API 侧同样会拒绝该请求。", + "not_authorized_body": "当前账户无平台管理权限,无法访问此页面。", "back_to_dashboard": "返回首页", "not_wired_yet": "功能尚未接入", "pending_backend_prefix": "待接入接口:", "checking_access": "正在验证权限…", - "access_unknown_title": "暂时无法确认你的权限", - "access_unknown_body": "账户信息没有读到,读到之前这一页先不显示。", + "access_unknown_title": "无法确认账户权限", + "access_unknown_body": "账户信息读取失败。确认访问权限前,页面内容不可用。", "loading_config": "正在加载配置…", "brand_name": "Felis", "brand_tagline": "Kubernetes 原生的 Minecraft 管理平台", @@ -23,22 +23,22 @@ "pagination_prev": "上一页", "pagination_next": "下一页", "pagination_of": "第 {{current}} / {{total}} 页", - "crash_title": "这个页面出错了", - "crash_body": "面板渲染这个页面时遇到了问题。重新加载通常就能恢复;如果反复出现,请把下面的错误信息发给管理员。", + "crash_title": "页面加载异常", + "crash_body": "页面渲染失败。请重新加载;若问题持续,请向管理员提供错误信息。", "crash_updated_title": "面板已更新", - "crash_updated_body": "面板刚发布了新版本,这个页面需要重新加载才能打开。", + "crash_updated_body": "面板版本已更新,请重新加载页面。", "crash_reload": "重新加载", "crash_details": "错误信息", "crash_copy": "复制错误信息", - "not_found_title": "找不到这个页面", - "not_found_body": "链接里的名称不合法,或者这个页面不存在。", - "connection_lost": "连不上 Felis:网络断开了,或者 Cloudflare Access 的登录已过期。网络恢复后会自动消失;Access 过期需要刷新页面重新登录。", + "not_found_title": "页面不存在", + "not_found_body": "链接中的名称无效,或请求的页面不存在。", + "connection_lost": "无法连接 Felis,可能由于网络中断或 Cloudflare Access 会话过期。网络恢复后将自动重新连接;Access 会话过期时,请刷新页面并重新登录。", "reload_page": "刷新页面", - "config_unavailable": "面板配置(/config.json)没有加载成功,这里显示的服务器地址和链接可能不对。刷新页面可重试。", + "config_unavailable": "面板配置(/config.json)加载失败,服务器地址和链接可能不准确。请刷新页面重试。", "build_dev": "开发版", "open_menu": "打开菜单", "copy_address": "复制地址 {{address}}", "address_copied": "已复制", "address_copy_failed": "已选中,请用系统菜单复制", - "refresh_failed": "刷新失败,下面是上次读到的状态。" + "refresh_failed": "刷新失败,当前显示上次成功读取的状态。" } diff --git a/panel/src/i18n/resources/zh-CN/dashboard.json b/panel/src/i18n/resources/zh-CN/dashboard.json index 721bb33..fcff6a6 100644 --- a/panel/src/i18n/resources/zh-CN/dashboard.json +++ b/panel/src/i18n/resources/zh-CN/dashboard.json @@ -1,7 +1,7 @@ { "title": "仪表盘", "subtitle": "服务器运行状态一览。", - "no_servers_hint": "创建一个新服务器或认领一台现有服务器即可开始。", + "no_servers_hint": "可创建服务器、认领已有服务器,或联系管理员分配。", "stat_servers": "服务器", "stat_running": "运行中", "stat_players_online": "在线玩家", @@ -12,9 +12,9 @@ "active_load": "玩家在线负载", "status_distribution": "节点状态分布", "account_linked_title": "已关联游戏身份", - "account_linked_desc": "您的 Web 身份已成功绑定至 Minecraft 角色。拥有完整的所有权认领及启动权限。", + "account_linked_desc": "Web 账户已关联至 Minecraft 角色,可执行已授权的认领与启动操作。", "account_unlinked_title": "未关联游戏角色", - "account_unlinked_desc": "您需要关联 Minecraft 角色才能认领或操作服务器。请立即前往个人中心绑定。", + "account_unlinked_desc": "认领及操作服务器前须关联 Minecraft 角色。请在账户页面完成关联。", "go_link": "前往绑定", "link_status_checking": "正在确认是否已关联游戏角色…", "link_status_failed": "暂时无法获取绑定状态", @@ -30,13 +30,13 @@ "spec_failed_ratio": "节点故障率", "no_servers_monitor": "当前集群暂无服务器实例", "images_count": "个支持的白名单镜像环境", - "images_security_note": "。所有后端镜像运行前均通过 Trivy CVE 漏洞扫描,强制在隔离的安全容器中托管。", + "images_security_note": "。镜像可用范围由白名单控制,构建扫描结果可在构建记录中查看。", "manage_game_character": "管理游戏角色", "instances_count": "{{count}} 实例", "quick_start_guide": "快速入门指引:", - "quick_start_step1": "认领无主服后,系统会自动将您的 Minecraft UUID 添加至准入白名单。", - "quick_start_step2": "服务器如果长时间处于无人在线空闲状态,平台会自动执行休眠存档以节约资源。", - "quick_start_step3": "游戏入口统一由 Velocity 网关承载,只需连接您的专用子域名即可。", + "quick_start_step1": "认领服务器后,系统将对应 Minecraft UUID 加入访问白名单。", + "quick_start_step2": "服务器达到配置的空闲时长后将自动停止并保存世界,以释放运行资源。", + "quick_start_step3": "游戏入口由 Velocity 网关提供,通过服务器专用子域名连接。", "legend_title": "3D 节点监控图例", "legend_running": "运行中", "legend_starting": "启动中", @@ -45,10 +45,10 @@ "legend_failed": "异常失败", "legend_unknown": "未知状态", "welcome_title": "欢迎使用 Felis 编排平台", - "welcome_desc": "您已成功登录用户控制台。您可以在「我的服务器」页面查看并认领分配给您的专属节点,或者对其进行休眠、启动和控制台日志监控。", + "welcome_desc": "在「我的服务器」中查看、认领和管理已授权的服务器,并执行启动、停止及日志查看操作。", "go_to_servers_btn": "操作我的服务器", "fleet_flat_webgl": "浏览器未启用 WebGL,已切换为平面视图。", "fleet_flat_error": "3D 视图加载失败,已切换为平面视图。", - "fleet_flat_empty": "还没有服务器", + "fleet_flat_empty": "暂无服务器", "staff_unlinked_desc": "Minecraft 角色尚未关联。面板管理功能已可使用;准备进入游戏时,可在账户页选择认证源并关联角色。" } diff --git a/panel/src/i18n/resources/zh-CN/errors.json b/panel/src/i18n/resources/zh-CN/errors.json index b755bd3..e3952ea 100644 --- a/panel/src/i18n/resources/zh-CN/errors.json +++ b/panel/src/i18n/resources/zh-CN/errors.json @@ -2,107 +2,107 @@ "local_auth_disabled": "当前部署已禁用本地登录,请通过组织的安全入口访问控制台。", "staff_account": "该账户为管理员账户——请使用管理员登录。", "not_linked": "请先关联 Minecraft 账户(账户页 → 关联)。", - "invalid_code": "代码无效或已过期——请重新获取后再试。", + "invalid_code": "绑定码无效或已过期,请重新获取。", "already_linked": "该 Minecraft 账户已关联至其他用户。", - "bad_mc_uuid": "这不是 Minecraft UUID。它的样子是 069a79f4-44e9-4726-a5be-fca90e38aaf5,短横线可带可不带。", + "bad_mc_uuid": "Minecraft UUID 格式无效。示例:069a79f4-44e9-4726-a5be-fca90e38aaf5,可省略连字符。", "quota_exceeded": "服务器数量已达配额上限。", - "already_claimed": "该服务器已被他人抢先认领。", + "already_claimed": "该服务器已被其他账户认领。", "image_not_whitelisted": "该镜像未在白名单中。", - "image_not_in_registry": "内部镜像仓库里没有这个镜像标签,可能从未推送过,或已被删除。请重新构建或推送该镜像后再试。", - "registry_unavailable": "暂时连不上内部镜像仓库,无法确定该镜像对应的构建,请稍后再试。", - "image_change_unconfirmed": "更换镜像会让世界用新构建的 Minecraft 版本打开,区块升级后无法再用旧版本打开。请先备份世界,再勾选确认后保存。", + "image_not_in_registry": "内部镜像仓库不存在此镜像标签,可能尚未推送或已被删除。请重新构建或推送镜像后重试。", + "registry_unavailable": "无法连接内部镜像仓库,镜像构建版本未确认。请稍后重试。", + "image_change_unconfirmed": "更换镜像后,世界将在下次启动时使用新构建的 Minecraft 版本。升级后的区块不支持旧版本。请先备份世界并勾选确认,再保存配置。", "subdomain_taken": "该子域名已被占用。", "already_exists": "同名服务器已存在。", - "world_volume_exists": "同名的旧服务器留下了世界卷。请换一个名称,或请运维删除旧的世界卷。", - "server_retiring": "这台服务器已被放弃或正在删除,下一次每日回收时处理。撤销之前它不能启动或被认领。", + "world_volume_exists": "同名服务器的世界卷仍存在。请使用其他名称,或由运维确认并处理旧世界卷。", + "server_retiring": "服务器已进入放弃或删除流程,将在下一次每日回收时处理。撤销请求前无法启动或认领。", "confirm_mismatch": "输入的名称和服务器名称不一致。", - "system_server": "系统服务器由平台管理,不能放弃或删除。", - "world_volume_orphaned": "这台服务器的 MinecraftServer 已被手动删除,但世界卷还在。请运维先归档并删除这个世界卷,再删除服务器。", + "system_server": "系统服务器由平台管理,不允许放弃或删除。", + "world_volume_orphaned": "服务器的 MinecraftServer 资源已被手动删除,但世界卷仍存在。请由运维归档并删除残留世界卷,再删除服务器记录。", "cooldown": "启动冷却中——请稍后再试。", - "not_running": "服务器未在运行——请先启动它再管理访问权限。", + "not_running": "服务器未运行。请先启动服务器,再管理访问权限。", "console_unavailable": "暂时无法连接服务器控制台,请稍后重试。", - "luckperms_missing": "这台服务器没有装 LuckPerms,权限和用户组的改动不会生效。大厅请让运维重跑安装脚本来补上;其他服务器需要先装 LuckPerms 插件。", - "no_backup": "这台服务器暂时没有可回档的备份。", - "backup_corrupt": "这份备份回读校验未通过,已无法完整恢复——请选择另一份备份。", - "not_stopped": "请先把服务器完全停止——这项操作要改动世界存储卷,运行中的服务器独占着它。", - "maintenance_in_progress": "这台服务器的世界正在回档、备份、导出、下载文件、改动文件或闲置回收,等它完成后再试。回档、备份和改动文件通常一两分钟,导出和下载要等浏览器下载完,闲置回收视世界大小可能更久。", - "no_world_volume": "这台服务器还没有世界卷——先启动一次让它创建,然后再试。", - "file_changed": "这个文件在你打开之后被改过了(另一位管理者保存过,或者服务器上次运行时改写了它)。为了不覆盖那次修改,这次保存没有写入。", - "volume_full": "服务器的存储卷已满,这次改动没有写入,原有文件保持不变。删掉用不着的文件,或者请管理员给它扩容。", - "backup_cooldown": "这台服务器刚备份过,手动备份之间有冷却时间——请过几分钟再试。", + "luckperms_missing": "服务器未安装 LuckPerms,无法应用权限和用户组更改。大厅需由运维重新执行安装脚本;其他服务器须先安装 LuckPerms 插件。", + "no_backup": "服务器暂无可恢复的备份。", + "backup_corrupt": "备份回读校验失败,无法完整恢复。请选择其他备份。", + "not_stopped": "此操作需要修改世界卷,运行中的服务器独占挂载该卷。请先完全停止服务器。", + "maintenance_in_progress": "世界正在执行恢复、备份、导出、文件下载、文件修改或回收操作。请等待操作结束后重试。导出和下载需等待浏览器完成传输。", + "no_world_volume": "服务器尚未创建世界卷。请先启动服务器以创建世界卷,再重试。", + "file_changed": "文件在打开后已被其他管理者或服务器进程修改。本次保存未写入,以避免覆盖现有更改。", + "volume_full": "服务器存储卷已满,本次更改未写入,原有文件保持不变。请清理文件或联系管理员扩容。", + "backup_cooldown": "手动备份冷却时间尚未结束,请稍后重试。", "backup_store_full": "备份存储已满,暂时无法手动备份——请联系管理员清理空间。", "restore_unavailable": "回档功能当前不可用,请稍后再试。", - "export_busy": "正在准备的导出太多了:每人同时一个,整个平台同时最多两个,每人每小时最多六个。请过几分钟再试。", - "export_expired": "这个下载已过期或已经用过了——请重新导出。", - "export_not_ready": "下载还没准备好——请稍等片刻再试。", - "export_unavailable": "当前部署没有开通世界和备份下载——请联系管理员。", + "export_busy": "导出请求已达限制:每账户最多同时 1 个,平台最多同时 2 个,每账户每小时最多 6 个。请等待现有任务结束或限流解除后重试。", + "export_expired": "下载链接已过期或已被使用,请重新导出。", + "export_not_ready": "下载准备未完成,请稍后重试。", + "export_unavailable": "当前部署未配置世界及备份下载功能,请联系管理员。", "session_expired": "会话已过期——请重新登录。", - "forbidden": "你无权执行此操作。", + "forbidden": "当前账户无权执行此操作。", "self_protected": "不能对当前登录的账号执行此操作。", - "owner_protected": "所有者账号不能在面板里降级、禁用或删除,只能在主机的应急控制台(sudo felis breakGlass)上管理。", - "session_not_found": "这个会话已经结束了。", - "generic": "出了点问题,请稍后重试。", + "owner_protected": "面板不允许降级、禁用或删除 Owner 账户。请使用主机应急控制台(sudo felis breakGlass)管理。", + "session_not_found": "会话已结束或不存在。", + "generic": "操作失败,请稍后重试。", "otp_resend_cooldown": "验证码发送频繁,请稍后再试。", "otp_locked": "验证码错误次数过多,请重新获取验证码。", "otp_account_locked": "24 小时内验证码错误次数已达上限,此账号的邮箱验证码暂时停用,请稍后再试或改用 Passkey。", - "rate_limited": "登录请求过于频繁,请稍等一分钟再试。", - "mail_rate_limited": "服务器当前发信过多,验证码暂时发不出去,请稍后再试或改用 Passkey。", + "rate_limited": "登录请求已触发限流,请等待限流解除后重试。", + "mail_rate_limited": "邮件发送已触发限流,验证码暂时无法发送。请稍后重试或使用 Passkey。", "passkey_challenge_invalid": "验证挑战无效或已过期,请重试。", "invalid_attestation": "无法验证此 Passkey,请重试。", "passkey_already_bound": "此 Passkey 已被其他账户绑定。", "passkey_unavailable": "Passkey 功能当前不可用。", - "last_passkey": "这是你唯一的 Passkey,邮箱也还没验证,不能删除。先验证邮箱或再注册一个 Passkey。", + "last_passkey": "当前账户仅有一个 Passkey,且邮箱未验证,不允许删除。请先验证邮箱或注册其他 Passkey。", "passkey_not_allowed": "操作已被用户或浏览器取消。", "passkey_aborted": "Passkey 注册已被取消。", "bad_name": "服务器名称不合法:需为 3–32 位小写字母、数字或连字符,且不能使用保留名。", "bad_subdomain": "子域名不合法:需为 3–32 位小写字母、数字或连字符,且不能使用保留名。", "bad_display_name": "显示名称最多 64 个字符,只能包含可见字符和空格,不能含换行、制表符或不可见的控制字符。", - "at_capacity": "集群的在线服务器已达上限——等一台服务器停止后再试。", + "at_capacity": "集群运行服务器数量已达上限。请等待其他服务器停止后重试。", "storage_immutable": "存储容量不支持在线调整。", "bad_idle_stop": "空闲停服时长须在 1 分钟到 24 小时之间,或选择“从不”。", "email_taken": "该邮箱已在其他账户上完成验证;请直接用该邮箱登录,或换一个地址。", "mail_undeliverable": "验证码邮件发送失败——请稍后重试,或联系管理员检查邮件服务。", - "mail_unavailable": "服务器没有配置邮件服务,发不出验证码。请改用 Passkey 登录,或请管理员配置邮件。", + "mail_unavailable": "邮件服务未配置,无法发送验证码。请使用 Passkey 登录,或联系管理员配置邮件服务。", "bad_path": "路径不合法——请使用世界目录内的相对路径。", "too_large": "内容超出大小限制,无法处理。", "files_timeout": "文件操作超时——请稍后重试。", "files_unavailable": "文件功能当前不可用。", - "file_exists": "这里已经有同名的文件或文件夹。换个名字,或者先把原来那个改名或删除。", - "upload_staging_full": "面板的上传暂存空间快满了,这个文件没有转存过去。稍后再试,或者请管理员清理上传卷。", - "upload_incomplete": "文件还没传完上传就中断了,什么都没有改动。请重试。", - "length_required": "这次上传没有声明文件大小,被拒绝了。请从面板重新上传。", - "digest_mismatch": "文件在传输途中被改动了,服务器已拒收,什么都没有写入。请重试。", - "digest_required": "这次请求没有附带校验值,被拒绝了。请刷新面板后再试。", - "bad_digest": "这次请求附带的校验值格式不对,被拒绝了。请刷新面板后再试。", - "file_unreadable": "读不出这个文件:它在选中之后被改动、移走或删除了。请重新选择文件再上传。", - "read_damaged": "文件传过来时损坏了,所以没有打开:保存它会把损坏写回去。请重新打开。", - "upload_not_found": "这次分片上传已经不在了(取消过、已经写入、闲置超过 6 小时,或者面板服务重启过)。请重新上传。", - "too_many_uploads": "你同时进行的大文件上传已经有 4 个了。等其中一个完成,或者取消一个再试。", - "op_lost": "看不到这次操作的进度了。刷新列表看看文件有没有写入。", + "file_exists": "当前目录已存在同名文件或文件夹。请使用其他名称,或先重命名、删除现有条目。", + "upload_staging_full": "上传暂存空间不足,文件未完成转存。请稍后重试,或联系管理员清理上传卷。", + "upload_incomplete": "上传在文件传输完成前中断,本次操作未修改文件。请重新上传。", + "length_required": "上传请求未声明文件大小,已被拒绝。请通过面板重新上传。", + "digest_mismatch": "文件传输校验失败,服务器已拒绝接收,文件未写入。请重新上传。", + "digest_required": "请求缺少校验值,已被拒绝。请刷新面板后重试。", + "bad_digest": "请求校验值格式无效,已被拒绝。请刷新面板后重试。", + "file_unreadable": "无法读取所选文件,文件可能已被修改、移动或删除。请重新选择后上传。", + "read_damaged": "文件传输校验失败,未打开文件,以避免后续保存损坏数据。请重新读取。", + "upload_not_found": "分片上传记录不存在,可能已取消、已完成写入、闲置超过 6 小时或因服务重启失效。请重新上传。", + "too_many_uploads": "当前账户已达 4 个并发大文件上传上限。请等待任务完成,或取消现有上传后重试。", + "op_lost": "无法读取操作进度。请刷新文件列表以确认写入结果。", "jobs_unavailable": "后台任务服务当前不可用。", "already_terminal": "该构建已经结束,无法重复取消。", "build_unavailable": "构建功能当前不可用。", "build_logs_unavailable": "构建日志暂时不可用。", - "scan_document_not_kept": "这次构建没有留下这份扫描文件:文件太大没有保存,或者生成它的步骤失败了。", + "scan_document_not_kept": "构建未留存此扫描文件,可能由于文件过大或生成步骤失败。", "already_reviewed": "该提交已经审核过了。", - "context_changed": "提交者在你审阅之后重新上传了构建上下文。请重新下载并审阅新的上传再通过。", - "submission_quota_exceeded": "你的提交配额已满:待审核提交过多,或待审核和被拒绝的上传占满了存储额度。可以撤回一个待审核的提交;被拒绝的上传在审核 7 天后释放空间,已批准的不占额度。", + "context_changed": "构建上下文在审核期间已被重新上传。请重新下载并审核最新内容后再批准。", + "submission_quota_exceeded": "提交配额已满,待审核数量或上传存储已达上限。可撤回待审核提交;被拒绝的上传在审核 7 天后释放空间,已批准的提交不计入配额。", "submission_cooldown": "操作太频繁——请稍后再试。", "submissions_unavailable": "提交流程当前不可用。", "uploads_unavailable": "上传功能当前不可用。", "upload_busy": "这次上传的另一部分仍在发送——请稍等片刻再试。", - "upload_offset_mismatch": "上传进度与服务器对不上——重试即可从中断处接着传。", - "uploads_store_unavailable": "上传存储暂时没有响应——稍后重试即可,已传的部分会保留。", + "upload_offset_mismatch": "上传偏移量与服务器记录不一致。请重试,系统将从已确认的位置继续上传。", + "uploads_store_unavailable": "上传存储未响应,已接收的内容保留。请稍后重试。", "part_too_large": "上传的某一片超过了服务器接受的大小。", "backup_unavailable": "备份功能当前不可用。", "account_retired": "该账户已退役——请使用迁移后的账户登录。", "no_migration": "当前没有进行中的迁移。", "not_confirmed": "请先在当前浏览器完成身份确认,确认 10 分钟内有效。", - "migrate_quota_exceeded": "这次迁移带来的服务器超出了你的配额。请管理员调高配额后,在迁移码过期前用同一个码再兑换一次。", - "invalid_quota": "配额要填 0 到 2147483647 之间的整数,不限就留空。", + "migrate_quota_exceeded": "迁移服务器数量超出当前账户配额。请联系管理员调整配额,并在迁移码过期前重新兑换同一迁移码。", + "invalid_quota": "配额须为 0–2147483647 的整数。留空表示不限制。", "already_confirmed": "该迁移已经确认过了。", "invalid_target": "迁移目标账户不能与来源账户相同。", - "target_not_found": "找不到迁移目标账户。", + "target_not_found": "迁移目标账户不存在。", "target_unavailable": "迁移目标账户当前不可用。", "no_passkey": "该账户尚未绑定 Passkey。", "passkey_required": "此操作需要先绑定 Passkey。", @@ -110,38 +110,38 @@ "passkey_login_failed": "Passkey 验证失败——请重试。", "passkey_login_invalid": "Passkey 登录请求无效或已过期——请重新发起。", "too_many_challenges": "验证请求过于频繁——请稍后再试。", - "reauth_required": "请先确认是你本人:这项更改需要你在几分钟内用 Passkey 或邮箱验证码验证过。", + "reauth_required": "此操作需要近期身份验证。请使用 Passkey 或邮箱验证码重新验证身份。", "staff_reauth": "管理员账户须用 Passkey 或重新登录来确认身份。", "no_session": "只能在已登录 Felis 的浏览器中进行此操作。", "op_login_invalid": "本次管理员登录未能完成——请重新发起登录。", - "op_login_not_found": "找不到该管理员登录请求。", + "op_login_not_found": "管理员登录请求不存在。", "op_login_mismatch": "该管理员登录请求属于另一个账户。", "too_many_streams": "同时打开的实时连接过多——请关闭一些页面后再试。", "protected_admin": "该名字属于已绑定的管理员账户,不能被认领。", "auth_unavailable": "登录服务当前不可用。", "setup_token_invalid": "初始化令牌无效或已过期。", - "network_error": "连不上 Felis:网络断开了,或者 Cloudflare Access 的登录已过期。刷新页面可以重新登录。", + "network_error": "无法连接 Felis,可能由于网络中断或 Cloudflare Access 会话过期。请检查网络;Access 会话过期时,请刷新页面并重新登录。", "upstream_unavailable": "服务暂时不可用,请稍后重试。", "internal_error": "操作失败,服务器出现内部错误。请联系管理员查看日志。", "request_id": "请求编号:{{id}}", - "bad_path_param": "链接里的名称无效,请从列表重新打开。", - "payload_too_large": "内容超过了入口允许的大小,没有发送成功。", + "bad_path_param": "链接中的名称无效,请从列表重新打开目标页面。", + "payload_too_large": "请求内容超出入口大小限制,发送失败。", "passkey_no_credential": "浏览器没有返回 Passkey,请重试。", - "not_found": "要找的内容已不存在,可能已被删除,请刷新后重试。", - "conflict": "期间状态已发生变化,请刷新后重试。", + "not_found": "请求的内容不存在或已被删除,请刷新后重试。", + "conflict": "操作期间状态已变更,请刷新后重试。", "bad_request": "请求未被接受:{{detail}}", - "restore_in_progress": "这台服务器的世界正在恢复另一份备份,请等它完成后再试。", + "restore_in_progress": "世界正在恢复其他备份。请等待当前恢复完成后重试。", "uploads_full": "上传存储已满,需要管理员删除已审核的提交后才能继续上传。", "unsupported_media_type": "请求格式不被服务器接受,请刷新页面后重试。", "schedules_unavailable": "计划任务当前不可用。", - "schedule_limit": "这台服务器的计划任务数量已达上限,请先删除一个。", - "schedule_running": "这个任务正在执行,请等本次执行结束后再试。", - "schedule_stale": "保存这个任务后服务器换了所有者,请先重新保存任务再执行。", + "schedule_limit": "服务器计划任务数量已达上限。请删除现有任务后重试。", + "schedule_running": "任务正在执行,请等待本次执行结束后重试。", + "schedule_stale": "任务保存后,服务器所有者已变更。请重新保存任务后再执行。", "bad_schedule": "计划任务未保存:{{detail}}", "auth_source_unknown": "请选择已配置的认证源。", "auth_source_unavailable": "认证源暂时不可用或返回了无效角色,请稍后重试。", "auth_source_lookup_unsupported": "此认证源尚未配置角色查询地址。", - "minecraft_profile_not_found": "在所选认证源中没有找到这个角色,请检查角色名或 UUID。", + "minecraft_profile_not_found": "所选认证源中不存在此角色。请检查角色名或 UUID。", "auth_sources_unavailable": "认证源管理暂不可用。", "auth_sources_changed": "认证源已被其他操作修改。请放弃当前更改并重新读取后再保存。", "auth_source_tag_locked": "已保存的认证源标识不能改名或移除,请使用停用。" diff --git a/panel/src/i18n/resources/zh-CN/files.json b/panel/src/i18n/resources/zh-CN/files.json index 414a825..eb897e4 100644 --- a/panel/src/i18n/resources/zh-CN/files.json +++ b/panel/src/i18n/resources/zh-CN/files.json @@ -10,24 +10,24 @@ "col_size": "大小", "col_modified": "修改时间", "empty_dir_title": "此目录为空", - "empty_dir_hint": "把文件拖到这里上传,或者用上方的按钮新建文件、文件夹。", + "empty_dir_hint": "可拖拽文件至列表上传,或使用工具栏创建文件和文件夹。", "list_truncated": "列表已按服务器安全上限截断。", - "stopped_required_title": "需要先停服", - "stopped_required_body": "只有在服务器完全停止后才能浏览或编辑文件——世界卷被运行中的服务器独占挂载。请先停止服务器。", - "not_yours_title": "这不是你的服务器", + "stopped_required_title": "服务器必须停止", + "stopped_required_body": "运行中的服务器独占挂载世界卷。浏览或编辑文件前,必须完全停止服务器。", + "not_yours_title": "无权访问服务器文件", "not_yours_body": "只有服务器所有者或管理员才能编辑其文件。", - "binary_hint": "这是二进制文件,文本编辑会损坏它。请下载后使用对应的软件打开,修改后重新上传。", + "binary_hint": "二进制文件不支持文本编辑。请下载后使用适配软件处理,再重新上传;文本编辑可能损坏文件。", "saved": "已保存 {{path}}", "saving": "保存中…", "save": "保存", "too_large": "超出编辑器 {{limit}} 上限。", - "conflict_title": "文件已被其他人修改", - "conflict_body": "你打开这个文件之后,它被改过了(另一位管理者保存过,或者服务器上次运行时改写了它)。你的修改还在编辑框里,没有写入。", + "conflict_title": "文件内容已变更", + "conflict_body": "文件在打开后已被其他管理者或服务器进程修改。本次编辑尚未写入,请选择载入最新内容或覆盖现有文件。", "conflict_reload": "载入最新内容", - "conflict_reload_hint": "放弃你的修改,改看现在的文件", + "conflict_reload_hint": "放弃当前编辑并载入最新文件内容", "conflict_overwrite": "仍然覆盖", - "conflict_overwrite_hint": "用你的版本替换对方的修改", - "discard_prompt": "有未保存的修改,要放弃吗?", + "conflict_overwrite_hint": "使用当前编辑内容覆盖现有文件", + "discard_prompt": "放弃未保存的修改?", "discard": "放弃修改", "keep_editing": "继续编辑", "status_refresh_failed": "刷新服务器状态失败,页面显示的可能不是最新状态:{{reason}}", @@ -35,7 +35,7 @@ "new_file": "新建文件", "new_folder": "新建文件夹", "upload": "上传", - "upload_hint": "上传文件到这个文件夹,也可以直接拖到列表上。文件夹请先压成 .zip 再上传", + "upload_hint": "上传文件至当前目录,或拖拽文件至列表。上传文件夹前须将其压缩为 .zip。", "in_folder": "位置:{{dir}}", "name_label": "名称", "create": "创建", @@ -43,31 +43,31 @@ "rename_title": "重命名 {{name}}", "rename_item": "重命名 {{name}}", "delete_item": "删除 {{name}}", - "managed_no_rename": "这个由 Felis 管理,名称和位置都要保持不变。", - "wait_for_uploads": "等上传完成后再操作。", + "managed_no_rename": "此条目由 Felis 管理,不允许更改名称或位置。", + "wait_for_uploads": "上传完成前无法执行此操作。", "name_required": "请输入名称。", - "name_slash": "名称里不能有“/”。", + "name_slash": "名称不得包含“/”。", "name_dots": "“.” 和 “..” 不能用作名称。", - "name_too_long": "名称太长了。", - "name_taken": "这里已经有同名的文件或文件夹。", + "name_too_long": "名称超出长度限制。", + "name_taken": "当前目录已存在同名文件或文件夹。", "folder_created": "已新建文件夹 {{path}}", - "renamed": "已把 {{from}} 重命名为 {{to}}", + "renamed": "已将 {{from}} 重命名为 {{to}}", "deleted": "已删除 {{path}}", "delete": "删除", "delete_file_title": "删除 {{name}}?", "delete_folder_title": "删除文件夹 {{name}}?", - "delete_file_body": "文件会被永久删除。之后可能还要用的话,先做一次备份。", - "delete_folder_body": "文件夹和里面的所有内容都会被永久删除。之后可能还要用的话,先做一次备份。", - "secret_config_unreadable": "config/paper-global.yml 里有所有服务器共用的代理转发密钥,编辑器不打开它。", + "delete_file_body": "文件将被永久删除。请在删除前备份需要保留的内容。", + "delete_folder_body": "文件夹及其全部内容将被永久删除。请在删除前备份需要保留的内容。", + "secret_config_unreadable": "config/paper-global.yml 包含共享代理转发密钥,不允许在线读取或编辑。", "drop_here": "松开即可上传到 {{dir}}", - "upload_no_folders": "不支持上传文件夹(已跳过 {{count}} 个):散文件传到一半断掉会留下残缺存档。请先压成 .zip 上传,再对它点「解压到此处」。", + "upload_no_folders": "不支持直接上传文件夹,已跳过 {{count}} 个。为避免上传中断导致存档不完整,请先压缩为 .zip,上传后执行「解压到此处」。", "uploads_label": "上传", "uploads_title": "上传 · 已完成 {{done}}/{{total}}", "upload_queued": "等待中", "upload_sending": "{{sent}} / {{total}} · {{percent}}%", "upload_landing": "正在写入服务器…", "upload_done": "已上传", - "upload_exists": "这里已经有同名文件。", + "upload_exists": "当前目录已存在同名文件。", "upload_replace": "替换", "upload_skip": "跳过", "upload_replace_all": "全部替换", @@ -77,32 +77,32 @@ "upload_dismiss": "移除 {{name}}", "upload_clear_done": "清除已完成", "upload_progress_label": "正在上传 {{name}}", - "upload_folder_there": "这里已经有同名文件夹。", - "upload_name_too_long": "这个文件名超过 255 字节,服务器上的文件名存不下。先改短再上传。", - "upload_no_room": "世界卷只剩 {{free}},放不下这个 {{size}} 的文件。先删掉些用不着的文件再重试。", + "upload_folder_there": "当前目录已存在同名文件夹。", + "upload_name_too_long": "文件名超过 255 字节。请缩短文件名后重新上传。", + "upload_no_room": "世界卷可用空间为 {{free}},不足以写入大小为 {{size}} 的文件。请释放存储空间后重试。", "upload_landing_progress": "正在写入服务器… {{percent}}%", - "wait_for_op": "等后台操作完成后再改动。", - "wait_for_download": "等下载准备好后再操作。", - "wait_for_backup": "正在备份这台服务器的世界,备份结束后才能改动文件。", - "wait_for_restore": "正在回档这台服务器的世界,回档结束后才能改动文件。", - "wait_for_world_export": "正在导出这台服务器的世界,那边的下载结束后才能改动文件。", - "wait_for_file_download": "有一个下载正在读取这台服务器的世界,传完后才能改动文件。", - "unzip_item": "把 {{name}} 解压到此处", + "wait_for_op": "后台操作完成前无法修改文件。", + "wait_for_download": "下载准备完成前无法执行此操作。", + "wait_for_backup": "世界备份进行中,完成后方可修改文件。", + "wait_for_restore": "世界恢复进行中,完成后方可修改文件。", + "wait_for_world_export": "世界导出进行中,下载结束后方可修改文件。", + "wait_for_file_download": "文件下载正在读取世界数据,传输完成后方可修改文件。", + "unzip_item": "将 {{name}} 解压至当前目录", "unzip_conflicts_title": "解压 {{name}} 会覆盖 {{count}} 个文件", - "unzip_conflicts_body": "压缩包里的这些文件在这里已经存在。确认后会用压缩包里的版本替换它们,其余文件照常解压。旧版本之后可能还要用的话,先做一次备份。", - "unzip_conflicts_more": "还有 {{count}} 个没有列出。", + "unzip_conflicts_body": "压缩包中的部分文件与当前目录内容冲突。确认后将覆盖冲突文件并解压其余内容。请在操作前备份需要保留的原始文件。", + "unzip_conflicts_more": "另有 {{count}} 个条目未列出。", "unzip_overwrite": "覆盖并解压", "download_item": "下载 {{name}}", - "download_folder_item": "把文件夹 {{name}} 打包成 .zip 下载", + "download_folder_item": "将文件夹 {{name}} 打包为 .zip 下载", "download_preparing": "正在准备 {{name}} 的下载…", "download_started": "已开始下载 {{filename}}。", - "download_started_props": "已开始下载 {{filename}}。里面的 rcon.password 已隐去。", - "download_started_config": "已开始下载 {{filename}}。所有服务器共用的代理转发密钥 paper-global.yml 不在里面。", - "download_failed": "下载没有准备好。", - "download_failed_because": "下载没有准备好:{{reason}}", - "download_out_of_memory": "这个文件夹里的文件太多,打包任务的内存装不下它的文件清单,被系统停掉了,什么都没下载。请分成几个小一些的文件夹分别下载。", - "download_busy": "正在准备的文件下载太多了:每人同时最多两个,整个平台同时最多四个,每人每小时最多三十个。请过几分钟再试。", - "secret_config_no_download": "config/paper-global.yml 里有所有服务器共用的代理转发密钥,不能下载。", + "download_started_props": "已开始下载 {{filename}}。下载内容中的 rcon.password 已隐去。", + "download_started_config": "已开始下载 {{filename}}。包含共享代理转发密钥的 paper-global.yml 已排除。", + "download_failed": "下载准备失败。", + "download_failed_because": "下载准备失败:{{reason}}", + "download_out_of_memory": "打包任务因文件清单超出可用内存而终止,下载未开始。请拆分为较小的文件夹后分别下载。", + "download_busy": "文件下载请求已达限制:每账户最多同时 2 个,平台最多同时 4 个,每账户每小时最多 30 个。请等待现有任务结束或限流解除后重试。", + "secret_config_no_download": "config/paper-global.yml 包含共享代理转发密钥,不允许下载。", "ops_label": "后台操作", "ops_refresh_failed": "读取后台操作失败:{{reason}}", "op_unzip": "解压 {{path}}", @@ -114,18 +114,18 @@ "op_upload_done": "已写入。", "op_dismiss": "关闭 {{path}} 的结果", "op_conflicts": "查看冲突", - "op_upload_exists": "这里已经有同名文件,没有替换。重新上传时选「替换」即可。", - "op_unzip_conflicts": "会覆盖 {{count}} 个已有文件,还没有解压。", - "op_volume_full": "世界卷空间不够:需要 {{need}},只剩 {{avail}}。什么都没有改动。", - "archive_invalid": "这个压缩包损坏了,或者不是 zip 文件。什么都没有改动。", - "archive_invalid_entry": "压缩包里的 {{entry}} 损坏了(大小或校验和对不上)。什么都没有改动。", - "archive_unsafe": "压缩包里的 {{entry}} 会写到这个文件夹外面,或者是设备文件。整个压缩包都没有解压。", - "archive_symlink": "压缩包里的 {{entry}} 是符号链接。整个压缩包都没有解压。", - "type_conflict": "压缩包里的 {{entry}} 和这里已有的同名项一个是文件、一个是文件夹,覆盖解决不了。先把这里的 {{entry}} 改名或删掉再解压。", - "job_failed": "后台任务没说明原因就结束了。刷新列表确认一下,再试一次。", - "job_timed_out": "后台任务 2 小时还没做完,被停下了。刷新列表确认一下,再试一次。", - "job_out_of_memory": "后台任务用完了内存,被系统停掉了。服务器上的文件没有被改动,再试一次。", - "job_out_of_memory_unzip": "压缩包里的文件太多,解压任务的内存装不下它的文件清单,被系统停掉了。服务器上的文件没有被改动。把它拆成几个小一些的 zip,分别上传解压。", + "op_upload_exists": "同名文件已存在,本次上传未替换原文件。重新上传时可选择「替换」。", + "op_unzip_conflicts": "解压将覆盖 {{count}} 个现有文件,操作尚未执行。", + "op_volume_full": "世界卷空间不足:需要 {{need}},可用 {{avail}}。本次操作未修改文件。", + "archive_invalid": "压缩包损坏或格式不是 ZIP。本次操作未修改文件。", + "archive_invalid_entry": "压缩包条目 {{entry}} 的大小或校验和不匹配。本次操作未修改文件。", + "archive_unsafe": "压缩包条目 {{entry}} 包含越界路径或设备文件,整个压缩包未解压。", + "archive_symlink": "压缩包条目 {{entry}} 为符号链接,整个压缩包未解压。", + "type_conflict": "条目 {{entry}} 与当前目录中的同名项存在文件与文件夹类型冲突,无法直接覆盖。请先重命名或删除现有的 {{entry}},再执行解压。", + "job_failed": "后台任务已结束,未提供失败原因。请刷新文件列表确认操作结果后重试。", + "job_timed_out": "后台任务超过 2 小时执行上限,已终止。请刷新文件列表确认操作结果后重试。", + "job_out_of_memory": "后台任务因内存不足而终止,服务器文件未修改。请检查任务资源后重试。", + "job_out_of_memory_unzip": "解压任务因文件清单超出可用内存而终止,服务器文件未修改。请将内容拆分为较小的 ZIP 文件,分别上传并解压。", "binary_title": "此文件不能作为文本编辑", "preview_large_title": "文件超出在线预览上限", "preview_large_hint": "在线编辑器最多读取 {{limit}}。请下载后编辑,再重新上传。", diff --git a/panel/src/i18n/resources/zh-CN/lobby.json b/panel/src/i18n/resources/zh-CN/lobby.json index af3e5ae..7fb8e5a 100644 --- a/panel/src/i18n/resources/zh-CN/lobby.json +++ b/panel/src/i18n/resources/zh-CN/lobby.json @@ -4,8 +4,8 @@ "space_label": "选择要自定义的空间", "login": "登录空间", "lobby": "正式大厅", - "login_description": "玩家先在这里验证身份,再进入正式大厅。登录场景使用 Limbo schematic,不能直接使用 Paper 世界地图。", - "lobby_description": "玩家在这里选择服务器。可以更换地图、安装插件、自定义大厅体验并授权建造。", + "login_description": "玩家在登录空间完成身份验证后进入正式大厅。登录场景使用 Limbo schematic,不支持直接使用 Paper 世界地图。", + "lobby_description": "正式大厅提供服务器选择,可配置地图、插件、场景体验及建造权限。", "connection_version": "安装时的游戏版本:Java {{version}}", "unknown_version": "未记录", "version_hint": "客户端版本兼容由代理插件决定。更换版本需配套更新登录空间、大厅镜像和代理;此处显示安装版本,实际版本以镜像为准。", @@ -18,7 +18,7 @@ "restarting": "正在重启…", "restart_requested": "已发送重启请求,可在上方查看启动状态。", "restart_title": "重启该空间?", - "restart_hint": "重启会暂时断开这里的玩家,重新启动后应用已保存的设置。", + "restart_hint": "重启将断开此空间内的玩家连接。已保存配置在启动后生效。", "restart_required": "设置已保存,等待重启生效", "save": "保存设置", "saving": "正在保存…", @@ -57,7 +57,7 @@ "bookHelp": "登录说明", "openBook": "自动打开登录书", "loginTimeoutSeconds": "登录等待时间(30–3600 秒)", - "login_settings_hint": "绑定码和登录链接始终由系统生成,聊天中的提示仍保留。部署中若显式设置登录超时环境变量,它优先于这里的等待时间。", + "login_settings_hint": "绑定码及登录链接由系统生成,聊天提示保持启用。部署中显式设置的登录超时环境变量优先于此处配置。", "lobby_settings_hint": "欢迎提示会附带可点击的服务器菜单。出生点使用地图保存的位置,可在大厅控制台用 setworldspawn x y z 设置。保护开关影响整个大厅;建造权限玩家保留自己的游戏模式。", "ADVENTURE": "冒险", "SURVIVAL": "生存", @@ -80,13 +80,13 @@ "backups_title": "地图备份与恢复", "backups_hint": "更换地图、插件或镜像前保存备份;可选择已有备份恢复。", "builder_title": "大厅建造权限", - "builder_hint": "授予 felis.lobby.build 后,该玩家可在大厅建造,不必开放所有人的建造权限。", + "builder_hint": "授予 felis.lobby.build 后,指定玩家可在大厅建造,无需向所有玩家开放建造权限。", "builder_player": "Minecraft 角色名", "builder_grant": "允许建造", "builder_revoke": "禁止建造", "builder_running": "请先启动大厅,才能修改建造权限。", "discard_title": "放弃未保存的设置?", - "discard_hint": "切换空间会丢失本次编辑。可以取消后先保存。", + "discard_hint": "切换空间将放弃未保存的修改。需要保留时,请取消切换并保存配置。", "discard": "放弃并切换", "appearance_description": "设置玩家加入时看到的欢迎信息与菜单。", "players_description": "设置加入行为、游戏模式与玩家保护。", diff --git a/panel/src/i18n/resources/zh-CN/ops.json b/panel/src/i18n/resources/zh-CN/ops.json index 6688f78..933b0a9 100644 --- a/panel/src/i18n/resources/zh-CN/ops.json +++ b/panel/src/i18n/resources/zh-CN/ops.json @@ -16,7 +16,7 @@ "fleet_sort": "排序方式", "fleet_sort_name": "按名称排序", "fleet_sort_status": "按状态排序", - "fleet_sort_players": "在线人数多的在前", + "fleet_sort_players": "按在线人数降序", "fleet_count": "共 {{count}} 台", "fleet_count_filtered": "{{shown}} / {{total}} 台", "fleet_col_status": "状态", @@ -29,7 +29,7 @@ "fleet_unclaimed": "未认领", "fleet_owner_you": "你", "fleet_owner_unknown": "未知", - "fleet_owner_unknown_hint": "暂时查不到所有者,下次刷新时会重新显示。", + "fleet_owner_unknown_hint": "服务器所有者读取失败,将在下次刷新时重新查询。", "fleet_endpoint_idle": "未运行", "policy_owneronly": "仅所有者", "policy_public": "公开", diff --git a/panel/src/i18n/resources/zh-CN/schedules.json b/panel/src/i18n/resources/zh-CN/schedules.json index 82eef98..0dd1a93 100644 --- a/panel/src/i18n/resources/zh-CN/schedules.json +++ b/panel/src/i18n/resources/zh-CN/schedules.json @@ -1,28 +1,28 @@ { "title": "计划任务", - "subtitle": "按设定的时间重启、关闭、启动或备份这台服务器,或向它的控制台发送命令。", + "subtitle": "按计划执行服务器启动、停止、重启、备份或控制台命令。", "back_to_console": "返回控制台", "not_yours_title": "无权管理计划任务", "not_yours_body": "只有所有者或管理员才能查看和修改该服务器的计划任务。", "new_task": "新建任务", "count": "{{count}} / {{limit}} 个任务", - "limit_reached": "每台服务器最多 {{limit}} 个计划任务,删除一个后才能再添加。", + "limit_reached": "计划任务数量已达 {{limit}} 个上限。请删除现有任务后再添加。", "unavailable_title": "计划任务不可用", - "unavailable_body": "当前 felis-api 未配置计划任务存储,这里无法查看或保存任务。请联系管理员检查 felis-api 的配置。", - "empty_title": "还没有计划任务", - "empty_hint": "可以从常用任务开始,也可以点“新建任务”自己设置。", + "unavailable_body": "felis-api 未配置计划任务存储,无法读取或保存任务。请联系管理员检查配置。", + "empty_title": "暂无计划任务", + "empty_hint": "可选择常用任务模板,或执行「新建任务」配置自定义任务。", "template_restart": "每天 04:00 重启", "template_backup": "每天 05:00 备份", "template_announce": "每 30 分钟在聊天栏发公告", "template_announce_command": "say 欢迎来到服务器!", "action_command": "控制台命令", - "action_command_desc": "向服务器控制台发送一条命令,效果和在控制台里输入一样。仅在服务器运行时执行。", + "action_command_desc": "向服务器控制台发送命令,与直接输入控制台命令一致。仅在服务器运行时执行。", "action_restart": "重启", "action_restart_desc": "关闭服务器后再启动,在线玩家会被断开。服务器已关闭时跳过。", "action_stop": "关闭", "action_stop_desc": "关闭服务器,在线玩家会被断开。", "action_start": "启动", - "action_start_desc": "服务器关闭时启动它。", + "action_start_desc": "仅在服务器停止时执行启动。", "action_backup": "备份", "action_backup_desc": "备份世界。运行中的服务器会先关闭,备份完成后再启动。", "days_all": "每天", @@ -59,29 +59,29 @@ "opt_hours": "{{count}} 小时", "create_title": "新建计划任务", "edit_title": "编辑计划任务", - "dialog_desc": "选择执行什么、什么时候执行。", + "dialog_desc": "配置任务类型、执行时间及运行参数。", "create": "创建", "save": "保存", - "field_action": "执行什么", + "field_action": "任务类型", "field_command": "命令", "command_placeholder": "say 服务器将在 04:00 重启", - "command_hint": "一条控制台命令,开头的 / 可加可不加。", - "field_when": "什么时候", + "command_hint": "填写一条控制台命令,命令开头的 / 可省略。", + "field_when": "执行方式", "mode_daily": "每天一次", "mode_interval": "循环执行", "field_time": "时间", "field_every": "间隔", "interval_preview": "每天从 00:00 起算:{{times}}", "interval_hourly_limit": "重启、关闭、启动和备份最多每小时执行一次。", - "field_days": "哪几天", + "field_days": "执行日期", "field_timezone": "时区", - "timezone_hint": "按这个时区计算时间。你的浏览器时区是 {{zone}}。", + "timezone_hint": "执行时间按所选时区计算。浏览器时区:{{zone}}。", "timezone_use_browser": "改用浏览器时区", "field_warn": "提醒玩家", "warn_none": "不提醒", "warn_option": "提前 {{count}} 分钟", "warn_hint": "服务器运行时,在关闭前于游戏聊天栏发一条中英双语提醒。", - "backup_note": "备份运行中的服务器时会先关闭它(在线玩家会被断开),备份世界后再启动,通常需要几分钟。已关闭的服务器备份后保持关闭。", + "backup_note": "备份运行中的服务器时,将先停止服务器并断开在线玩家,备份完成后重新启动。已停止的服务器在备份后保持停止。", "field_label": "名称(可选)", "label_placeholder": "每晚重启", "field_enabled": "启用", @@ -94,7 +94,7 @@ "err_timezone": "无法识别的时区,请填写类似 Asia/Shanghai 的名称。", "err_label_long": "名称超过 {{max}} 个字符。", "next_run": "下次执行:{{when}}", - "next_run_off": "已停用,只在点“立即执行”时运行", + "next_run_off": "已停用,仅在执行「立即执行」时运行", "never_run": "尚未执行", "last_run": "上次执行:{{when}}", "run_started_at": "开始于:{{when}}", @@ -109,20 +109,20 @@ "run_state_starting": "正在启动服务器…", "warn_badge": "提前 {{count}} 分钟提醒", "off_badge": "已停用", - "tz_differs": "按 {{zone}} 时区计算,你的浏览器时区是 {{browser}}。", + "tz_differs": "任务时区:{{zone}}。浏览器时区:{{browser}}。", "toggle_label": "按计划执行「{{name}}」", "run_now": "立即执行", "edit": "编辑", "delete": "删除", - "busy_hint": "任务正在执行,请等本次执行结束。", - "owner_changed": "服务器更换了所有者,这个任务已自动停用。检查无误后保存或重新启用即可恢复。", + "busy_hint": "任务正在执行,请等待本次执行结束。", + "owner_changed": "服务器所有者已变更,任务已自动停用。确认配置后,可重新保存或启用任务。", "detail_not_running": "服务器未在运行。", "detail_already_stopped": "服务器已经是关闭状态。", "detail_already_running": "服务器已经在运行。", "detail_gone": "服务器已不存在。", - "detail_no_world": "服务器还没有世界存档。", + "detail_no_world": "服务器尚未创建世界存档。", "detail_missed": "计划时间点 felis-api 未在运行。", - "detail_started_meanwhile": "执行结束前有人启动了服务器。", + "detail_started_meanwhile": "任务结束前,服务器已被其他操作启动。", "detail_interrupted": "felis-api 在执行过程中停止了。", "detail_store_full": "备份存储已满,请联系管理员清理空间。", "detail_console": "无法连接服务器控制台。", @@ -130,15 +130,15 @@ "detail_retiring": "服务器正在被放弃或删除。", "run_title": "立即执行「{{name}}」?", "run_confirm": "立即执行", - "run_body_command": "立即向服务器控制台发送下面这条命令,服务器关闭时跳过:", + "run_body_command": "向服务器控制台发送以下命令;服务器停止时将跳过:", "run_body_restart": "立即关闭服务器,然后重新启动。", "run_body_stop": "立即关闭服务器。", - "run_body_start": "服务器关闭时立即启动它。", + "run_body_start": "服务器停止时立即执行启动。", "run_body_backup": "立即备份世界。运行中的服务器会先关闭,备份后再启动;已关闭的服务器备份后保持关闭。", "run_kick": "在线玩家会被立即断开:手动执行不发送提醒。", "run_keeps_next": "下次计划执行时间保持不变。", "delete_title": "删除「{{name}}」?", - "delete_body": "任务会被删除,之后不再执行。", + "delete_body": "删除后任务将停止执行,此操作不保留任务配置。", "delete_confirm": "删除", "created": "已创建「{{name}}」。", "saved": "已保存「{{name}}」。", @@ -149,6 +149,6 @@ "notes_title": "计划任务如何执行", "note_timing": "felis-api 每 15 秒检查一次,任务会在设定时间后约 15 秒内开始。晚于计划 10 分钟以上的执行(例如 felis-api 当时未运行)会记为错过,下一次照常执行。", "note_state": "命令、重启和关闭仅在服务器运行时执行,启动仅在服务器关闭时执行,其余情况会跳过。", - "note_owner": "服务器更换所有者后,它的任务会自动停用;重新保存即可交给当前所有者。", + "note_owner": "服务器所有者变更后,关联任务将自动停用。重新保存任务后,任务归属将更新为当前所有者。", "note_run_now": "“立即执行”对已停用的任务同样有效,不发送提醒,也不影响下次计划执行。" } diff --git a/panel/src/i18n/resources/zh-CN/servers.json b/panel/src/i18n/resources/zh-CN/servers.json index 780da04..986d61e 100644 --- a/panel/src/i18n/resources/zh-CN/servers.json +++ b/panel/src/i18n/resources/zh-CN/servers.json @@ -1,12 +1,12 @@ { "my_servers_title": "我的服务器", - "my_servers_subtitle": "管理你拥有或可用的服务器。", + "my_servers_subtitle": "管理当前账户拥有或有权访问的服务器。", "search_placeholder": "搜索服务器名称或地址…", "filter_status_all": "全部状态", "search_no_match": "没有匹配的服务器。", "search_clear_btn": "清除筛选", "no_servers_linked": "暂无关联的服务器", - "no_servers_hint": "认领一台你拥有访问权限的服务器,或联系管理员为你分配。", + "no_servers_hint": "可认领已授权的服务器,或联系管理员分配。", "phase_running": "运行中", "phase_starting": "启动中", "phase_stopping": "停止中", @@ -18,7 +18,7 @@ "console": "控制台", "claim": "认领", "claim_server_title": "认领服务器", - "claim_server_desc": "确认认领服务器「{{name}}」吗?认领后你将成为该服务器的所有者,获得控制台、白名单与备份管理权限。", + "claim_server_desc": "认领「{{name}}」后,当前账户将成为服务器所有者,并获得控制台、白名单及备份管理权限。", "claiming": "认领中…", "stop": "停止", "stopping": "停止中…", @@ -66,7 +66,7 @@ "system_service": "系统服务", "system_service_hint": "系统服务由管理员在“登录与大厅”中管理。", "players_back_to_console": "返回控制台", - "players_not_yours_title": "这不是你的服务器", + "players_not_yours_title": "无权管理此服务器", "players_not_yours_body": "只有所有者或管理员才能管理此服务器的玩家。", "players_not_running_title": "服务器已休眠", "players_not_running_body": "玩家管理需要与服务器保持实时连接。启动后即可管理白名单、在线玩家与封禁。", @@ -84,7 +84,7 @@ "access_whitelist_desc": "白名单开启时,仅名单内玩家可进服。", "access_whitelist_add": "添加", "access_whitelist_empty": "白名单暂无玩家。", - "access_whitelist_empty_hint": "在上方添加玩家即可放行进服。", + "access_whitelist_empty_hint": "添加玩家后将允许其加入服务器。", "access_whitelist_remove": "将 {{player}} 移出白名单", "access_remove": "移除", "access_whitelist_load_error": "无法加载白名单。", @@ -108,19 +108,19 @@ "access_ban_confirm": "确认封禁 {{player}}?此玩家将被踢出并无法再进入。", "access_ban_confirm_yes": "确认封禁", "access_ban_empty": "暂无封禁玩家。", - "access_ban_empty_hint": "被封禁的玩家会显示在这里,可随时一键解封。", + "access_ban_empty_hint": "封禁名单将列出被封禁的玩家,并提供解封操作。", "access_ban_load_error": "无法加载封禁名单。", "access_ban_raw": "查看服务器原始返回", "access_cancel": "取消", "access_banned": "已封禁 {{player}}。", "access_pardoned": "已解封 {{player}}。", "wake_list_title": "唤醒名单", - "wake_list_desc": "自动启动策略设为「唤醒名单」时,服务器休眠后只有你、管理员和这里能唤醒的玩家可以把它启动。玩家第一次进服会自动加入;取消某人的唤醒权限后,他再进服也不会恢复,需要时在这里重新允许。服务器换主人时名单清空。", - "wake_list_inactive_owner": "当前自动启动策略是「仅所有者」,休眠时只有你和管理员能启动,这份名单暂不生效。在控制台的「编辑服务器配置」里改成「唤醒名单」后生效。", - "wake_list_inactive_public": "当前自动启动策略是「公开」,任何玩家进服都能启动,这份名单暂不生效。在控制台的「编辑服务器配置」里改成「唤醒名单」后,只有名单里能唤醒的玩家可以启动。", + "wake_list_desc": "自动启动策略为「唤醒名单」时,仅服务器所有者、管理员和具有唤醒权限的玩家可启动休眠服务器。玩家首次加入后自动列入名单;撤销权限后,再次加入不会恢复权限,须手动重新授予。服务器所有者变更后名单清空。", + "wake_list_inactive_owner": "当前策略为「仅所有者」,仅服务器所有者和管理员可启动休眠服务器,唤醒名单不生效。可在「编辑服务器配置」中将策略改为「唤醒名单」。", + "wake_list_inactive_public": "当前策略为「公开」,任意玩家加入均可触发启动,唤醒名单不生效。改为「唤醒名单」后,仅服务器所有者、管理员和具有唤醒权限的玩家可启动。", "wake_list_load_error": "无法加载唤醒名单。", - "wake_list_empty": "还没有玩家进过这台服务器。", - "wake_list_empty_hint": "玩家第一次进服后会出现在这里。", + "wake_list_empty": "暂无玩家加入记录。", + "wake_list_empty_hint": "玩家首次加入后将自动列入名单。", "wake_list_unlinked": "未绑定账号的玩家", "wake_list_joined": "{{when}}首次进服", "wake_list_revoked_badge": "已取消", @@ -132,12 +132,12 @@ "wake_list_restored": "已重新允许 {{player}} 唤醒服务器。", "create_server_btn": "新建服务器", "create_server_title": "创建服务器", - "create_server_desc": "从白名单镜像及规格中选择,平台自动处理其余配置。不暴露原始集群配置。", + "create_server_desc": "选择白名单镜像及资源规格。平台自动完成服务器配置,不提供原始集群配置编辑。", "create_server_name": "名称", "create_server_name_placeholder": "survival", "create_server_name_hint": "3–32 位小写字母、数字或连字符。", "create_server_name_invalid": "请用 3–32 位小写字母、数字或连字符,首尾不能是连字符。", - "create_server_name_reserved": "“{{name}}” 是平台保留的名字,请换一个。", + "create_server_name_reserved": "「{{name}}」为平台保留名称,请使用其他名称。", "create_server_subdomain": "子域名", "create_server_subdomain_placeholder": "survival", "create_server_subdomain_hint": "访问地址:{{host}}", @@ -146,7 +146,7 @@ "create_server_image": "镜像", "create_server_image_loading": "加载镜像列表中…", "create_server_image_choose": "选择一个镜像", - "create_server_image_unavailable": "镜像列表未读到", + "create_server_image_unavailable": "镜像列表不可用", "create_server_image_failed": "镜像列表读取失败:{{reason}}", "create_server_image_none": "暂无白名单镜像", "create_server_memory": "内存", @@ -154,7 +154,7 @@ "create_server_policy": "自动启动策略", "create_server_policy_owner": "仅所有者——通过面板启动", "create_server_policy_public": "公开——任意玩家加入即启动", - "create_server_policy_allowlist": "唤醒名单——进过服、未被取消唤醒的玩家可启动", + "create_server_policy_allowlist": "唤醒名单——已加入且具有唤醒权限的玩家可启动", "create_server_cancel": "取消", "create_server_submit": "创建", "edit_server_title": "编辑服务器配置", @@ -162,8 +162,8 @@ "edit_server_desc_long": "正在修改服务器的 spec 配置。未修改的项将保持原样。", "edit_server_submit": "保存配置", "edit_server_image_build": "构建 {{id}}", - "edit_server_image_pinned_hint": "已锁定在构建 {{id}}:镜像标签以后指向新构建(比如重跑安装器)不会影响这台服务器,只有在这里更换镜像才会。", - "edit_server_image_warning": "保存后,服务器会换用这个镜像标签现在指向的构建;重新选择原来的标签,拿到的也是它最新的构建。如果 Minecraft 版本变了,世界会在下次启动时升级,升级过的区块无法再用旧版本打开。", + "edit_server_image_pinned_hint": "服务器已固定至构建 {{id}}。镜像标签指向其他构建时不会自动更新;更换镜像配置后才会使用新构建。", + "edit_server_image_warning": "保存后将使用镜像标签当前指向的构建,重新选择同一标签也将采用其最新构建。Minecraft 版本变更可能在下次启动时升级世界;升级后的区块不支持旧版本。", "edit_server_image_confirm": "我已备份世界,确认更换镜像", "luckperms_group_name": "用户组名称", "luckperms_node": "权限节点", @@ -183,9 +183,9 @@ "luckperms_remove_group_btn": "移除用户组", "luckperms_remove_perm_btn": "移除权限", "luckperms_perm_form_title": "添加或移除权限", - "luckperms_remove_perm_hint": "移除会清掉该世界范围里的这个节点,授予和拒绝都一样清掉;权限值只对添加生效。", - "luckperms_groups_unread": "读不到这名玩家现有的用户组。要移除某个组,在下面填上组名后点「移除用户组」。", - "luckperms_perms_unread": "读不到这名玩家现有的权限节点。要移除某个节点,在下面填上节点名(按世界设置的再填世界)后点「移除权限」。", + "luckperms_remove_perm_hint": "移除操作将删除指定世界范围内的权限节点,无论其值为允许或拒绝。权限值仅用于添加操作。", + "luckperms_groups_unread": "无法读取玩家现有用户组。可填写用户组名称并执行「移除用户组」。", + "luckperms_perms_unread": "无法读取玩家现有权限节点。可填写节点名称及适用的世界范围,并执行「移除权限」。", "luckperms_groups_title": "所属用户组", "luckperms_permissions_title": "拥有权限节点", "luckperms_node_column": "权限节点", @@ -198,16 +198,16 @@ "luckperms_recent_actions": "最近操作历史", "luckperms_no_recent_actions": "暂无最近操作历史。", "luckperms_revert": "撤销", - "luckperms_revert_to_set": "撤销会把 {{node}} 改回 {{value}}", + "luckperms_revert_to_set": "撤销后将 {{node}} 恢复为 {{value}}", "luckperms_revert_to_unset": "撤销会移除 {{node}}", "luckperms_revert_unknown": "无法撤销:原值未知", "luckperms_remove_group": "移除权限组 {{group}}", "luckperms_remove_perm": "移除权限 {{node}}", "luckperms_revert_failed": "撤销失败:{{reason}}", - "luckperms_revert_success": "已成功撤销该操作!", + "luckperms_revert_success": "操作已撤销。", "luckperms_presets": "预设", "luckperms_no_parent_groups": "未分配任何父组", - "luckperms_no_reply": "这台服上的 LuckPerms 不通过 RCON 回话,所以显示不出玩家现有的用户组和权限节点。添加和移除照常生效:在下面填上组名或节点名即可。按玩家名的操作只对「自 LuckPerms 安装以来进过本服」的玩家可靠,对没进过服的玩家名可能静默不生效。", + "luckperms_no_reply": "LuckPerms 未通过 RCON 返回查询结果,无法显示现有用户组和权限节点。添加及移除操作仍可执行。按玩家名操作仅对安装 LuckPerms 后已加入过服务器的玩家可靠;未加入过的玩家可能无法应用更改,且不返回错误。", "luckperms_no_output": "(服务器未返回输出,无法确认结果)", "luckperms_global": "全局", "luckperms_no_perms": "尚未分配任何权限节点", @@ -215,37 +215,37 @@ "luckperms_clear_history": "清除历史记录", "edit_server_cpu": "CPU 限制", "edit_server_cpu_placeholder": "例如 1、1.5、500m,留空不限制", - "edit_server_cpu_invalid": "填核数(1、1.5)或毫核(500m),要大于 0。", + "edit_server_cpu_invalid": "CPU 限制须为大于 0 的核数(如 1、1.5)或毫核(如 500m)。", "owned_filter_mine": "我", "edit_server_idle": "空闲自动停服", - "edit_server_idle_hint": "没人在线达到这个时长就自动停服,省下内存和 CPU;玩家下次进服时自动唤醒。", - "edit_server_idle_unknown": "现在读不到在线人数,空闲停服暂停中,直到能读到为止。", + "edit_server_idle_hint": "在线人数为零且达到配置时长后,服务器将自动停止并释放内存及 CPU。后续玩家加入时,按自动启动策略处理唤醒。", + "edit_server_idle_unknown": "在线人数读取失败,空闲停服已暂停。读取恢复后将继续判断。", "idle_stop_never": "从不", "idle_stop_seconds": "{{count}} 秒", "idle_stop_minutes": "{{count}} 分钟", "idle_stop_hours": "{{count}} 小时", - "stop_confirm_players": "{{count}} 名玩家在线。停服会先在游戏里提醒他们,30 秒后断开。确定停止?", - "stop_confirm_unknown": "读不到在线人数,可能有玩家在线。确定停止?", + "stop_confirm_players": "当前有 {{count}} 名玩家在线。停止操作将先发送游戏内通知,30 秒后断开玩家连接。确认停止?", + "stop_confirm_unknown": "在线人数读取失败,可能仍有玩家在线。确认停止?", "retire_card_title": "放弃服务器", "retire_card_title_admin": "放弃或删除服务器", - "retire_card_desc": "不再需要时交还给平台:世界归档后,服务器释放给其他人认领。", + "retire_card_desc": "放弃服务器后,世界将归档,服务器将释放供其他账户认领。", "retire_card_desc_admin": "放弃会归档世界并释放服务器;删除会归档世界并移除服务器,名称和子域名可以重新使用。", "retire_release": "放弃服务器", "retire_delete": "删除服务器", "retire_release_title": "放弃「{{name}}」?", "retire_delete_title": "删除「{{name}}」?", - "retire_step_stop": "服务器会立即停止,处理完之前不能启动。", + "retire_step_stop": "服务器将进入停止流程,回收处理完成或请求撤销前无法启动。", "retire_step_release": "下一次每日回收运行时,世界先归档(默认保留 90 天),然后删除世界存储,服务器释放给其他人认领。", - "retire_step_delete": "下一次每日回收运行时,世界先归档(默认保留 90 天),然后删除世界存储和这台服务器,名称和子域名释放出来。", - "retire_step_cancel_owner": "回收运行之前可以在控制台撤销;在此之前它仍计入你的配额。", + "retire_step_delete": "下一次每日回收时将归档世界(默认保留 90 天),再删除世界存储及服务器记录,释放名称和子域名。", + "retire_step_cancel_owner": "回收执行前可在控制台撤销请求。完成回收前,服务器仍计入账户配额。", "retire_step_cancel_admin": "回收运行之前管理员可以在控制台撤销。", "retire_confirm_label": "输入服务器名称以确认:", "retire_confirm_release": "放弃服务器", "retire_confirm_delete": "删除服务器", - "retiring_title_release": "这台服务器已被放弃", - "retiring_title_delete": "这台服务器即将被删除", - "retiring_body_release": "{{when}}提出。下一次每日回收运行时会归档世界,然后删除世界存储并释放服务器。在此之前它保持停止、不能启动,仍计入配额。", - "retiring_body_delete": "{{when}}提出。下一次每日回收运行时会归档世界,然后删除世界存储和这台服务器。在此之前它保持停止、不能启动。", + "retiring_title_release": "服务器待回收", + "retiring_title_delete": "服务器待删除", + "retiring_body_release": "请求提交于 {{when}}。下一次每日回收时将归档世界、删除世界存储并释放服务器。处理完成前服务器保持停止,无法启动,仍计入账户配额。", + "retiring_body_delete": "请求提交于 {{when}}。下一次每日回收时将归档世界、删除世界存储及服务器记录。处理完成前服务器保持停止,无法启动。", "retiring_cancel": "撤销", "retiring_cancel_admin_only": "删除由管理员提出,只有管理员可以撤销。", "retiring_badge_release": "等待回收", @@ -278,7 +278,7 @@ "startup_booting": "正在启动 Minecraft 并检查就绪状态", "startup_elapsed": "本次启动已等待 {{seconds}} 秒", "startup_memory": "可调度内存不足。请降低此服务器的内存配置、停止其他服务器,或增加节点容量。Java 尚未启动。", - "startup_hint": "启动状态会自动刷新。资源调度、文件准备与游戏启动是不同阶段,没有日志输出并不代表程序卡死。", + "startup_hint": "启动分为资源调度、文件准备及游戏初始化阶段,状态将自动刷新。无日志输出时,请依据当前阶段及诊断信息判断进度。", "status_stale": "状态已过期", "status_stale_detail": "无法确认服务器当前状态。最后成功读取:{{time}}。正在自动重试。{{error}}", "startup_failed": "启动受阻或容器异常", @@ -288,10 +288,10 @@ "startup_waiting_resources": "等待资源", "startup_failed_hint": "请根据报错检查镜像、配置与内存。修正问题后再重新启动服务器。", "emergency_stop": "应急停止", - "emergency_stop_warning": "普通停止无响应时使用。直接将工作负载缩到零,绕过游戏进程和 Operator,保留正常退出时间;不会删除世界,但无法保证未保存的内容。需要重新验证身份。", - "emergency_stop_accepted": "停止意图已保存,工作负载已缩到零。仍需等待 Pod 退出;上方状态会继续检查,不能将请求受理视为已经停止。", + "emergency_stop_warning": "普通停止无响应时,可直接将工作负载副本数设为零,绕过游戏进程及 Operator,并保留正常终止宽限期。世界数据不删除,但未保存的更改可能丢失。执行前须重新验证身份。", + "emergency_stop_accepted": "停止意图已保存,工作负载副本数已设为零。系统将继续检查 Pod 退出状态;请求受理不代表进程已停止。", "emergency_stop_unknown": "操作结果尚未确认。停止意图可能已经保存,请重新读取状态;若控制 API 不可达,请使用主机侧恢复步骤。", - "host_recovery": "控制 API 不可达?查看主机侧应急停止步骤", - "host_recovery_hint": "面板无法绕过已经离线的控制 API。仅在部署主机上执行下列命令:先保存停止意图,再缩到零,最后检查游戏 Pod。", - "host_recovery_verify": "如果 Pod 仍在退出或节点不可达,不能确认进程已停止。检查对应节点和容器运行时;不要强制删除 Pod 来冒充停止成功。" + "host_recovery": "主机侧应急停止步骤", + "host_recovery_hint": "控制 API 离线时,面板无法执行集群操作。请在部署主机依次执行以下命令:保存停止意图、将副本数设为零、检查游戏 Pod。命名空间不是 minecraft 时须替换命令中的命名空间。", + "host_recovery_verify": "Pod 仍在终止或节点不可达时,无法确认进程已停止。请检查对应节点及容器运行时;强制删除 Pod 不能作为进程退出的依据。" } diff --git a/panel/src/i18n/resources/zh-CN/submissions.json b/panel/src/i18n/resources/zh-CN/submissions.json index f9c5b0e..79ab340 100644 --- a/panel/src/i18n/resources/zh-CN/submissions.json +++ b/panel/src/i18n/resources/zh-CN/submissions.json @@ -27,7 +27,7 @@ "build_status_failed": "构建失败", "build_status_cancelled": "已取消", "no_submissions_title": "暂无提交记录", - "no_submissions_hint": "您还没有提交过任何模组包。", + "no_submissions_hint": "当前账户暂无模组包提交记录。", "search_no_results": "无匹配结果", "search_no_results_hint": "尝试更换搜索词或筛选条件。", "search_placeholder": "搜索提交记录...", @@ -38,17 +38,17 @@ "field_context_ref": "构建上下文引用", "field_image_ref": "目标镜像引用", "field_context_sha256": "上下文 SHA-256", - "field_context_sha256_hint": "平台收到的文件摘要,可用 sha256sum 与本地文件核对。审核通过的就是这份内容。", + "field_context_sha256_hint": "服务器已接收内容的摘要。可使用 sha256sum 与本地文件核对;审核通过后将固定此内容。", "clear_btn": "清除", "file_hint": "支持 .tar.gz 格式,最大 {{limit}}", "withdraw_btn": "撤回提交", - "withdraw_hint": "撤回会删除该提交及其已上传的构建上下文,并释放你的待审核名额与存储配额。", + "withdraw_hint": "撤回将删除提交及已上传的构建上下文,并释放对应的待审核名额与存储配额。", "withdraw_confirm": "确认撤回", "withdraw_cancel": "取消", "upload_progress_label": "上传进度", "upload_progress": "{{sent}} / {{total}}", "pause_btn": "暂停", "resume_btn": "继续上传", - "resume_hint": "“{{name}}”已保存,构建上下文已有 {{sent}} 传到服务器。再次提交即可从这里接着传。", - "restart_hint": "“{{name}}”已保存。新选的文件会从头上传。" + "resume_hint": "「{{name}}」已保存,服务器已接收 {{sent}} 构建上下文。再次提交将从已确认的位置继续上传。", + "restart_hint": "「{{name}}」已保存。重新选择文件后将从起始位置上传。" } diff --git a/panel/src/lib/api.test.ts b/panel/src/lib/api.test.ts index cb4d953..cfeb148 100644 --- a/panel/src/lib/api.test.ts +++ b/panel/src/lib/api.test.ts @@ -114,7 +114,7 @@ describe("session auth wire shapes", () => { it("maps the auth error codes to stable human copy", async () => { const { humanizeError } = await import("./api"); - expect(humanizeError({ code: "local_auth_disabled" })).toMatch(/turned off/i); + expect(humanizeError({ code: "local_auth_disabled" })).toMatch(/disabled/i); expect(humanizeError({ code: "staff_account" })).toMatch(/operator/i); }); @@ -227,7 +227,7 @@ describe("account migration wire shapes", () => { it("says a redeem over quota can be retried with the same code once the quota fits", () => { expect(humanizeError({ status: 403, code: "migrate_quota_exceeded" })).toBe( - "The servers this migration brings over don't fit your quota. Ask an admin to raise it, then redeem the same code again before it expires.", + "The migration exceeds the current account’s server quota. Request a quota adjustment from an administrator and redeem the same code again before it expires.", ); }); }); @@ -430,36 +430,36 @@ describe("api access-control wire shapes", () => { it("says a server name is held by a world volume left behind", async () => { const { humanizeError } = await import("./api"); expect(humanizeError({ status: 409, code: "world_volume_exists" })).toBe( - "An earlier server with that name left its world volume behind. Choose another name, or ask the operator to delete the old volume.", + "A world volume from a server of the same name still exists. Use a different name or request an operator to verify and process the existing volume.", ); }); it("says why a user change was refused for the caller's own or the owner account", async () => { const { humanizeError } = await import("./api"); expect(humanizeError({ status: 403, code: "self_protected" })).toBe( - "You can't do that to the account you're signed in with.", + "This operation is not permitted on the signed-in account.", ); expect(humanizeError({ status: 403, code: "owner_protected" })).toBe( - "The owner account can't be demoted, disabled or deleted from the panel. Only the host's break-glass console (sudo felis breakGlass) manages it.", + "The Owner account cannot be demoted, disabled, or deleted from the panel. Manage it through the host recovery console (sudo felis breakGlass).", ); }); it("says what a Minecraft UUID looks like when the typed one is not", () => { expect(humanizeError({ status: 400, code: "bad_mc_uuid" })).toBe( - "That isn't a Minecraft UUID. It looks like 069a79f4-44e9-4726-a5be-fca90e38aaf5, with or without the dashes.", + "Invalid Minecraft UUID format. Example: 069a79f4-44e9-4726-a5be-fca90e38aaf5. Hyphens are optional.", ); }); it("says what a display name may hold when the server refuses one", () => { expect(humanizeError({ status: 400, code: "bad_display_name" })).toBe( - "That display name is invalid — use at most 64 characters, all visible ones or spaces, with no line breaks, tabs or invisible control characters.", + "Invalid display name. Use at most 64 visible characters or spaces, excluding line breaks, tabs, and control characters.", ); }); it("says email codes are off when the install has no mail relay", async () => { const { humanizeError } = await import("./api"); expect(humanizeError({ status: 503, code: "mail_unavailable" })).toBe( - "This server can't send email codes because no mail relay is set up. Sign in with a passkey, or ask the server operator to configure email.", + "Mail delivery is not configured, so verification codes cannot be sent. Sign in with a Passkey or request mail configuration from an administrator.", ); }); @@ -673,7 +673,7 @@ describe("image whitelist and builds wire shapes", () => { it("says an uploads store that did not answer is worth another try", async () => { const { humanizeError } = await import("./api"); expect(humanizeError({ status: 503, code: "uploads_store_unavailable" })).toBe( - "The uploads store did not answer — try again in a moment; what was already sent is kept.", + "Upload storage did not respond. Received content is retained. Retry later.", ); }); @@ -876,7 +876,7 @@ describe("image whitelist and builds wire shapes", () => { fakeFetch({ path: "world/level.dat", content, sha256: "a".repeat(64), content_sha256: hexOf(new Uint8Array([0, 1, 2])) }), ); await expect(api.readServerFile("survival", "world/level.dat")).rejects.toMatchObject({ code: "read_damaged" }); - expect(humanizeError({ code: "read_damaged" })).toMatch(/arrived damaged/); + expect(humanizeError({ code: "read_damaged" })).toMatch(/File transfer verification failed/); }); it("writeServerFile PUTs {content} — an explicit \"\" is a deliberate truncate, not an omitted field", async () => { @@ -992,7 +992,7 @@ describe("path parameters", () => { await expect(api.deleteUser(bad)).rejects.toMatchObject({ code: "bad_path_param" }); } expect(spy).not.toHaveBeenCalled(); - expect(humanizeError({ status: 0, code: "bad_path_param" })).toMatch(/not valid/i); + expect(humanizeError({ status: 0, code: "bad_path_param" })).toMatch(/invalid/i); }); it("does not encode a value twice", async () => { @@ -1041,7 +1041,7 @@ describe("the caller's own sessions", () => { }); it("says a session that is already gone has ended", () => { - expect(humanizeError({ status: 404, code: "session_not_found" })).toBe("That session has already ended."); + expect(humanizeError({ status: 404, code: "session_not_found" })).toBe("The session has ended or does not exist."); }); }); @@ -1091,11 +1091,11 @@ describe("responses that are not the API's JSON", () => { message: "this build's scan kept no sbom: it was too large to keep, or the step that writes it failed", }; expect(humanizeError(err)).toBe( - "This build kept no copy of that scan file: it was too large to keep, or the step that writes it failed.", + "The scan document was not retained for this build. The file may have exceeded the size limit or its generation step may have failed.", ); await i18next.changeLanguage("zh-CN"); try { - expect(humanizeError(err)).toBe("这次构建没有留下这份扫描文件:文件太大没有保存,或者生成它的步骤失败了。"); + expect(humanizeError(err)).toBe("构建未留存此扫描文件,可能由于文件过大或生成步骤失败。"); } finally { await i18next.changeLanguage("en-US"); } @@ -1103,7 +1103,7 @@ describe("responses that are not the API's JSON", () => { it("maps an unknown 5xx code to the unavailable line", () => { expect(humanizeError({ status: 500, code: "unknown", message: "internal error" })).toMatch(/unavailable/i); - expect(humanizeError({ status: 413, code: "error", message: "Payload Too Large" })).toMatch(/larger/i); + expect(humanizeError({ status: 413, code: "error", message: "Payload Too Large" })).toMatch(/exceeds/i); }); it("keeps an internal error's request ID without exposing backend details", async () => { @@ -1381,13 +1381,13 @@ describe("server file manager wire shapes", () => { code: "file_unreadable", }); expect(FakeXHR.last).toBeUndefined(); - expect(humanizeError({ code: "file_unreadable" })).toMatch(/changed, moved or deleted/); + expect(humanizeError({ code: "file_unreadable" })).toMatch(/modified, moved, or deleted/); }); it("the upload refusals over a checksum read as what to do next", () => { - expect(humanizeError({ code: "digest_mismatch" })).toMatch(/changed on its way/); - expect(humanizeError({ code: "digest_required" })).toMatch(/without a checksum/); - expect(humanizeError({ code: "bad_digest" })).toMatch(/checksum was malformed/); + expect(humanizeError({ code: "digest_mismatch" })).toMatch(/File transfer verification failed/); + expect(humanizeError({ code: "digest_required" })).toMatch(/omitted its checksum/); + expect(humanizeError({ code: "bad_digest" })).toMatch(/checksum format is invalid/); }); it("createServerFile PUTs the content with create_only, so nothing already there is replaced", async () => { @@ -1580,9 +1580,9 @@ describe("server file manager wire shapes", () => { }); it("words the upload session codes in the panel's own copy", () => { - expect(humanizeError({ status: 404, code: "upload_not_found", message: "raw" })).toMatch(/^This upload is gone/); - expect(humanizeError({ status: 429, code: "too_many_uploads", message: "raw" })).toMatch(/4 large uploads in progress/); - expect(humanizeError({ status: 0, code: "op_lost", message: "" })).toMatch(/progress can no longer be read/); + expect(humanizeError({ status: 404, code: "upload_not_found", message: "raw" })).toMatch(/^The multipart upload record is unavailable/); + expect(humanizeError({ status: 429, code: "too_many_uploads", message: "raw" })).toMatch(/four concurrent large-file uploads/); + expect(humanizeError({ status: 0, code: "op_lost", message: "" })).toMatch(/Operation progress is unavailable/); }); }); @@ -1679,10 +1679,10 @@ describe("scheduled task wire shapes", () => { // "invalid request"); the panel words them itself so a Chinese UI never shows it. describe("copy for the generic server codes", () => { it("names a missing record, a lost race and a refused format in the UI's words", () => { - expect(humanizeError({ status: 404, code: "not_found", message: "user not found" })).toMatch(/no longer exists/); - expect(humanizeError({ status: 409, code: "conflict", message: "conflict" })).toMatch(/changed in the meantime/); + expect(humanizeError({ status: 404, code: "not_found", message: "user not found" })).toMatch(/does not exist/); + expect(humanizeError({ status: 409, code: "conflict", message: "conflict" })).toMatch(/changed during the operation/); expect(humanizeError({ status: 409, code: "restore_in_progress", message: "restore running" })).toMatch( - /still being restored/, + /being restored to the world/, ); expect(humanizeError({ status: 415, code: "unsupported_media_type", message: "json only" })).toMatch( /format the server does not accept/, @@ -1693,35 +1693,35 @@ describe("copy for the generic server codes", () => { expect(humanizeError({ status: 400, code: "bad_request", message: "mc_uuid is required" })).toBe( "The request was not accepted: mc_uuid is required", ); - expect(humanizeError({ status: 400, code: "bad_request", message: "" })).toBe("Something went wrong."); + expect(humanizeError({ status: 400, code: "bad_request", message: "" })).toBe("The operation failed. Retry later."); }); it("words the file manager's refusals itself", () => { expect(humanizeError({ status: 409, code: "file_exists", message: "something is already at a.txt" })).toBe( - "Something with that name is already there. Pick another name, or rename or delete the one that is there first.", + "A file or folder with this name already exists in the current directory. Use a different name, or rename or delete the existing entry.", ); - expect(humanizeError({ status: 507, code: "upload_staging_full", message: "raw" })).toMatch(/upload space is nearly full/); - expect(humanizeError({ status: 400, code: "upload_incomplete", message: "raw" })).toMatch(/stopped before the whole file arrived/); - expect(humanizeError({ status: 411, code: "length_required", message: "raw" })).toMatch(/did not say how large it is/); + expect(humanizeError({ status: 507, code: "upload_staging_full", message: "raw" })).toMatch(/Upload staging space is insufficient/); + expect(humanizeError({ status: 400, code: "upload_incomplete", message: "raw" })).toMatch(/interrupted before the complete file was transferred/); + expect(humanizeError({ status: 411, code: "length_required", message: "raw" })).toMatch(/omitted the file size/); }); it("words the scheduled tasks' refusals itself, and keeps the reason a schedule was refused", () => { expect(humanizeError({ status: 503, code: "schedules_unavailable", message: "raw" })).toBe( - "Scheduled tasks aren't available right now.", + "Scheduled tasks are currently unavailable.", ); expect(humanizeError({ status: 409, code: "schedule_limit", message: "raw" })).toBe( - "This server already has as many scheduled tasks as it can hold. Delete one first.", + "The server’s scheduled-task limit has been reached. Remove an existing task before retrying.", ); expect(humanizeError({ status: 409, code: "schedule_running", message: "raw" })).toBe( - "This task is running right now. Try again once the run finishes.", + "The task is running. Retry after the current execution completes.", ); expect(humanizeError({ status: 409, code: "schedule_stale", message: "raw" })).toBe( - "The server has a new owner since this task was saved. Save the task again before running it.", + "The server owner changed after the task was saved. Save the task again before execution.", ); expect( humanizeError({ status: 400, code: "bad_schedule", message: "a restart can repeat at most every 60 minutes" }), ).toBe("The task was not saved: a restart can repeat at most every 60 minutes"); - expect(humanizeError({ status: 400, code: "bad_schedule", message: "" })).toBe("Something went wrong."); + expect(humanizeError({ status: 400, code: "bad_schedule", message: "" })).toBe("The operation failed. Retry later."); }); it("reads a full upload store as full, not as an outage", () => { @@ -1731,18 +1731,18 @@ describe("copy for the generic server codes", () => { it("words a failure the panel caught itself from its code", () => { const err = clientError("passkey_no_credential"); expect(err.status).toBe(0); - expect(humanizeError(err)).toBe("The browser returned no passkey. Try again."); + expect(humanizeError(err)).toBe("The browser returned no Passkey credential. Retry."); }); it("words the re-authentication refusals", () => { expect(humanizeError({ status: 403, code: "reauth_required", message: "raw" })).toBe( - "Confirm it's you first: this change needs your Passkey or an email code from the last few minutes.", + "This operation requires recent identity verification. Verify identity again using a Passkey or email code.", ); expect(humanizeError({ status: 403, code: "staff_reauth", message: "raw" })).toBe( "Operator accounts confirm with a Passkey or by signing in again.", ); expect(humanizeError({ status: 400, code: "no_session", message: "raw" })).toBe( - "This only works in a browser signed in to Felis.", + "This operation requires an active Felis browser session.", ); }); }); @@ -1800,16 +1800,16 @@ describe("world export wire shapes", () => { it("words the export refusals itself", () => { expect(humanizeError({ status: 429, code: "export_busy", message: "raw" })).toBe( - "Too many exports are being prepared: one at a time per person, two at a time across the platform, and six an hour per person. Try again in a few minutes.", + "Export limits have been reached: one concurrent request per account, two across the platform, and six per account per hour. Retry after existing tasks complete or rate limits reset.", ); expect(humanizeError({ status: 410, code: "export_expired", message: "raw" })).toBe( - "This download has expired or was already used — start the export again.", + "The download link has expired or has already been used. Export the content again.", ); expect(humanizeError({ status: 409, code: "export_not_ready", message: "raw" })).toBe( - "The download isn't ready yet — wait a moment and try again.", + "Download preparation is incomplete. Retry later.", ); expect(humanizeError({ status: 503, code: "export_unavailable", message: "raw" })).toBe( - "Downloading worlds and backups isn't set up on this deployment — ask an administrator.", + "World and backup downloads are not configured in this deployment. Contact an administrator.", ); }); }); diff --git a/panel/src/pages/Account.test.tsx b/panel/src/pages/Account.test.tsx index 27353a3..e5ee92e 100644 --- a/panel/src/pages/Account.test.tsx +++ b/panel/src/pages/Account.test.tsx @@ -178,7 +178,7 @@ describe("Account passkey delete", () => { expect(await screen.findByText("Firefox on Windows")).toBeTruthy(); await userEvent.click(await screen.findByRole("button", deleteButton("Laptop"))); - expect(within(screen.getByRole("dialog")).getByText(/Your other devices are signed out too\./)).toBeTruthy(); + expect(within(screen.getByRole("dialog")).getByText(/signs out sessions on other devices/)).toBeTruthy(); await userEvent.click(within(screen.getByRole("dialog")).getByRole("button", { name: t("account:passkey_delete_confirm") })); await waitFor(() => expect(screen.queryByText("Firefox on Windows")).toBeNull()); diff --git a/panel/src/pages/AccountReauth.test.tsx b/panel/src/pages/AccountReauth.test.tsx index 89f2e78..7bc61a8 100644 --- a/panel/src/pages/AccountReauth.test.tsx +++ b/panel/src/pages/AccountReauth.test.tsx @@ -82,11 +82,11 @@ function renderAccount() { ); } -const checkDialog = () => screen.findByRole("dialog", { name: "Confirm it's you" }); +const checkDialog = () => screen.findByRole("dialog", { name: "Account identity verification" }); async function askToDeleteLaptop() { await userEvent.click(await screen.findByRole("button", { name: "Delete passkey “Laptop”" })); - const confirmDelete = screen.getByRole("dialog", { name: "Delete this passkey?" }); + const confirmDelete = screen.getByRole("dialog", { name: "Delete this Passkey?" }); await userEvent.click(within(confirmDelete).getByRole("button", { name: "Delete" })); } @@ -140,8 +140,8 @@ describe("Account re-authentication", () => { const dialog = await checkDialog(); await userEvent.click(within(dialog).getByRole("button", { name: "Cancel" })); - await waitFor(() => expect(screen.queryByRole("dialog", { name: "Confirm it's you" })).toBeNull()); - const confirmDelete = screen.getByRole("dialog", { name: "Delete this passkey?" }); + await waitFor(() => expect(screen.queryByRole("dialog", { name: "Account identity verification" })).toBeNull()); + const confirmDelete = screen.getByRole("dialog", { name: "Delete this Passkey?" }); expect(within(confirmDelete).queryByRole("alert")).toBeNull(); expect(mocks.passkeyDelete).toHaveBeenCalledTimes(1); expect(mocks.passkeyList).toHaveBeenCalledTimes(1); @@ -159,7 +159,7 @@ describe("Account re-authentication", () => { await proveByEmail(dialog); const alert = await within(dialog).findByRole("alert"); - expect(alert.textContent).toBe("That code is invalid or expired — request a fresh one and try again."); + expect(alert.textContent).toBe("The binding code is invalid or expired. Obtain a new code."); expect(mocks.passkeyDelete).toHaveBeenCalledTimes(1); }); @@ -249,7 +249,7 @@ describe("Account re-authentication", () => { await proveByEmail(await checkDialog()); const status = await within(screen.getByRole("dialog", { name: "Add Passkey" })).findByRole("status"); - expect(status.textContent).toBe("Confirmed. Press Continue to add the passkey."); + expect(status.textContent).toBe("Identity verified. Select Continue to add a Passkey."); expect(mocks.passkeyRegisterBegin).toHaveBeenCalledTimes(1); expect(mocks.credentialsCreate).not.toHaveBeenCalled(); expect(within(screen.getByRole("dialog", { name: "Add Passkey" })).getByRole("button", { name: "Continue" })).toHaveProperty( @@ -268,7 +268,7 @@ describe("Account re-authentication", () => { await userEvent.click(await screen.findByRole("button", { name: "Change email" })); expect( screen.getByText( - "Enter the new address and we'll send it a code. Once it's verified, sign-in codes go there, the old address gets a notice, and your other devices are signed out.", + "Enter the new email address to receive a verification code. After verification, sign-in codes will be sent to the new address, a change notification will be sent to the previous address, and sessions on other devices will be signed out.", ), ).toBeTruthy(); const address = screen.getByPlaceholderText("user@example.com"); diff --git a/panel/src/pages/AccountSessions.test.tsx b/panel/src/pages/AccountSessions.test.tsx index 665b391..34595e1 100644 --- a/panel/src/pages/AccountSessions.test.tsx +++ b/panel/src/pages/AccountSessions.test.tsx @@ -89,18 +89,18 @@ describe("AccountSessionsCard", () => { expect(within(phone).queryByText("This device")).toBeNull(); expect(within(phone).getByText("Active 20 hours ago")).toBeTruthy(); expect(within(phone).getByText("203.0.113.24")).toBeTruthy(); - expect(within(phone).getByRole("button", { name: "Sign out Safari on iPhone" })).toBeTruthy(); + expect(within(phone).getByRole("button", { name: "Sign out the session on Safari on iPhone" })).toBeTruthy(); // Seen a minute ago: the server records activity once a minute, so that is now. expect(within(row("Edge on Windows")).getByText("Active now")).toBeTruthy(); - expect(screen.queryByText("Operator sessions sign out on their own after 30 minutes without activity.")).toBeNull(); + expect(screen.queryByText("Operations account sessions expire after 30 minutes of inactivity.")).toBeNull(); }); it("tells an operator that their sessions idle out", async () => { mocks.listMySessions.mockResolvedValue([mac]); renderCard({ staff: true }); expect( - await screen.findByText("Operator sessions sign out on their own after 30 minutes without activity."), + await screen.findByText("Operations account sessions expire after 30 minutes of inactivity."), ).toBeTruthy(); }); @@ -109,7 +109,7 @@ describe("AccountSessionsCard", () => { mocks.revokeMySession.mockResolvedValue({ ok: true, signed_out: false }); renderCard(); - await userEvent.click(await screen.findByRole("button", { name: "Sign out Safari on iPhone" })); + await userEvent.click(await screen.findByRole("button", { name: "Sign out the session on Safari on iPhone" })); expect(mocks.revokeMySession).toHaveBeenCalledWith("h-iphone"); expect((await screen.findByRole("status")).textContent).toBe("Signed out Safari on iPhone."); @@ -122,7 +122,7 @@ describe("AccountSessionsCard", () => { mocks.revokeMySession.mockRejectedValue({ status: 404, code: "session_not_found", message: "gone" }); renderCard(); - await userEvent.click(await screen.findByRole("button", { name: "Sign out Safari on iPhone" })); + await userEvent.click(await screen.findByRole("button", { name: "Sign out the session on Safari on iPhone" })); expect((await screen.findByRole("status")).textContent).toBe("Signed out Safari on iPhone."); expect(screen.queryByRole("alert")).toBeNull(); @@ -134,10 +134,10 @@ describe("AccountSessionsCard", () => { mocks.revokeMySession.mockRejectedValue({ status: 403, code: "self_protected", message: "no" }); renderCard(); - await userEvent.click(await screen.findByRole("button", { name: "Sign out Safari on iPhone" })); + await userEvent.click(await screen.findByRole("button", { name: "Sign out the session on Safari on iPhone" })); expect((await screen.findByRole("alert")).textContent).toBe( - "You can't do that to the account you're signed in with.", + "This operation is not permitted on the signed-in account.", ); expect(screen.queryByRole("status")).toBeNull(); await waitFor(() => expect(mocks.listMySessions).toHaveBeenCalledTimes(2)); @@ -151,13 +151,13 @@ describe("AccountSessionsCard", () => { await userEvent.click(await screen.findByRole("button", { name: "Sign out other devices" })); const dialog = screen.getByRole("dialog"); - expect(within(dialog).getByText("Sign out every other device?")).toBeTruthy(); + expect(within(dialog).getByText("Sign out all other device sessions?")).toBeTruthy(); expect(mocks.revokeMyOtherSessions).not.toHaveBeenCalled(); await userEvent.click(within(dialog).getByRole("button", { name: "Sign out other devices" })); expect(mocks.revokeMyOtherSessions).toHaveBeenCalledTimes(1); - expect((await screen.findByRole("status")).textContent).toBe("Signed out 2 other devices."); + expect((await screen.findByRole("status")).textContent).toBe("Sessions on 2 other devices have been signed out."); await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull()); await waitFor(() => expect(screen.queryByText("Safari on iPhone")).toBeNull()); expect(screen.getByText("Chrome on macOS")).toBeTruthy(); @@ -171,7 +171,7 @@ describe("AccountSessionsCard", () => { await userEvent.click(await screen.findByRole("button", { name: "Sign out other devices" })); await userEvent.click(within(screen.getByRole("dialog")).getByRole("button", { name: "Sign out other devices" })); - expect((await screen.findByRole("status")).textContent).toBe("Signed out 1 other device."); + expect((await screen.findByRole("status")).textContent).toBe("The session on 1 other device has been signed out."); }); it("has nothing to sign out elsewhere when this is the only device", async () => { diff --git a/panel/src/pages/Login.test.tsx b/panel/src/pages/Login.test.tsx index 3ccda54..fc4bb60 100644 --- a/panel/src/pages/Login.test.tsx +++ b/panel/src/pages/Login.test.tsx @@ -112,13 +112,13 @@ describe("Login", () => { await renderLogin(); await userEvent.click(screen.getByRole("button", { name: t("auth:passkey_btn") })); - expect((await screen.findByRole("alert")).textContent).toBe("The browser returned no passkey. Try again."); + expect((await screen.findByRole("alert")).textContent).toBe("The browser returned no Passkey credential. Retry."); expect(calls.authPasskeyDiscoverableFinish).not.toHaveBeenCalled(); await userEvent.type(screen.getByLabelText(t("auth:email_address")), "a@b.c"); await userEvent.click(screen.getByRole("button", { name: t("auth:passkey_btn") })); await vi.waitFor(() => expect(calls.authPasskeyLoginBegin).toHaveBeenCalledWith("a@b.c")); - expect((await screen.findByRole("alert")).textContent).toBe("The browser returned no passkey. Try again."); + expect((await screen.findByRole("alert")).textContent).toBe("The browser returned no Passkey credential. Retry."); expect(calls.authPasskeyLoginFinish).not.toHaveBeenCalled(); }); }); diff --git a/panel/src/pages/MySubmissionsPage.test.tsx b/panel/src/pages/MySubmissionsPage.test.tsx index c680906..9407766 100644 --- a/panel/src/pages/MySubmissionsPage.test.tsx +++ b/panel/src/pages/MySubmissionsPage.test.tsx @@ -162,12 +162,12 @@ describe("MySubmissionsPage", () => { expect( await screen.findByText( - "Can't reach Felis: the network is down, or your Cloudflare Access sign-in expired. Reload the page to sign in again.", + "Unable to connect to Felis. The network may be unavailable or the Cloudflare Access session may have expired. Check the network. If the Access session has expired, reload the page and sign in again.", ), ).toBeTruthy(); expect( screen.getByText( - "“Skyblock Pack” is saved and 2 KB of its build context is on the server. Submit again to continue from there.", + "“Skyblock Pack” is saved and the server has received 2 KB of its build context. Resubmission resumes from the confirmed offset.", ), ).toBeTruthy(); expect((screen.getByLabelText(/Display Name/) as HTMLInputElement).disabled).toBe(true); @@ -197,7 +197,7 @@ describe("MySubmissionsPage", () => { const other = new File(["\x1f\x8bother"], "other.tar.gz", { type: "application/gzip" }); await userEvent.upload(screen.getByLabelText(/Build Context/), other); - expect(screen.getByText("“Skyblock Pack” is saved. The file you picked uploads from the start.")).toBeTruthy(); + expect(screen.getByText("“Skyblock Pack” is saved. A newly selected file is uploaded from the beginning.")).toBeTruthy(); upload.uploadContext.mockResolvedValueOnce(undefined); await userEvent.click(screen.getByRole("button", { name: "Submit" })); await vi.waitFor(() => expect(upload.uploadContext).toHaveBeenCalledTimes(2)); @@ -222,7 +222,7 @@ describe("MySubmissionsPage", () => { expect( await screen.findByText( - "“Skyblock Pack” is saved and 1 KB of its build context is on the server. Submit again to continue from there.", + "“Skyblock Pack” is saved and the server has received 1 KB of its build context. Resubmission resumes from the confirmed offset.", ), ).toBeTruthy(); expect(screen.queryByRole("alert")).toBeNull(); diff --git a/panel/src/pages/ServerBackups.test.tsx b/panel/src/pages/ServerBackups.test.tsx index e26b7ca..df7a913 100644 --- a/panel/src/pages/ServerBackups.test.tsx +++ b/panel/src/pages/ServerBackups.test.tsx @@ -228,7 +228,7 @@ describe("ServerBackups back up now", () => { renderPage(); await screen.findByText("Page 1 of 3"); await vi.waitFor(() => expect(backUp().disabled).toBe(true)); - expect(backUp().title).toBe("A backup, restore or world export is already running; back up once it finishes."); + expect(backUp().title).toBe("A backup, restoration, or world export is in progress. Wait for the current operation to complete before starting a backup."); }); it("keeps the page when a reread fails, and says the status may be stale", async () => { diff --git a/panel/src/pages/ServerConsole.test.tsx b/panel/src/pages/ServerConsole.test.tsx index 2d5bb96..b59b72a 100644 --- a/panel/src/pages/ServerConsole.test.tsx +++ b/panel/src/pages/ServerConsole.test.tsx @@ -59,7 +59,7 @@ describe("ServerConsole host recovery", () => { tier.isOwner = true; calls.status.mockRejectedValue(new Error("API offline")); renderConsole(); - expect(await screen.findByText("Control API unreachable? Show host recovery steps")).toBeTruthy(); + expect(await screen.findByText("Host emergency shutdown procedure")).toBeTruthy(); expect(screen.getByText(/kubectl patch minecraftserver survival/)).toBeTruthy(); expect(screen.queryByRole("button", { name: "Stop" })).toBeNull(); tier.isOwner = false; @@ -235,7 +235,7 @@ describe("ServerConsole retirement", () => { calls.status.mockResolvedValue(stopped({ retiring: { requested_at: new Date().toISOString(), delete: false } })); renderConsole(); - expect(await screen.findByText("This server has been given up")).toBeTruthy(); + expect(await screen.findByText("Server pending reclamation")).toBeTruthy(); expect(screen.getByRole("button", { name: "Cancel request" })).toBeTruthy(); expect(screen.getByText("Given up")).toBeTruthy(); expect(screen.queryByRole("button", { name: "Wake" })).toBeNull(); @@ -350,13 +350,13 @@ describe("startup diagnostics and stale state", () => { expect(await screen.findByText("Startup issue")).toBeTruthy(); expect(screen.getByText(/OOMKilled.*137/)).toBeTruthy(); expect(screen.queryByText("Starting", { exact: true })).toBeNull(); - expect(screen.getByText("Logs are not available yet")).toBeTruthy(); + expect(screen.getByText("Logs unavailable")).toBeTruthy(); }); it("explains scheduling failure before logs exist", async () => { calls.status.mockResolvedValue(status({ phase: "Starting", desiredState: "Running", startup: { stage: "scheduling", reason: "Unschedulable", message: "0/1 nodes: Insufficient memory", startedAt: new Date().toISOString(), logsAvailable: false } })); renderConsole(); expect(await screen.findByText("Waiting for resources and scheduling")).toBeTruthy(); - expect(screen.getByText(/Not enough schedulable memory/)).toBeTruthy(); + expect(screen.getByText(/Insufficient schedulable memory/)).toBeTruthy(); expect(screen.queryByTestId("log-stream")).toBeNull(); expect(screen.queryByRole("button", { name: "Delete server" })).toBeNull(); expect(screen.getByRole("button", { name: "More actions" })).toBeTruthy(); diff --git a/panel/src/pages/ServerLuckPerms.test.tsx b/panel/src/pages/ServerLuckPerms.test.tsx index 35a4285..6203087 100644 --- a/panel/src/pages/ServerLuckPerms.test.tsx +++ b/panel/src/pages/ServerLuckPerms.test.tsx @@ -66,7 +66,7 @@ describe("ServerLuckPerms without LuckPerms", () => { await userEvent.type(await screen.findByPlaceholderText("Steve"), "Alex{Enter}"); const alert = await screen.findByRole("alert"); - expect(alert.textContent).toMatch(/LuckPerms isn't installed/); + expect(alert.textContent).toMatch(/LuckPerms is not installed/); expect(calls.accessLuckPermsInfo).toHaveBeenCalledWith("lobby", "Alex"); await userEvent.type(screen.getByLabelText("Group Name"), "vip"); @@ -74,7 +74,7 @@ describe("ServerLuckPerms without LuckPerms", () => { expect(calls.accessGroup).toHaveBeenCalledWith("lobby", "add", "Alex", "vip"); const alerts = await screen.findAllByRole("alert"); expect(alerts).toHaveLength(2); - expect(alerts.every((a) => /LuckPerms isn't installed/.test(a.textContent ?? ""))).toBe(true); + expect(alerts.every((a) => /LuckPerms is not installed/.test(a.textContent ?? ""))).toBe(true); expect(screen.queryByText(/success/i)).toBeNull(); }); }); @@ -126,12 +126,12 @@ describe("ServerLuckPerms when LuckPerms does not answer", () => { async function lookUpAlex() { renderPage(); await userEvent.type(await screen.findByPlaceholderText("Steve"), "Alex{Enter}"); - await screen.findByText(/Couldn't read this player's groups/); + await screen.findByText(/The player’s current groups could not be read/); } it("says the lists are unread, never that they are empty", async () => { await lookUpAlex(); - expect(screen.getByText(/Couldn't read this player's permission nodes/)).toBeTruthy(); + expect(screen.getByText(/The player’s current permission nodes could not be read/)).toBeTruthy(); expect(screen.queryByText("No parent groups assigned")).toBeNull(); expect(screen.queryByText("No explicit permission nodes assigned")).toBeNull(); }); @@ -169,7 +169,7 @@ describe("ServerLuckPerms when LuckPerms does not answer", () => { await lookUpAlex(); await userEvent.type(screen.getByLabelText("Permission Node"), "essentials.fly"); await userEvent.click(screen.getByRole("button", { name: /^remove permission$/i })); - expect((await screen.findByRole("alert")).textContent).toMatch(/LuckPerms isn't installed/); + expect((await screen.findByRole("alert")).textContent).toMatch(/LuckPerms is not installed/); expect((screen.getByLabelText("Permission Node") as HTMLInputElement).value).toBe("essentials.fly"); }); @@ -179,7 +179,7 @@ describe("ServerLuckPerms when LuckPerms does not answer", () => { await userEvent.type(await screen.findByPlaceholderText("Steve"), "Alex{Enter}"); expect(await screen.findByText("No parent groups assigned")).toBeTruthy(); expect(screen.getByText("No explicit permission nodes assigned")).toBeTruthy(); - expect(screen.queryByText(/Couldn't read this player's/)).toBeNull(); + expect(screen.queryByText(/The player’s current/)).toBeNull(); }); }); @@ -259,17 +259,17 @@ describe("ServerLuckPerms reverting a permission change", () => { reason: humanizeError({ status: 502, code: "rcon_unavailable", message: "console down" }), }), ); - expect(alert.textContent?.startsWith("Couldn't revert: ")).toBe(true); + expect(alert.textContent?.startsWith("Unable to revert: ")).toBe(true); expect(screen.getByRole("button", { name: "Revert" })).toBeTruthy(); }); it("offers no revert for a removal whose value was never read", async () => { calls.accessLuckPermsInfo.mockResolvedValue({ player: "Alex", groups: [], permissions: [], output: "" }); await lookUpAlex(); - await screen.findByText(/Couldn't read this player's permission nodes/); + await screen.findByText(/The player’s current permission nodes could not be read/); await typeNode("essentials.fly", "world_nether"); await userEvent.click(screen.getByRole("button", { name: /^remove permission$/i })); - expect(await screen.findByText("Can't revert: value unknown")).toBeTruthy(); + expect(await screen.findByText("Cannot revert: previous value unknown")).toBeTruthy(); expect(screen.getByText("- essentials.fly [world_nether]")).toBeTruthy(); expect(screen.queryByRole("button", { name: "Revert" })).toBeNull(); @@ -290,7 +290,7 @@ describe("ServerLuckPerms reverting a permission change", () => { await typeNode("essentials.fly", "world_nether"); await userEvent.click(screen.getByRole("button", { name: /^remove permission$/i })); expect(calls.accessPermission).toHaveBeenLastCalledWith("lobby", "unset", "Alex", "essentials.fly", undefined, "world_nether"); - expect(await screen.findByText("Can't revert: value unknown")).toBeTruthy(); + expect(await screen.findByText("Cannot revert: previous value unknown")).toBeTruthy(); expect(screen.getByText("- essentials.fly [world_nether]")).toBeTruthy(); } diff --git a/panel/src/pages/ServerSchedules.test.tsx b/panel/src/pages/ServerSchedules.test.tsx index 003037c..beef509 100644 --- a/panel/src/pages/ServerSchedules.test.tsx +++ b/panel/src/pages/ServerSchedules.test.tsx @@ -169,9 +169,9 @@ describe("ServerSchedules", () => { const backup = await rowOf("Backup"); expect(within(backup).getByText("Mon, Wed, Fri at 05:30")).toBeTruthy(); - expect(within(backup).getByText("Off: it runs only when you press Run now")).toBeTruthy(); + expect(within(backup).getByText("Disabled; runs only when Run now is selected")).toBeTruthy(); expect(within(backup).getByText("Off")).toBeTruthy(); - expect(within(backup).getByText("Hasn't run yet")).toBeTruthy(); + expect(within(backup).getByText("Not executed")).toBeTruthy(); expect(screen.getByText("3 of 20 tasks")).toBeTruthy(); }); @@ -210,7 +210,7 @@ describe("ServerSchedules", () => { expect(within(nightly).getByText("Skipped")).toBeTruthy(); expect( within(nightly).getByText( - "The server changed owner, so this task switched itself off. Check it, then save it or switch it on to use it again.", + "The server owner has changed and the task was disabled automatically. Verify the configuration before saving or enabling it again.", ), ).toBeTruthy(); expect(within(await rowOf("Who")).getByText("Reply: There are 0 of a max of 20 players online")).toBeTruthy(); @@ -359,7 +359,7 @@ describe("ServerSchedules", () => { const dialog = await openCreate(); await userEvent.click(within(dialog).getByRole("button", { name: "Create" })); expect((await within(dialog).findByRole("alert")).textContent).toBe( - "This server already has as many scheduled tasks as it can hold. Delete one first.", + "The server’s scheduled-task limit has been reached. Remove an existing task before retrying.", ); expect(screen.getByRole("dialog")).toBe(dialog); expect((within(dialog).getByRole("button", { name: "Create" }) as HTMLButtonElement).disabled).toBe(false); @@ -370,7 +370,7 @@ describe("ServerSchedules", () => { renderPage(); await userEvent.click(await screen.findByRole("button", { name: "Back up every night at 05:00" })); const dialog = await screen.findByRole("dialog"); - expect(within(dialog).getByText(/A backup of a running server stops it/)).toBeTruthy(); + expect(within(dialog).getByText(/Backing up a running server first stops it/)).toBeTruthy(); await userEvent.click(within(dialog).getByRole("button", { name: "Create" })); expect(calls.createSchedule).toHaveBeenCalledWith("survival", { label: "", @@ -483,11 +483,11 @@ describe("ServerSchedules", () => { const busy = await rowOf("Busy"); expect(within(busy).getByText("Stopping the server…")).toBeTruthy(); expect(within(busy).getByText("Started 5 minutes ago")).toBeTruthy(); - expect(within(busy).queryByText("Hasn't run yet")).toBeNull(); + expect(within(busy).queryByText("Not executed")).toBeNull(); for (const name of ["Run now", "Edit", "Delete"]) { const button = within(busy).getByRole("button", { name }) as HTMLButtonElement; expect(button.disabled).toBe(true); - expect(button.parentElement?.title).toBe("This task is running. Wait for the run to finish."); + expect(button.parentElement?.title).toBe("The task is running. Wait for the current execution to complete."); } expect((within(busy).getByRole("switch") as HTMLButtonElement).disabled).toBe(true); @@ -504,7 +504,7 @@ describe("ServerSchedules", () => { const add = (await screen.findByRole("button", { name: "New task" })) as HTMLButtonElement; await rowOf("One"); expect(add.disabled).toBe(true); - expect(add.parentElement?.title).toBe("A server holds at most 2 tasks. Delete one to add another."); + expect(add.parentElement?.title).toBe("The scheduled-task limit of 2 has been reached. Remove an existing task before adding another."); }); it("says plainly when felis-api has no schedule store, and treats other failures as errors", async () => { diff --git a/panel/src/pages/admin/AuthSourcesPage.test.tsx b/panel/src/pages/admin/AuthSourcesPage.test.tsx index add9512..9d233bf 100644 --- a/panel/src/pages/admin/AuthSourcesPage.test.tsx +++ b/panel/src/pages/admin/AuthSourcesPage.test.tsx @@ -129,7 +129,7 @@ describe("AuthSourcesPage", () => { calls.getAuthSources.mockRejectedValueOnce({ status: 503, code: "auth_sources_unavailable" }); page(); await screen.findByRole("alert"); expect(field("Permanent source ID").disabled).toBe(true); - await userEvent.click(button("Try again")); + await userEvent.click(button("Retry")); await ready(); expect(field("Permanent source ID").value).toBe("littleskin"); }); diff --git a/panel/src/pages/admin/BuildScanPanel.test.tsx b/panel/src/pages/admin/BuildScanPanel.test.tsx index 875138a..4adce8d 100644 --- a/panel/src/pages/admin/BuildScanPanel.test.tsx +++ b/panel/src/pages/admin/BuildScanPanel.test.tsx @@ -100,7 +100,7 @@ describe("BuildScanPanel", () => { "Listed in [registry] scan_accept as a known risk, so it never blocks.", ); const unfixed = within(panel).getByText("CVE-2024-0002").closest("li")!; - expect(unfixed.textContent).toBe("CVE-2024-0002HIGHopenssl3.0.13 → no fix yetusr/lib/libssl.so.3"); + expect(unfixed.textContent).toBe("CVE-2024-0002HIGHopenssl3.0.13 → No fix availableusr/lib/libssl.so.3"); expect(within(panel).getByText("Showing 4 of 6 findings. The full report lists every one.")).toBeTruthy(); }); @@ -112,7 +112,7 @@ describe("BuildScanPanel", () => { const sbom = within(panel).getByRole("button", { name: "SBOM" }) as HTMLButtonElement; expect(sbom.disabled).toBe(true); expect(sbom.getAttribute("title")).toBe( - "Not kept with this build: the file was too large, or the step that writes it failed.", + "The file was not retained for this build. It may have exceeded the size limit or its generation step may have failed.", ); await userEvent.click(within(panel).getByRole("button", { name: "Trivy report" })); expect(calls.downloadBuildScanDocument).toHaveBeenCalledWith("bld-7", "report"); @@ -124,7 +124,7 @@ describe("BuildScanPanel", () => { }); await userEvent.click(within(panel).getByRole("button", { name: "Trivy report" })); expect((await within(panel).findByRole("alert")).textContent).toBe( - "Couldn't download: This build kept no copy of that scan file: it was too large to keep, or the step that writes it failed.", + "Unable to download: The scan document was not retained for this build. The file may have exceeded the size limit or its generation step may have failed.", ); }); @@ -135,7 +135,7 @@ describe("BuildScanPanel", () => { expect(within(panel).getByText("Passed")).toBeTruthy(); expect(panel.textContent).toContain("· 1 package · Blocks on HIGH, including vulnerabilities with no fixed release"); expect(panel.textContent).not.toContain("accepted"); - expect(within(panel).getByText("Trivy found nothing to report in this image.")).toBeTruthy(); + expect(within(panel).getByText("Trivy detected no findings in this image.")).toBeTruthy(); expect(within(panel).queryByText("Found in")).toBeNull(); calls.downloadBuildScanDocument.mockResolvedValue(undefined); await userEvent.click(within(panel).getByRole("button", { name: "SBOM" })); @@ -147,7 +147,7 @@ describe("BuildScanPanel", () => { const { unmount } = render(); expect( await screen.findByText( - "No security scan was kept for this build. It stopped before the scan step, or it ran before builds kept their scans.", + "This build has no retained security scan record. It may have ended before scanning or been created before scan retention was enabled.", ), ).toBeTruthy(); unmount(); @@ -156,9 +156,9 @@ describe("BuildScanPanel", () => { calls.getBuildScan.mockResolvedValueOnce(BLOCKED); render(); expect((await screen.findByRole("alert")).textContent).toBe( - "Couldn't load the security scan: The operation failed because of an internal server error. Ask an admin to check the logs.", + "Unable to load the security scan: The operation failed because of an internal server error. Ask an admin to check the logs.", ); - await userEvent.click(screen.getByRole("button", { name: "Try again" })); + await userEvent.click(screen.getByRole("button", { name: "Retry" })); expect(await screen.findByRole("region", { name: "Security scan" })).toBeTruthy(); expect(screen.queryByRole("alert")).toBeNull(); }); diff --git a/panel/src/pages/admin/DBBackupCard.test.tsx b/panel/src/pages/admin/DBBackupCard.test.tsx index c33450d..93d9237 100644 --- a/panel/src/pages/admin/DBBackupCard.test.tsx +++ b/panel/src/pages/admin/DBBackupCard.test.tsx @@ -54,7 +54,7 @@ describe("DBBackupCard", () => { expect(await screen.findByText("Incomplete")).toBeTruthy(); expect(screen.queryByText("Healthy")).toBeNull(); expect(screen.getByText("The newest backup lacks the server definitions")).toBeTruthy(); - expect(screen.getByText(/Restoring from it brings back accounts and the database, but no servers/)).toBeTruthy(); + expect(screen.getByText(/Restoring the database alone cannot recreate servers/)).toBeTruthy(); expect(screen.getByText(REASON)).toBeTruthy(); expect(screen.getByTitle("sudo k3s kubectl get minecraftservers -A")).toBeTruthy(); expect(screen.getByTitle("sudo felis db backup")).toBeTruthy(); @@ -74,7 +74,7 @@ describe("DBBackupCard", () => { render(); expect(await screen.findByText("Overdue")).toBeTruthy(); expect(screen.getByText("The newest backup is more than 26 hours old")).toBeTruthy(); - expect(screen.getByText(/find out why the timer missed it:$/)).toBeTruthy(); + expect(screen.getByText(/inspect the timer logs:$/)).toBeTruthy(); expect(screen.getByText("yesterday").className).toBe("text-destructive"); expect(screen.getByTitle("sudo systemctl start felis-db-backup.service")).toBeTruthy(); }); @@ -87,7 +87,7 @@ describe("DBBackupCard", () => { render(); expect(await screen.findByText("Overdue")).toBeTruthy(); expect(screen.getByText("The daily backup last completed 2 days ago, past the 26-hour limit")).toBeTruthy(); - expect(screen.getByText(/^The newer backup above was taken by hand or by another job and can be restored from/)).toBeTruthy(); + expect(screen.getByText(/^The newer backup was created manually or by another task and is available for restoration/)).toBeTruthy(); // The manual bundle itself is fresh: its age stays out of the alarm. expect(screen.getByText("3 hours ago").className).toBe(""); expect(screen.getByTitle("sudo systemctl start felis-db-backup.service")).toBeTruthy(); @@ -96,8 +96,8 @@ describe("DBBackupCard", () => { it("says the daily timer never completed when only other kinds exist", async () => { calls.getDBBackup.mockResolvedValue(status({ label: "pre-migrate", name: "felis-db-20260926T101500Z-pre-migrate.tar" }, true)); render(); - expect(await screen.findByText("The daily timer has not completed a backup yet")).toBeTruthy(); - expect(screen.getByText(/^The newer backup above was taken by hand/)).toBeTruthy(); + expect(await screen.findByText("The daily timer has not completed a backup")).toBeTruthy(); + expect(screen.getByText(/^The newer backup was created manually/)).toBeTruthy(); }); it("names an off-site copy snapshot by its kind", async () => { diff --git a/panel/src/pages/admin/ImageBuildPage.test.tsx b/panel/src/pages/admin/ImageBuildPage.test.tsx index c5d5cd7..2a892f3 100644 --- a/panel/src/pages/admin/ImageBuildPage.test.tsx +++ b/panel/src/pages/admin/ImageBuildPage.test.tsx @@ -229,7 +229,7 @@ describe("ImageBuildPage cancel", () => { calls.listBuilds.mockRejectedValue({ status: 409, code: "test", message: "database is away" }); await userEvent.click(within(dialog).getByRole("button", { name: t("admin:cancel_build_confirm") })); - expect((await screen.findByRole("alert")).textContent).toBe("Couldn't refresh the build list: database is away"); + expect((await screen.findByRole("alert")).textContent).toBe("Unable to refresh the build list: database is away"); expect(screen.getByText(RUNNING.image_ref)).toBeTruthy(); expect(screen.getByText(MINE.image_ref)).toBeTruthy(); }); diff --git a/panel/src/pages/admin/LobbyPage.test.tsx b/panel/src/pages/admin/LobbyPage.test.tsx index e72fc89..e7492b3 100644 --- a/panel/src/pages/admin/LobbyPage.test.tsx +++ b/panel/src/pages/admin/LobbyPage.test.tsx @@ -137,7 +137,7 @@ describe("LobbyPage", () => { await userEvent.click(screen.getByRole("button", { name: "Save settings" })); expect(calls.writeServerFile).not.toHaveBeenCalled(); calls.readServerFile.mockResolvedValue({ content: btoa('{"menuTitleEn":"Recovered title"}'), sha256: "hash" }); - await userEvent.click(screen.getByRole("button", { name: "Try again" })); + await userEvent.click(screen.getByRole("button", { name: "Retry" })); expect((await readyField("English menu title")).value).toBe("Recovered title"); expect(screen.queryByRole("alert")).toBeNull(); }); diff --git a/panel/src/pages/admin/UpdateReportCard.test.tsx b/panel/src/pages/admin/UpdateReportCard.test.tsx index 910cdf5..e3821f9 100644 --- a/panel/src/pages/admin/UpdateReportCard.test.tsx +++ b/panel/src/pages/admin/UpdateReportCard.test.tsx @@ -54,7 +54,7 @@ describe("UpdateReportCard", () => { expect(rowText("k3s")).toBe("k3sInstalled: v1.36.2+k3s1Newer release: —Up to date"); expect(rowText("jre")).toBe("jreInstalled: 21.0.8+9Newer release: —Feed unreachableadoptium: context deadline exceeded"); expect(rowText("postgresql")).toBe( - "postgresqlInstalled: —Newer release: —Version unreadablepsql: not foundPostgreSQL 13 is past its end of life", + "postgresqlInstalled: —Newer release: —Version read failedpsql: not foundPostgreSQL 13 is past its end of life", ); expect(screen.getByTitle("sudo felis update --panel --cloudflared").textContent).toBe("sudo felis update --panel --cloudflared"); expect(screen.queryByText("sudo felis update --record")).toBeNull(); @@ -76,7 +76,7 @@ describe("UpdateReportCard", () => { calls.getUpdateReport.mockResolvedValue({ report: null, stale: true, max_age_seconds: 93600 }); render(); expect(await screen.findByText("Never checked")).toBeTruthy(); - expect(screen.getByText("No version check has been recorded yet")).toBeTruthy(); + expect(screen.getByText("No version check records")).toBeTruthy(); expect(screen.getByTitle("sudo felis update --record")).toBeTruthy(); expect(screen.getByTitle("journalctl -u felis-update-check -n 50 --no-pager")).toBeTruthy(); }); diff --git a/panel/src/pages/admin/UserDetailPage.test.tsx b/panel/src/pages/admin/UserDetailPage.test.tsx index c1c2380..abce07b 100644 --- a/panel/src/pages/admin/UserDetailPage.test.tsx +++ b/panel/src/pages/admin/UserDetailPage.test.tsx @@ -140,7 +140,7 @@ describe("UserDetailPage", () => { expect(calls.revokeUserSession).toHaveBeenCalledWith("u-1", "h-phone"); await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull()); await waitFor(() => expect(screen.queryByText("Chrome on Android")).toBeNull()); - expect(screen.queryByText("That session has already ended.")).toBeNull(); + expect(screen.queryByText("The session has ended or does not exist.")).toBeNull(); }); it("shows why a revoke failed for any other reason", async () => { @@ -178,9 +178,9 @@ describe("UserDetailPage", () => { }); describe("danger zone for accounts the server protects", () => { - const SELF_REASON = "You can't disable or delete the account you're signed in with."; + const SELF_REASON = "The signed-in account cannot be disabled or deleted."; const OWNER_REASON = - "The owner account can't be disabled or deleted from the panel. Only the host's break-glass console (sudo felis breakGlass) manages it."; + "The Owner account cannot be disabled or deleted from the panel. Manage it through the host recovery console (sudo felis breakGlass)."; const button = (name: string) => screen.getByRole("button", { name }) as HTMLButtonElement; const describedBy = (b: HTMLElement) => @@ -197,7 +197,7 @@ describe("UserDetailPage", () => { expect(button(name).parentElement?.getAttribute("title")).toBe(SELF_REASON); } expect(button("Unbind passkeys").disabled).toBe(false); - expect(screen.getByText("You can't change your own role.")).toBeTruthy(); + expect(screen.getByText("The current account’s role cannot be changed.")).toBeTruthy(); expect(screen.queryByRole("combobox")).toBeNull(); }); @@ -212,7 +212,7 @@ describe("UserDetailPage", () => { expect(describedBy(button("Delete User"))).toBe(OWNER_REASON); expect( screen.getByText( - "The owner's role is fixed. Only the host's break-glass console (sudo felis breakGlass) manages the owner.", + "The Owner role cannot be changed. Manage it through the host recovery console (sudo felis breakGlass).", ), ).toBeTruthy(); expect(screen.queryByRole("combobox")).toBeNull(); diff --git a/panel/src/pages/servers/ServersPage.test.tsx b/panel/src/pages/servers/ServersPage.test.tsx index 84fb625..bbd7f72 100644 --- a/panel/src/pages/servers/ServersPage.test.tsx +++ b/panel/src/pages/servers/ServersPage.test.tsx @@ -324,7 +324,7 @@ describe("ServersPage order and paging", () => { await sortBy(user, "Sort by status"); expect(await tableOrder()).toEqual(["skyblock", "survival", "Arcade", "node-2", "node-10", "creative"]); - await sortBy(user, "Most players first"); + await sortBy(user, "Online players, descending"); expect(await tableOrder()).toEqual(["skyblock", "survival", "Arcade", "creative", "node-2", "node-10"]); }); @@ -340,7 +340,7 @@ describe("ServersPage order and paging", () => { // "surv" opens both survival names and sits inside my-surv, which ranks it below. expect(await tableOrder()).toEqual(["survival-a", "survival-b", "my-surv"]); - await sortBy(user, "Most players first"); + await sortBy(user, "Online players, descending"); expect(await tableOrder()).toEqual(["survival-b", "survival-a", "my-surv"]); await user.clear(screen.getByPlaceholderText(/Search/)); expect(await tableOrder()).toEqual(["my-surv", "survival-b", "survival-a"]);