feat: move snippets, keybinding actions and macros out of core into plugins

This commit is contained in:
LukeGus committed 2026-09-29 11:39:14 -05:00
1 parent d9bba10612
commit e0c30a4e6f
262 files changed
+9091 -10744

No files matched your search

@@ -668,9 +668,6 @@ export function registerHostBulkRoutes(
pin: hostData.pin || false,
sudoPassword: hostData.sudoPassword || null,
jumpHosts: jumpHosts ? JSON.stringify(jumpHosts) : null,
quickActions: hostData.quickActions
? JSON.stringify(hostData.quickActions)
: null,
...importedStatusCheck(hostData as Record<string, unknown>),
terminalConfig: hostData.terminalConfig
? JSON.stringify(hostData.terminalConfig)
@@ -921,7 +918,6 @@ export function registerHostBulkRoutes(
jumpHosts: hostData.jumpHosts
? JSON.stringify(hostData.jumpHosts)
: null,
quickActions: null,
statusCheckEnabled: true,
statusCheckInterval: null,
terminalConfig: null,
@@ -179,7 +179,6 @@ export type NormalizedImportedHost = Record<string, unknown> & {
pin?: unknown;
sudoPassword?: unknown;
jumpHosts?: unknown;
quickActions?: unknown;
statusCheckEnabled?: unknown;
statusCheckInterval?: unknown;
terminalConfig?: unknown;
@@ -418,11 +417,11 @@ function parseJsonObject(value: unknown): Record<string, unknown> | null {
}
/**
* sshOptions, and terminalConfig in the shape 2.8 clients and exports read: core's own
* keys only (the SSH options and the startup snippet). The terminal's look
* and behavior are the ssh-terminal plugin's host settings, which it puts
* back through its hostPayloadLegacy. A sudo password 2.8 kept in
* terminal_config comes out as sudoPassword, for the sanitizers to handle.
* sshOptions, and terminalConfig in the shape 2.8 clients and exports read:
* core's own keys only, the SSH options. Everything else 2.8 kept there now
* belongs to plugins' host settings, which they put back through their
* hostPayloadLegacy. A sudo password 2.8 kept in terminal_config comes out as
* sudoPassword, for the sanitizers to handle.
*/
export function hostTerminalExport(host: Record<string, unknown>): {
sshOptions: HostSshOptions;
@@ -434,9 +433,6 @@ export function hostTerminalExport(host: Record<string, unknown>): {
host.sshOptions != null ? host.sshOptions : raw,
);
const terminalConfig: Record<string, unknown> = { ...sshOptions };
if (raw && raw.startupSnippetId !== undefined) {
terminalConfig.startupSnippetId = raw.startupSnippetId;
}
const legacySudo = !host.sudoPassword ? raw?.sudoPassword : undefined;
return {
sshOptions,
@@ -477,9 +473,9 @@ export function transformHostResponse(
vncUser: host.vncUser || undefined,
telnetUser: host.telnetUser || undefined,
jumpHosts: host.jumpHosts ? JSON.parse(host.jumpHosts as string) : [],
quickActions: host.quickActions
? JSON.parse(host.quickActions as string)
: [],
// Moved into a plugin's host settings; its hostPayloadLegacy puts it
// back for 2.8 clients.
quickActions: undefined,
statusCheckEnabled:
host.statusCheckEnabled !== false && host.statusCheckEnabled !== 0,
statusCheckInterval:
-15
View File
@@ -194,7 +194,6 @@ router.post(
sudoPassword,
pin,
jumpHosts,
quickActions,
statusCheckEnabled,
statusCheckInterval,
terminalConfig,
@@ -296,9 +295,6 @@ router.post(
overrideCredentialUsername: overrideCredentialUsername ? 1 : 0,
pin: pin ? 1 : 0,
jumpHosts: Array.isArray(jumpHosts) ? JSON.stringify(jumpHosts) : null,
quickActions: Array.isArray(quickActions)
? JSON.stringify(quickActions)
: null,
statusCheckEnabled: statusCheckEnabled === false ? 0 : 1,
statusCheckInterval: normalizeStatusInterval(statusCheckInterval),
terminalConfig: terminalConfig
@@ -682,7 +678,6 @@ router.post(
keyPassword: resolvedKeyPassword,
keyType: resolvedKeyType,
jumpHosts: [],
quickActions: [],
statusCheckEnabled: true,
statusCheckInterval: null,
notes: "",
@@ -794,7 +789,6 @@ router.put(
sudoPassword,
pin,
jumpHosts,
quickActions,
statusCheckEnabled,
statusCheckInterval,
terminalConfig,
@@ -897,9 +891,6 @@ router.put(
overrideCredentialUsername: overrideCredentialUsername ? 1 : 0,
pin: pin ? 1 : 0,
jumpHosts: Array.isArray(jumpHosts) ? JSON.stringify(jumpHosts) : null,
quickActions: Array.isArray(quickActions)
? JSON.stringify(quickActions)
: null,
statusCheckEnabled: statusCheckEnabled === false ? 0 : 1,
statusCheckInterval: normalizeStatusInterval(statusCheckInterval),
terminalConfig: terminalConfig
@@ -1768,9 +1759,6 @@ router.get(
jumpHosts: resolvedHost.jumpHosts
? JSON.parse(resolvedHost.jumpHosts as string)
: null,
quickActions: resolvedHost.quickActions
? JSON.parse(resolvedHost.quickActions as string)
: null,
terminalConfig:
hostTerminalExport(resolvedHost).terminalConfig ?? null,
sshOptions: hostTerminalExport(resolvedHost).sshOptions,
@@ -1903,9 +1891,6 @@ router.get(
jumpHosts: resolvedHost.jumpHosts
? JSON.parse(resolvedHost.jumpHosts as string)
: null,
quickActions: resolvedHost.quickActions
? JSON.parse(resolvedHost.quickActions as string)
: null,
terminalConfig:
hostTerminalExport(resolvedHost).terminalConfig ?? null,
sshOptions: hostTerminalExport(resolvedHost).sshOptions,
@@ -1,13 +1,31 @@
const VALID_ACTION_TYPES = [
"copy",
"paste",
"sendControlCode",
"sendText",
"runSnippet",
"nextTab",
"previousTab",
"openCommandPalette",
];
import {
CORE_KEYBINDING_ACTIONS,
validateKeybindingActionParams,
type PluginKeybindingActionContribution,
} from "@termix/plugin-sdk/manifest";
export interface DeclaredKeybindingAction extends PluginKeybindingActionContribution {
pluginId: string;
}
let declaredActions: () => DeclaredKeybindingAction[] = () => [];
/** Set by the plugin runtime: every installed plugin's declared actions. */
export function setKeybindingActionSource(
source: () => DeclaredKeybindingAction[],
): void {
declaredActions = source;
}
/** The declaration a saved action type answers to, if a plugin made one. */
export function findKeybindingAction(
type: string,
): DeclaredKeybindingAction | undefined {
return declaredActions().find((action) => action.id === type);
}
const ACTION_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9.-]{0,63}$/;
const MAX_LOOSE_VALUE = 65_536;
export function isValidKeyCombo(combo: unknown): boolean {
return (
@@ -22,22 +40,32 @@ export function isValidKeyCombo(combo: unknown): boolean {
);
}
/**
* A saved action. The shell's own types take no parameters; a type a plugin
* declared is checked against its declaration, whether or not the plugin is
* running. A type nobody declares (its plugin was removed) is only checked
* loosely, so the rest of the list can still be saved, and whichever plugin
* claims it later ignores what it cannot use.
*/
export function isValidKeybindingAction(action: unknown): boolean {
if (!action || typeof action !== "object") return false;
const type = (action as { type?: unknown }).type;
if (typeof type !== "string" || !VALID_ACTION_TYPES.includes(type))
if (!action || typeof action !== "object" || Array.isArray(action)) {
return false;
if (type === "sendText") {
return typeof (action as { text?: unknown }).text === "string";
}
if (type === "sendControlCode") {
const code = (action as { controlCode?: unknown }).controlCode;
return typeof code === "string" && /^[a-zA-Z]$/.test(code);
const record = action as Record<string, unknown>;
const type = record.type;
if (typeof type !== "string" || !ACTION_TYPE_PATTERN.test(type)) {
return false;
}
if (type === "runSnippet") {
return typeof (action as { snippetId?: unknown }).snippetId === "string";
}
return true;
if (CORE_KEYBINDING_ACTIONS.includes(type)) return true;
const declared = findKeybindingAction(type);
if (declared) return validateKeybindingActionParams(record, declared);
return Object.entries(record).every(
([, value]) =>
value === null ||
typeof value === "boolean" ||
typeof value === "number" ||
(typeof value === "string" && value.length <= MAX_LOOSE_VALUE),
);
}
export function isValidKeybinding(entry: unknown): boolean {
@@ -647,7 +647,7 @@ export function registerUserAdminRoutes(
if (outcome.status === "wipe_confirmation_required") {
return res.status(409).json({
error:
"This user has not logged in since the encryption upgrade, so their data cannot be recovered. Set confirmDataWipe to reset anyway and delete their hosts, credentials and snippets.",
"This user has not logged in since the encryption upgrade, so their data cannot be recovered. Set confirmDataWipe to reset anyway and delete their hosts, credentials and other saved data.",
code: "DATA_WIPE_REQUIRED",
});
}
@@ -71,7 +71,7 @@ export async function resetUserPassword(
await createCurrentRecentActivityRepository().deleteByUserId(userId);
await createCurrentHostRepository().deleteByUserId(userId);
await createCurrentCredentialRepository().deleteByUserId(userId);
// A plugin holding this user's data (snippets and anything else keyed by
// A plugin holding this user's data (anything keyed by
// refUser()) wipes it on this topic, since the user row itself survives.
pluginEvents.emit(TOPICS.userDataWiped, { userId });
@@ -416,7 +416,7 @@ export function registerUserPasswordResetRoutes(
if (outcome.status === "wipe_confirmation_required") {
return res.status(409).json({
error:
"This account has not logged in since the encryption upgrade, so its stored data cannot be recovered without the old password. Resetting will permanently delete its hosts, credentials and snippets.",
"This account has not logged in since the encryption upgrade, so its stored data cannot be recovered without the old password. Resetting will permanently delete its hosts, credentials and other saved data.",
code: "DATA_WIPE_REQUIRED",
});
}
@@ -27,8 +27,6 @@ const pickPreferences = (row?: UserPreferenceRecord | null) => ({
pinAppRail: row?.pinAppRail ?? null,
expandAppRailOnHover: row?.expandAppRailOnHover ?? null,
showPinAppRailButton: row?.showPinAppRailButton ?? null,
foldersCollapsed: row?.foldersCollapsed ?? null,
confirmSnippetExecution: row?.confirmSnippetExecution ?? null,
disableUpdateCheck: row?.disableUpdateCheck ?? null,
confirmTabClose: row?.confirmTabClose ?? null,
hiddenRailTabs: row?.hiddenRailTabs ?? null,
@@ -37,7 +35,6 @@ const pickPreferences = (row?: UserPreferenceRecord | null) => ({
customThemes: row?.customThemes ?? null,
customKeybindings: row?.customKeybindings ?? null,
terminalDefaults: row?.terminalDefaults ?? null,
terminalMacros: row?.terminalMacros ?? null,
});
const connectionDefaultFields = ["terminalDefaults"] as const;
@@ -57,7 +54,7 @@ export function validateDefaultsJson(value: string): boolean {
* /user-preferences:
* get:
* summary: Get preferences for the current user
* description: showHostTags, hostTrayOnClick, compactHostView and statusColorScheme are legacy fields, kept here read-only for backward compatibility; the authoritative copy is GET /host-sidebar/preferences. foldersCollapsed is likewise legacy and read-only; its authoritative copy is the snippets plugin's own user settings.
* description: showHostTags, hostTrayOnClick, compactHostView and statusColorScheme are legacy fields, kept here read-only for backward compatibility; the authoritative copy is GET /host-sidebar/preferences.
* tags:
* - User Preferences
* responses:
@@ -106,12 +103,6 @@ export function validateDefaultsJson(value: string): boolean {
* showPinAppRailButton:
* type: boolean
* nullable: true
* foldersCollapsed:
* type: boolean
* nullable: true
* confirmSnippetExecution:
* type: boolean
* nullable: true
* disableUpdateCheck:
* type: boolean
* nullable: true
@@ -134,7 +125,7 @@ export function validateDefaultsJson(value: string): boolean {
* customKeybindings:
* type: string
* nullable: true
* description: JSON-encoded array of the user's custom terminal keybindings.
* description: JSON-encoded array of the user's custom keybindings. An action's type is one of the shell's own or one a plugin declares in contributes.keybindingActions.
*/
router.get("/", authenticateJWT, async (req: Request, res: Response) => {
const userId = (req as AuthenticatedRequest).userId;
@@ -157,7 +148,7 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
* /user-preferences:
* put:
* summary: Update preferences for the current user
* description: showHostTags, hostTrayOnClick, compactHostView and statusColorScheme are no longer accepted here -- they moved to PUT /host-sidebar/preferences as part of the sidebar redesign. foldersCollapsed is no longer accepted either, now that it is a snippets plugin user setting.
* description: showHostTags, hostTrayOnClick, compactHostView and statusColorScheme are no longer accepted here -- they moved to PUT /host-sidebar/preferences as part of the sidebar redesign. Values that moved into a feature's own user settings are ignored too.
* tags:
* - User Preferences
* requestBody:
@@ -189,8 +180,6 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
* type: boolean
* showPinAppRailButton:
* type: boolean
* confirmSnippetExecution:
* type: boolean
* disableUpdateCheck:
* type: boolean
* confirmTabClose:
@@ -202,7 +191,7 @@ router.get("/", authenticateJWT, async (req: Request, res: Response) => {
* description: JSON-encoded array of the user's saved global custom terminal themes.
* customKeybindings:
* type: string
* description: JSON-encoded array of the user's custom terminal keybindings.
* description: JSON-encoded array of the user's custom keybindings. Each action is checked against the shell's own types and the parameters a plugin declares for its type.
* responses:
* 200:
* description: Preferences updated successfully.
@@ -221,14 +210,12 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
pinAppRail,
expandAppRailOnHover,
showPinAppRailButton,
confirmSnippetExecution,
disableUpdateCheck,
confirmTabClose,
hiddenRailTabs,
customThemes,
customKeybindings,
terminalDefaults,
terminalMacros,
} = req.body as {
reopenTabsOnLogin?: boolean;
theme?: string | null;
@@ -241,21 +228,19 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
pinAppRail?: boolean | null;
expandAppRailOnHover?: boolean | null;
showPinAppRailButton?: boolean | null;
confirmSnippetExecution?: boolean | null;
disableUpdateCheck?: boolean | null;
confirmTabClose?: boolean | null;
hiddenRailTabs?: string | null;
customThemes?: string | null;
customKeybindings?: string | null;
terminalDefaults?: string | null;
terminalMacros?: string | null;
};
// showHostTags, hostTrayOnClick, compactHostView, statusColorScheme are no
// longer writable here -- they moved to /host-sidebar/preferences as of the
// sidebar redesign. The columns stay in the table (read once as a
// migration seed by that route) but this endpoint silently ignores them if
// a stale client still sends them. foldersCollapsed similarly moved out,
// to the snippets plugin's own user settings.
// a stale client still sends them. The same goes for the preferences that
// moved into a plugin's own user settings.
const updates: UserPreferenceUpdate = {
updatedAt: new Date().toISOString(),
@@ -280,7 +265,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
customThemes,
customKeybindings,
terminalDefaults,
terminalMacros,
})) {
if (value !== undefined && value !== null && typeof value !== "string") {
return res.status(400).json({ error: `${key} must be a string` });
@@ -349,41 +333,12 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
}
}
if (terminalMacros !== undefined && terminalMacros !== null) {
let parsedMacros: unknown;
try {
parsedMacros = JSON.parse(terminalMacros);
} catch {
return res
.status(400)
.json({ error: "terminalMacros must be a JSON-encoded array" });
}
if (
terminalMacros.length > 512 * 1024 ||
!Array.isArray(parsedMacros) ||
parsedMacros.length > 100 ||
!parsedMacros.every(
(macro) =>
!!macro &&
typeof macro === "object" &&
typeof (macro as { id?: unknown }).id === "string" &&
typeof (macro as { name?: unknown }).name === "string" &&
Array.isArray((macro as { steps?: unknown }).steps),
)
) {
return res.status(400).json({
error: "terminalMacros must contain at most 100 valid macros",
});
}
}
const boolFields: Record<string, boolean | null | undefined> = {
commandAutocomplete,
commandPaletteEnabled,
pinAppRail,
expandAppRailOnHover,
showPinAppRailButton,
confirmSnippetExecution,
disableUpdateCheck,
confirmTabClose,
};
@@ -408,8 +363,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
updates.expandAppRailOnHover = expandAppRailOnHover;
if (showPinAppRailButton !== undefined)
updates.showPinAppRailButton = showPinAppRailButton;
if (confirmSnippetExecution !== undefined)
updates.confirmSnippetExecution = confirmSnippetExecution;
if (disableUpdateCheck !== undefined)
updates.disableUpdateCheck = disableUpdateCheck;
if (confirmTabClose !== undefined) updates.confirmTabClose = confirmTabClose;
@@ -418,7 +371,6 @@ router.put("/", authenticateJWT, async (req: Request, res: Response) => {
updates.customKeybindings = customKeybindings;
if (terminalDefaults !== undefined)
updates.terminalDefaults = terminalDefaults;
if (terminalMacros !== undefined) updates.terminalMacros = terminalMacros;
if (Object.keys(updates).length === 1) {
return res.status(400).json({ error: "No preferences provided" });
-7
View File
@@ -143,13 +143,6 @@ export async function resolveHostById(
host.socks5ProxyChain = [];
}
}
if (typeof host.quickActions === "string" && host.quickActions) {
try {
host.quickActions = JSON.parse(host.quickActions as string);
} catch {
host.quickActions = [];
}
}
if (typeof host.portKnockSequence === "string" && host.portKnockSequence) {
try {
host.portKnockSequence = JSON.parse(host.portKnockSequence as string);
+19
View File
@@ -21,6 +21,8 @@ import { invalidatePluginPermissionCache } from "./permissions.js";
import { setSshAuthTypeOwnerSource } from "../hosts/connect/auth-provider-registry.js";
import { setSecretResolverOwnerSource } from "../hosts/connect/secret-resolver-registry.js";
import { recordConflict } from "./conflicts.js";
import { setPluginImpersonationCheck } from "../utils/auth-manager.js";
import { setKeybindingActionSource } from "../database/routes/keybinding-validation.js";
let loader: PluginLoader | null = null;
@@ -34,6 +36,23 @@ export function getPluginRuntime(): { loader: PluginLoader } {
(pluginId) => loader?.get(pluginId)?.state === "active",
);
setPluginInstalledCheck((pluginId) => !!loader?.get(pluginId));
// An admin acting for another user reaches only the plugins that opted in.
setPluginImpersonationCheck((pluginId) => {
const plugin = loader?.get(pluginId);
return (
plugin?.state === "active" &&
plugin.manifest.contributes?.http?.adminImpersonation === true
);
});
// Saved keybindings are checked against every installed plugin's
// declarations, so a binding keeps validating while its plugin is off.
setKeybindingActionSource(() =>
(loader?.list() ?? []).flatMap((plugin) =>
(plugin.manifest.contributes?.keybindingActions ?? []).map(
(action) => ({ ...action, pluginId: plugin.id }),
),
),
);
// Lets a host whose auth type belongs to a disabled plugin name it.
setSshAuthTypeOwnerSource(() =>
(loader?.list() ?? []).flatMap((plugin) =>
+57 -10
View File
@@ -57,8 +57,6 @@ const HOST_REFERENCES = [
entityType: "hosts",
},
{ field: "jumpHosts[].hostId", entityType: "hosts" },
{ field: "quickActions[].snippetId", entityType: "commandSnippet" },
{ field: "terminalConfig.startupSnippetId", entityType: "commandSnippet" },
] as const;
/** Columns that only mean something on the device that holds them. */
@@ -69,6 +67,13 @@ const HOST_LOCAL_FIELDS = [
"hostKeyLastVerified",
];
/**
* Columns kept until 3.0.0 that nothing reads any more: their values moved
* into plugin host settings, which sync with the host's pluginSettings. They
* stay off the wire so a stale local id never reaches the other side.
*/
const HOST_RETIRED_FIELDS = ["quickActions"];
let registered = false;
/** Registers core's entities. Idempotent. */
@@ -148,7 +153,7 @@ export function registerCoreSyncEntities(): void {
table: hosts,
order: 50,
encryptedFields: FieldCrypto.fieldsFor("ssh_data"),
readOnlyFields: HOST_LOCAL_FIELDS,
readOnlyFields: [...HOST_LOCAL_FIELDS, ...HOST_RETIRED_FIELDS],
references: HOST_REFERENCES,
shouldSync: (row) => !row.localOnly && !row.sharedSource,
permissions: {
@@ -198,13 +203,18 @@ export function registerCoreSyncEntities(): void {
order: 90,
singleton: true,
readOnlyFields: ["storageMode"],
references: [
{
field: "customKeybindings[].action.snippetId",
entityType: "commandSnippet",
idType: "string",
},
],
// A keybinding parameter a plugin declares with a syncEntity holds a
// local row id of that entity; the wire carries its syncId.
serialize: (row, resolveSyncId) =>
mapKeybindingReferences(row, async (entityType, value) => {
const id = Number(value);
return Number.isInteger(id) ? resolveSyncId(entityType, id) : null;
}),
deserialize: (row, resolveId) =>
mapKeybindingReferences(row, async (entityType, value) => {
const id = await resolveId(entityType, value);
return id === null ? null : String(id);
}),
});
registerEntity(
@@ -253,6 +263,43 @@ export function registerCoreSyncEntities(): void {
);
}
/**
* Rewrites every keybinding parameter whose declaration names a sync entity,
* keeping the column's storage form. A value that does not map becomes null.
*/
export async function mapKeybindingReferences(
row: SyncRow,
map: (entityType: string, value: string) => Promise<string | null>,
): Promise<SyncRow> {
const stored = row.customKeybindings;
if (typeof stored !== "string" || !stored) return row;
let bindings: unknown;
try {
bindings = JSON.parse(stored);
} catch {
return row;
}
if (!Array.isArray(bindings)) return row;
const { findKeybindingAction } =
await import("../database/routes/keybinding-validation.js");
let changed = false;
for (const binding of bindings) {
const action = (binding as { action?: Record<string, unknown> } | null)
?.action;
if (!action || typeof action.type !== "string") continue;
const params = findKeybindingAction(action.type)?.params ?? {};
for (const [name, param] of Object.entries(params)) {
const value = action[name];
if (!param.syncEntity || typeof value !== "string" || !value) continue;
action[name] = await map(param.syncEntity, value);
changed = true;
}
}
return changed
? { ...row, customKeybindings: JSON.stringify(bindings) }
: row;
}
/** A synced parent link that would make a cycle here is dropped. */
async function dropInvalidParent(userId: string, hostId: number) {
const { createCurrentRepositoryContext } =
@@ -305,10 +305,17 @@ describe("transformHostResponse", () => {
it("parses JSON array fields and defaults them to []", () => {
const result = transformHostResponse({
jumpHosts: '[{"hostId":8}]',
quickActions: null,
portKnockSequence: null,
});
expect(result.jumpHosts).toEqual([{ hostId: 8 }]);
expect(result.quickActions).toEqual([]);
expect(result.portKnockSequence).toEqual([]);
});
it("leaves the 2.8 quick_actions column out, for its plugin to put back", () => {
const result = transformHostResponse({
quickActions: '[{"name":"x","snippetId":1}]',
});
expect(result.quickActions).toBeUndefined();
});
it("passes the stored SSH switch through", () => {
@@ -440,10 +447,7 @@ describe("transformHostResponse terminal fields", () => {
}),
});
expect(host.sshOptions).toEqual({ keepaliveInterval: 20 });
expect(host.terminalConfig).toEqual({
keepaliveInterval: 20,
startupSnippetId: 3,
});
expect(host.terminalConfig).toEqual({ keepaliveInterval: 20 });
});
it("reads the options out of terminal_config before the boot copy", () => {
@@ -1,10 +1,44 @@
import { describe, it, expect } from "vitest";
import { afterEach, beforeEach, describe, it, expect } from "vitest";
import {
isValidKeyCombo,
isValidKeybindingAction,
isValidKeybinding,
setKeybindingActionSource,
} from "../../../database/routes/keybinding-validation.js";
// What installed plugins declare in contributes.keybindingActions.
beforeEach(() => {
setKeybindingActionSource(() => [
{ pluginId: "terminal-fixture", id: "copy" },
{ pluginId: "terminal-fixture", id: "paste" },
{
pluginId: "terminal-fixture",
id: "sendControlCode",
params: {
controlCode: { type: "string", required: true, pattern: "^[a-zA-Z]$" },
},
},
{
pluginId: "terminal-fixture",
id: "sendText",
params: {
text: { type: "string", required: true },
appendEnter: { type: "boolean" },
},
},
{
pluginId: "commands-fixture",
id: "runSnippet",
params: {
snippetId: { type: "string", required: true, pattern: "^[0-9]+$" },
appendEnter: { type: "boolean" },
},
},
]);
});
afterEach(() => setKeybindingActionSource(() => []));
const validCombo = {
key: "c",
isCode: false,
@@ -36,15 +70,26 @@ describe("isValidKeybindingAction", () => {
expect(isValidKeybindingAction({ type: "paste" })).toBe(true);
});
it.each(["nextTab", "previousTab", "openCommandPalette"])(
it.each(["nextTab", "previousTab", "openCommandPalette", "reconnectSession"])(
"accepts the global %s action",
(type) => {
expect(isValidKeybindingAction({ type })).toBe(true);
},
);
it("rejects an unknown action type", () => {
expect(isValidKeybindingAction({ type: "explode" })).toBe(false);
it("keeps a type no installed plugin declares, if it looks sane", () => {
expect(isValidKeybindingAction({ type: "gone.action", arg: "x" })).toBe(
true,
);
expect(isValidKeybindingAction({ type: "gone", nested: { a: 1 } })).toBe(
false,
);
expect(isValidKeybindingAction({ type: "bad type!" })).toBe(false);
expect(isValidKeybindingAction({ type: 5 })).toBe(false);
});
it("refuses a parameter the declaration does not list", () => {
expect(isValidKeybindingAction({ type: "copy", text: "x" })).toBe(false);
});
it("requires text for sendText", () => {
@@ -72,6 +117,9 @@ describe("isValidKeybindingAction", () => {
isValidKeybindingAction({ type: "runSnippet", snippetId: "42" }),
).toBe(true);
expect(isValidKeybindingAction({ type: "runSnippet" })).toBe(false);
expect(
isValidKeybindingAction({ type: "runSnippet", snippetId: "abc" }),
).toBe(false);
});
});
Binary file not shown.
+28
View File
@@ -199,7 +199,9 @@ const HOST_ROWS: Row[] = [
allowLegacyAlgorithms: false,
agentForwarding: true,
environmentVariables: [{ key: "D4", value: "yes" }],
startupSnippetId: 1,
}),
quick_actions: JSON.stringify([{ name: "d4-uptime", snippetId: 1 }]),
mac_address: "AA:BB:CC:DD:EE:04",
wol_broadcast_address: "10.4.0.255",
stats_config: JSON.stringify({
@@ -387,6 +389,32 @@ export const ROWS: Record<string, Row[]> = {
},
]),
command_autocomplete: 1,
confirm_snippet_execution: 1,
folders_collapsed: 0,
terminal_macros: JSON.stringify([
{
id: "d4-macro",
name: "d4 macro",
steps: [{ id: "s1", type: "send", text: "uptime", pressEnter: true }],
},
]),
custom_keybindings: JSON.stringify([
{
id: "d4-kb",
combo: {
key: "u",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
action: { type: "runSnippet", snippetId: "1", appendEnter: true },
enabled: true,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
},
]),
},
],
sso_providers: [
@@ -686,3 +686,76 @@ describe("contributes.http.legacyRedirects", () => {
expect(errors).toMatch(/extra/);
});
});
describe("contributes.http.adminImpersonation", () => {
it("accepts a boolean and refuses anything else", () => {
expect(
validateManifest(
base({ contributes: { http: { adminImpersonation: true } } }),
),
).toEqual([]);
expect(
validateManifest(
base({ contributes: { http: { adminImpersonation: "yes" } } }),
).join(),
).toMatch(/adminImpersonation" must be a boolean/);
});
});
describe("contributes.keybindingActions", () => {
const withActions = (keybindingActions: unknown) =>
validateManifest(base({ contributes: { keybindingActions } }));
it("accepts actions with typed parameters", () => {
expect(
withActions([
{ id: "sample.run" },
{
id: "sample.send",
params: {
itemId: {
type: "string",
required: true,
pattern: "^[0-9]+$",
maxLength: 20,
syncEntity: "items",
},
enter: { type: "boolean" },
},
},
]),
).toEqual([]);
});
it("refuses the shell's own actions, duplicates and bad ids", () => {
const errors = withActions([
{ id: "nextTab" },
{ id: "sample.run" },
{ id: "sample.run" },
{ id: "bad id" },
]).join();
expect(errors).toMatch(/one of the shell's own actions/);
expect(errors).toMatch(/duplicates "sample.run"/);
expect(errors).toMatch(/letters, digits, dots and dashes/);
});
it("refuses bad parameters", () => {
const errors = withActions([
{
id: "sample.run",
params: {
type: { type: "string" },
a: { type: "number" },
b: { type: "string", pattern: "(" },
c: { type: "boolean", syncEntity: "items" },
d: { type: "string", extra: 1 },
},
},
]).join();
expect(errors).toMatch(/params.type is not a valid parameter name/);
expect(errors).toMatch(/a.type must be "string" or "boolean"/);
expect(errors).toMatch(/b.pattern must be a regular expression/);
expect(errors).toMatch(/c.syncEntity needs a string parameter/);
expect(errors).toMatch(/d/);
});
});
@@ -9,7 +9,11 @@ import {
resetSyncRegistry,
unregisterByOwner,
} from "../../plugins/sync-registry.js";
import { registerCoreSyncEntities } from "../../sync/entities.js";
import {
mapKeybindingReferences,
registerCoreSyncEntities,
} from "../../sync/entities.js";
import { setKeybindingActionSource } from "../../database/routes/keybinding-validation.js";
beforeEach(() => {
resetSyncRegistry();
@@ -197,14 +201,51 @@ describe("core sync entities", () => {
);
});
it("translates every snippet a host points at", () => {
const fields = (getEntity("hosts")?.references ?? [])
.filter((reference) => reference.entityType === "commandSnippet")
.map((reference) => reference.field);
expect(fields).toEqual([
"quickActions[].snippetId",
"terminalConfig.startupSnippetId",
it("leaves plugin data out of the host row, which travels in pluginSettings", () => {
const hosts = getEntity("hosts");
expect(
(hosts?.references ?? []).map((reference) => reference.entityType),
).not.toContain("commandSnippet");
expect(hosts?.readOnlyFields).toContain("quickActions");
});
it("translates a keybinding parameter that names a sync entity", async () => {
setKeybindingActionSource(() => [
{
pluginId: "fixture",
id: "runThing",
params: {
thingId: { type: "string", syncEntity: "things" },
label: { type: "string" },
},
},
]);
try {
const row = {
customKeybindings: JSON.stringify([
{ id: "a", action: { type: "runThing", thingId: "4", label: "4" } },
{ id: "b", action: { type: "nextTab" } },
]),
};
const out = await mapKeybindingReferences(row, async (entity, value) =>
entity === "things" && value === "4" ? "sync-4" : null,
);
const bindings = JSON.parse(out.customKeybindings as string);
expect(bindings[0].action).toEqual({
type: "runThing",
thingId: "sync-4",
label: "4",
});
expect(bindings[1].action).toEqual({ type: "nextTab" });
expect(
await mapKeybindingReferences(
{ customKeybindings: null },
async () => "x",
),
).toEqual({ customKeybindings: null });
} finally {
setKeybindingActionSource(() => []);
}
});
it("encrypts host and credential secrets", () => {
@@ -326,6 +326,10 @@ function upgradeChecks(current: () => Booted, bootIndex: number) {
containerRuntime: "podman",
});
expect(await host("ai")).toMatchObject({ enableAiAssistant: true });
expect(await host("snippets")).toMatchObject({
startupSnippetId: 1,
quickActions: [{ name: "d4-uptime", snippetId: 1 }],
});
expect(await host("wake-on-lan")).toMatchObject({
macAddress: "AA:BB:CC:DD:EE:04",
broadcastAddress: "10.4.0.255",
@@ -473,6 +477,11 @@ function upgradeChecks(current: () => Booted, bootIndex: number) {
terminalDefaults: { fontSize: 18, cursorBlink: false },
customThemes: [expect.objectContaining({ id: "d4-theme" })],
commandAutocomplete: true,
macros: [expect.objectContaining({ id: "d4-macro", name: "d4 macro" })],
});
expect(await settings(booted, "snippets", "user")).toMatchObject({
confirmExecution: true,
foldersCollapsed: false,
});
expect(await settings(booted, "step-ca", "admin")).toMatchObject({
caUrl: "https://ca.d4.example",
@@ -0,0 +1,174 @@
/**
* The snippets settings migration: host quick actions, the startup snippet
* and the two snippet user preferences must come through into the snippets
* plugin's settings, and running it twice must change nothing.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
interface Row {
pluginId: string;
scope: string;
scopeId: string | null;
key: string;
value: string | null;
}
const pluginRows: Row[] = [];
const hostRows: Array<Record<string, unknown>> = [];
const preferenceRows: Array<Record<string, unknown>> = [];
let pluginInstalled = true;
const find = (scope: string, scopeId: string | null, key: string) =>
pluginRows.find(
(row) =>
row.pluginId === "snippets" &&
row.scope === scope &&
row.scopeId === scopeId &&
row.key === key,
);
vi.mock("../../database/repositories/factory.js", () => ({
createCurrentPluginRepository: () => ({
findById: async (id: string) =>
pluginInstalled && id === "snippets" ? { id } : null,
}),
createCurrentPluginSettingsRepository: () => ({
get: async (
_pluginId: string,
scope: string,
scopeId: string | null,
key: string,
) => find(scope, scopeId, key) ?? null,
set: async (
pluginId: string,
scope: string,
scopeId: string | null,
key: string,
value: string | null,
) => {
const existing = find(scope, scopeId, key);
if (existing) existing.value = value;
else pluginRows.push({ pluginId, scope, scopeId, key, value });
},
}),
}));
vi.mock("../../database/db/index.js", () => ({
getDb: () => ({
all: async (query: unknown) =>
JSON.stringify(query).includes("user_preferences")
? preferenceRows
: hostRows,
}),
}));
const { runSnippetsSettingsMigration, snippetHostSettingsFromRow } =
await import("../../upgrade/snippets-settings-migration.js");
function value(scope: string, scopeId: string, key: string): unknown {
const row = find(scope, scopeId, key);
return row?.value == null ? undefined : JSON.parse(row.value);
}
beforeEach(() => {
pluginRows.length = 0;
hostRows.length = 0;
preferenceRows.length = 0;
pluginInstalled = true;
});
describe("snippetHostSettingsFromRow", () => {
it("reads quick actions and the startup snippet, dropping broken rows", () => {
expect(
snippetHostSettingsFromRow({
quick_actions: JSON.stringify([
{ name: "Restart", snippetId: 3 },
{ name: "Broken", snippetId: "x" },
{ name: "Str", snippetId: "4" },
]),
terminal_config: JSON.stringify({ fontSize: 14, startupSnippetId: 9 }),
}),
).toEqual({
quickActions: [
{ name: "Restart", snippetId: 3 },
{ name: "Str", snippetId: 4 },
],
startupSnippetId: 9,
});
});
it("reads nothing from a row without either", () => {
expect(
snippetHostSettingsFromRow({
quick_actions: "[]",
terminal_config: JSON.stringify({ fontSize: 14 }),
}),
).toEqual({});
});
});
describe("runSnippetsSettingsMigration", () => {
it("moves host and user values once", async () => {
hostRows.push(
{
id: 1,
quick_actions: JSON.stringify([{ name: "Up", snippetId: 2 }]),
terminal_config: JSON.stringify({ startupSnippetId: 5 }),
},
{ id: 2, quick_actions: null, terminal_config: "{}" },
);
preferenceRows.push(
{ user_id: "u1", confirm_snippet_execution: 1, folders_collapsed: 0 },
{
user_id: "u2",
confirm_snippet_execution: null,
folders_collapsed: null,
},
);
const first = await runSnippetsSettingsMigration();
expect(first).toEqual({ hostsMoved: 1, hostsSkipped: 0, usersMoved: 1 });
expect(value("host", "1", "quickActions")).toEqual([
{ name: "Up", snippetId: 2 },
]);
expect(value("host", "1", "startupSnippetId")).toBe(5);
expect(value("user", "u1", "confirmExecution")).toBe(true);
expect(value("user", "u1", "foldersCollapsed")).toBe(false);
expect(find("user", "u2", "confirmExecution")).toBeUndefined();
const snapshot = JSON.stringify(pluginRows);
const second = await runSnippetsSettingsMigration();
expect(second).toEqual({ hostsMoved: 0, hostsSkipped: 1, usersMoved: 0 });
expect(JSON.stringify(pluginRows)).toBe(snapshot);
});
it("never overwrites what the user already changed in the plugin", async () => {
pluginRows.push({
pluginId: "snippets",
scope: "host",
scopeId: "1",
key: "startupSnippetId",
value: "7",
});
hostRows.push({
id: 1,
quick_actions: JSON.stringify([{ name: "Up", snippetId: 2 }]),
terminal_config: JSON.stringify({ startupSnippetId: 5 }),
});
await runSnippetsSettingsMigration();
expect(value("host", "1", "startupSnippetId")).toBe(7);
expect(find("host", "1", "quickActions")).toBeUndefined();
});
it("does nothing while the plugin is not installed", async () => {
pluginInstalled = false;
hostRows.push({
id: 1,
quick_actions: JSON.stringify([{ name: "Up", snippetId: 2 }]),
terminal_config: null,
});
await runSnippetsSettingsMigration();
expect(pluginRows).toHaveLength(0);
});
});
@@ -0,0 +1,104 @@
/**
* The terminal macros migration: a user's saved macros must come through
* into the ssh-terminal plugin's macros setting, and running it twice must
* change nothing.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
interface Row {
pluginId: string;
scope: string;
scopeId: string | null;
key: string;
value: string | null;
}
const pluginRows: Row[] = [];
const preferenceRows: Array<Record<string, unknown>> = [];
const find = (scopeId: string, key: string) =>
pluginRows.find(
(row) =>
row.pluginId === "ssh-terminal" &&
row.scope === "user" &&
row.scopeId === scopeId &&
row.key === key,
);
vi.mock("../../database/repositories/factory.js", () => ({
createCurrentPluginRepository: () => ({
findById: async (id: string) => (id === "ssh-terminal" ? { id } : null),
}),
createCurrentPluginSettingsRepository: () => ({
get: async (_p: string, _s: string, scopeId: string, key: string) =>
find(scopeId, key) ?? null,
set: async (
pluginId: string,
scope: string,
scopeId: string,
key: string,
value: string | null,
) => {
const existing = find(scopeId, key);
if (existing) existing.value = value;
else pluginRows.push({ pluginId, scope, scopeId, key, value });
},
}),
}));
vi.mock("../../database/db/index.js", () => ({
getDb: () => ({ all: async () => preferenceRows }),
}));
const { runSshTerminalMacrosMigration, macrosFromPreferences } =
await import("../../upgrade/ssh-terminal-macros-migration.js");
const macro = { id: "m1", name: "Deploy", steps: [{ id: "s", type: "send" }] };
beforeEach(() => {
pluginRows.length = 0;
preferenceRows.length = 0;
});
describe("macrosFromPreferences", () => {
it("keeps valid macros and drops the rest", () => {
expect(
macrosFromPreferences(JSON.stringify([macro, { id: 1 }, "x"])),
).toEqual([macro]);
expect(macrosFromPreferences("[]")).toBeNull();
expect(macrosFromPreferences("{bad")).toBeNull();
});
});
describe("runSshTerminalMacrosMigration", () => {
it("moves each user's macros once", async () => {
preferenceRows.push(
{ user_id: "u1", terminal_macros: JSON.stringify([macro]) },
{ user_id: "u2", terminal_macros: "[]" },
);
expect(await runSshTerminalMacrosMigration()).toEqual({ usersMoved: 1 });
expect(JSON.parse(find("u1", "macros")!.value!)).toEqual([macro]);
expect(find("u2", "macros")).toBeUndefined();
const snapshot = JSON.stringify(pluginRows);
expect(await runSshTerminalMacrosMigration()).toEqual({ usersMoved: 0 });
expect(JSON.stringify(pluginRows)).toBe(snapshot);
});
it("leaves a user who already has macros in the plugin alone", async () => {
pluginRows.push({
pluginId: "ssh-terminal",
scope: "user",
scopeId: "u1",
key: "macros",
value: "[]",
});
preferenceRows.push({
user_id: "u1",
terminal_macros: JSON.stringify([macro]),
});
await runSshTerminalMacrosMigration();
expect(find("u1", "macros")!.value).toBe("[]");
});
});
@@ -66,7 +66,8 @@ vi.mock("../../utils/audit-logger.js", () => ({
getRequestMeta: () => ({ ipAddress: "", userAgent: "" }),
}));
const { AuthManager } = await import("../../utils/auth-manager.js");
const { AuthManager, setPluginImpersonationCheck, allowsAdminImpersonation } =
await import("../../utils/auth-manager.js");
const authManager = AuthManager.getInstance();
const middleware = authManager.createAuthMiddleware();
@@ -216,4 +217,48 @@ describe("AuthManager admin impersonation", () => {
expect(nexted).toBe(true);
expect((req as unknown as { userId: string }).userId).toBe("regular1");
});
it("rejects the header on a plugin route whose manifest did not opt in", async () => {
setPluginImpersonationCheck((pluginId) => pluginId === "opted-in");
try {
const { res, nexted } = await runMiddleware(token("admin1"), {
"x-admin-target-user": "target1",
__url: "/plugin-api/other/items",
});
expect(nexted).toBe(false);
expect(res.statusCode).toBe(403);
expect((res.body as { code?: string }).code).toBe(
"IMPERSONATION_NOT_ALLOWED",
);
} finally {
setPluginImpersonationCheck(() => false);
}
});
it("lets an admin act for a user on a plugin that opted in", async () => {
setPluginImpersonationCheck((pluginId) => pluginId === "opted-in");
try {
const { req, nexted } = await runMiddleware(token("admin1"), {
"x-admin-target-user": "target1",
__url: "/plugin-api/opted-in/items?x=1",
});
expect(nexted).toBe(true);
expect((req as unknown as { userId: string }).userId).toBe("target1");
} finally {
setPluginImpersonationCheck(() => false);
}
});
it("matches the plugin id exactly, not as a prefix", () => {
setPluginImpersonationCheck((pluginId) => pluginId === "opted-in");
try {
expect(allowsAdminImpersonation("/plugin-api/opted-in")).toBe(true);
expect(allowsAdminImpersonation("/plugin-api/opted-in-too/x")).toBe(
false,
);
expect(allowsAdminImpersonation("/users/sessions")).toBe(false);
} finally {
setPluginImpersonationCheck(() => false);
}
});
});
+4
View File
@@ -16,6 +16,8 @@ import { runTunnelsSettingsMigration } from "./tunnels-settings-migration.js";
import { runWebEndpointSettingsMigration } from "./web-endpoint-settings-migration.js";
import { runSshTerminalSettingsMigration } from "./ssh-terminal-settings-migration.js";
import { runSshTerminalLookMigration } from "./ssh-terminal-look-migration.js";
import { runSshTerminalMacrosMigration } from "./ssh-terminal-macros-migration.js";
import { runSnippetsSettingsMigration } from "./snippets-settings-migration.js";
import { runTmuxMonitorSettingsMigration } from "./tmux-monitor-settings-migration.js";
import { runSessionSharingSettingsMigration } from "./session-sharing-settings-migration.js";
import { runSessionRecordingSettingsMigration } from "./session-recording-settings-migration.js";
@@ -41,6 +43,8 @@ const MIGRATIONS: Array<[string, () => Promise<unknown>]> = [
["runWebEndpointSettingsMigration", runWebEndpointSettingsMigration],
["runSshTerminalSettingsMigration", runSshTerminalSettingsMigration],
["runSshTerminalLookMigration", runSshTerminalLookMigration],
["runSshTerminalMacrosMigration", runSshTerminalMacrosMigration],
["runSnippetsSettingsMigration", runSnippetsSettingsMigration],
["runTmuxMonitorSettingsMigration", runTmuxMonitorSettingsMigration],
["runSessionSharingSettingsMigration", runSessionSharingSettingsMigration],
[
@@ -0,0 +1,185 @@
/**
* Moves what core kept for snippets into the snippets plugin's settings.
*
* Per host: ssh_data.quick_actions becomes the quickActions host setting,
* and terminal_config.startupSnippetId the startupSnippetId one. Per user:
* user_preferences.confirm_snippet_execution and folders_collapsed become the
* confirmExecution and foldersCollapsed user settings.
*
* The columns keep their values until 3.0.0 drops them.
*
* Idempotent: a host that already has either host setting, and a user key
* already set, are skipped, so this is safe on every boot.
*/
import { sql } from "drizzle-orm";
import { databaseLogger } from "../utils/logger.js";
import {
legacyFlag,
selectLegacyRows,
} from "../utils/crypto-migration/raw-rows.js";
import {
createCurrentPluginRepository,
createCurrentPluginSettingsRepository,
} from "../database/repositories/factory.js";
const PLUGIN_ID = "snippets";
const HOST_KEYS = ["quickActions", "startupSnippetId"];
function parseJson(value: unknown): unknown {
if (typeof value !== "string" || !value) return value ?? undefined;
try {
let parsed = JSON.parse(value);
if (typeof parsed === "string") parsed = JSON.parse(parsed);
return parsed;
} catch {
return undefined;
}
}
function snippetId(value: unknown): number | null {
const id = typeof value === "string" ? Number(value) : value;
return typeof id === "number" && Number.isInteger(id) && id > 0 ? id : null;
}
/** The snippets host settings one 2.8 ssh_data row carried. */
export function snippetHostSettingsFromRow(row: {
quick_actions?: unknown;
terminal_config?: unknown;
}): Record<string, unknown> {
const values: Record<string, unknown> = {};
const actions = parseJson(row.quick_actions);
if (Array.isArray(actions)) {
const quickActions = actions.flatMap((entry) => {
const id = snippetId((entry as { snippetId?: unknown })?.snippetId);
if (id === null) return [];
const name = (entry as { name?: unknown }).name;
return [{ name: typeof name === "string" ? name : "", snippetId: id }];
});
if (quickActions.length > 0) values.quickActions = quickActions;
}
const config = parseJson(row.terminal_config) as
{ startupSnippetId?: unknown } | undefined;
const startup = snippetId(config?.startupSnippetId);
if (startup !== null) values.startupSnippetId = startup;
return values;
}
export interface SnippetsSettingsMigrationResult {
hostsMoved: number;
hostsSkipped: number;
usersMoved: number;
}
export async function runSnippetsSettingsMigration(): Promise<SnippetsSettingsMigrationResult> {
const result: SnippetsSettingsMigrationResult = {
hostsMoved: 0,
hostsSkipped: 0,
usersMoved: 0,
};
try {
const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID);
if (!plugin) return result;
} catch {
return result;
}
const pluginSettings = createCurrentPluginSettingsRepository();
try {
const rows = await selectLegacyRows<{
id: number;
quick_actions: unknown;
terminal_config: unknown;
}>(sql`
SELECT id, quick_actions, terminal_config FROM ssh_data
WHERE quick_actions IS NOT NULL OR terminal_config IS NOT NULL
`);
for (const row of rows) {
const values = snippetHostSettingsFromRow(row);
if (Object.keys(values).length === 0) continue;
const scopeId = String(row.id);
let done = false;
for (const key of HOST_KEYS) {
if (await pluginSettings.get(PLUGIN_ID, "host", scopeId, key)) {
done = true;
}
}
if (done) {
result.hostsSkipped++;
continue;
}
for (const [key, value] of Object.entries(values)) {
await pluginSettings.set(
PLUGIN_ID,
"host",
scopeId,
key,
JSON.stringify(value),
);
}
result.hostsMoved++;
}
} catch (error) {
databaseLogger.warn("Snippets host settings migration failed", {
operation: "snippets_settings_migration",
error: error instanceof Error ? error.message : String(error),
});
}
try {
const rows = await selectLegacyRows<{
user_id: string;
confirm_snippet_execution: unknown;
folders_collapsed: unknown;
}>(sql`
SELECT user_id, confirm_snippet_execution, folders_collapsed
FROM user_preferences
`);
for (const row of rows) {
const values: Record<string, boolean> = {};
if (row.confirm_snippet_execution != null) {
values.confirmExecution = legacyFlag(
row.confirm_snippet_execution,
false,
);
}
if (row.folders_collapsed != null) {
values.foldersCollapsed = legacyFlag(row.folders_collapsed, true);
}
let moved = false;
for (const [key, value] of Object.entries(values)) {
const existing = await pluginSettings.get(
PLUGIN_ID,
"user",
row.user_id,
key,
);
if (existing && existing.value !== null) continue;
await pluginSettings.set(
PLUGIN_ID,
"user",
row.user_id,
key,
JSON.stringify(value),
);
moved = true;
}
if (moved) result.usersMoved++;
}
} catch (error) {
databaseLogger.warn("Snippets user settings migration failed", {
operation: "snippets_settings_migration",
error: error instanceof Error ? error.message : String(error),
});
}
if (result.hostsMoved > 0 || result.usersMoved > 0) {
databaseLogger.info(
`Moved the snippet settings of ${result.hostsMoved} host(s) and ${result.usersMoved} user(s) into plugin settings`,
{ operation: "snippets_settings_migration" },
);
}
return result;
}
@@ -0,0 +1,98 @@
/**
* Moves user_preferences.terminal_macros into the ssh-terminal plugin's
* macros user setting. Macros a browser kept on its own (storage mode
* "local") are moved by the plugin's frontend the first time the panel opens.
*
* The column keeps its value until 3.0.0 drops it.
*
* Idempotent: a user whose macros setting is already set is skipped.
*/
import { sql } from "drizzle-orm";
import { databaseLogger } from "../utils/logger.js";
import { selectLegacyRows } from "../utils/crypto-migration/raw-rows.js";
import {
createCurrentPluginRepository,
createCurrentPluginSettingsRepository,
} from "../database/repositories/factory.js";
const PLUGIN_ID = "ssh-terminal";
const MAX_MACROS = 100;
/** The saved macros in a 2.8 terminal_macros value, or null for none. */
export function macrosFromPreferences(value: unknown): unknown[] | null {
if (typeof value !== "string" || !value) return null;
let parsed: unknown;
try {
parsed = JSON.parse(value);
} catch {
return null;
}
if (!Array.isArray(parsed)) return null;
const macros = parsed
.filter(
(macro) =>
!!macro &&
typeof macro === "object" &&
typeof (macro as { id?: unknown }).id === "string" &&
typeof (macro as { name?: unknown }).name === "string" &&
Array.isArray((macro as { steps?: unknown }).steps),
)
.slice(0, MAX_MACROS);
return macros.length > 0 ? macros : null;
}
export async function runSshTerminalMacrosMigration(): Promise<{
usersMoved: number;
}> {
const result = { usersMoved: 0 };
try {
const plugin = await createCurrentPluginRepository().findById(PLUGIN_ID);
if (!plugin) return result;
} catch {
return result;
}
const pluginSettings = createCurrentPluginSettingsRepository();
try {
const rows = await selectLegacyRows<{
user_id: string;
terminal_macros: unknown;
}>(sql`
SELECT user_id, terminal_macros FROM user_preferences
WHERE terminal_macros IS NOT NULL
`);
for (const row of rows) {
const macros = macrosFromPreferences(row.terminal_macros);
if (!macros) continue;
const existing = await pluginSettings.get(
PLUGIN_ID,
"user",
row.user_id,
"macros",
);
if (existing && existing.value !== null) continue;
await pluginSettings.set(
PLUGIN_ID,
"user",
row.user_id,
"macros",
JSON.stringify(macros),
);
result.usersMoved++;
}
} catch (error) {
databaseLogger.warn("Terminal macros migration failed", {
operation: "ssh_terminal_macros_migration",
error: error instanceof Error ? error.message : String(error),
});
}
if (result.usersMoved > 0) {
databaseLogger.info(
`Moved the terminal macros of ${result.usersMoved} user(s) into plugin settings`,
{ operation: "ssh_terminal_macros_migration" },
);
}
return result;
}
+25 -11
View File
@@ -70,13 +70,30 @@ interface RequestWithHeaders extends Request {
const ADMIN_TARGET_USER_HEADER = "x-admin-target-user";
// Data-plane routes an admin may hit on behalf of another user. Everything
// else (TOTP, sessions, tunnels, file manager, ...) rejects the header.
const IMPERSONATION_PATH_ALLOWLIST = [
/^\/host\/db\//,
/^\/credentials(\/|$)/,
/^\/plugin-api\/snippets(\/|$)/,
];
// Core data-plane routes an admin may hit on behalf of another user.
// Everything else (TOTP, sessions, ...) rejects the header, and so does every
// plugin route unless its manifest sets contributes.http.adminImpersonation.
const IMPERSONATION_PATH_ALLOWLIST = [/^\/host\/db\//, /^\/credentials(\/|$)/];
const PLUGIN_API_PATH = /^\/plugin-api\/([a-z][a-z0-9-]*)(\/|$)/;
let pluginAllowsImpersonation: (pluginId: string) => boolean = () => false;
/** Set by the plugin runtime: whether a running plugin opted in. */
export function setPluginImpersonationCheck(
check: (pluginId: string) => boolean,
): void {
pluginAllowsImpersonation = check;
}
/** Whether an admin may send X-Admin-Target-User to this path. */
export function allowsAdminImpersonation(path: string): boolean {
if (IMPERSONATION_PATH_ALLOWLIST.some((pattern) => pattern.test(path))) {
return true;
}
const plugin = PLUGIN_API_PATH.exec(path);
return !!plugin && pluginAllowsImpersonation(plugin[1]);
}
class AuthManager {
private static instance: AuthManager;
@@ -836,10 +853,7 @@ class AuthManager {
}
const path = (req.originalUrl || req.url || "").split("?")[0];
const allowed = IMPERSONATION_PATH_ALLOWLIST.some((pattern) =>
pattern.test(path),
);
if (!allowed) {
if (!allowsAdminImpersonation(path)) {
res.status(403).json({
error: "Impersonation is not allowed for this route",
code: "IMPERSONATION_NOT_ALLOWED",
-4
View File
@@ -67,10 +67,6 @@ const swaggerOptions: SwaggerJSDocOptions = {
name: "RBAC",
description: "Role-based access control for host sharing",
},
{
name: "Snippets",
description: "Command snippet management",
},
{
name: "Terminal",
description: "Terminal command history",
+4 -18
View File
@@ -61,11 +61,6 @@ export interface JumpHost {
hostId: number;
}
export interface QuickAction {
name: string;
snippetId: number;
}
export type Host = {
id: number;
name: string;
@@ -92,7 +87,6 @@ export type Host = {
overrideCredentialUsername?: boolean;
userId?: string;
jumpHosts?: JumpHost[];
quickActions?: QuickAction[];
statusCheckEnabled?: boolean;
/** Seconds between status checks; null follows the global setting. */
statusCheckInterval?: number | null;
@@ -176,11 +170,6 @@ export interface JumpHostData {
hostId: number;
}
export interface QuickActionData {
name: string;
snippetId: number;
}
export interface ProxyNode {
host: string;
port: number;
@@ -217,7 +206,6 @@ export interface HostData {
overrideCredentialUsername?: boolean;
forceKeyboardInteractive?: boolean;
jumpHosts?: JumpHostData[];
quickActions?: QuickActionData[];
statusCheckEnabled?: boolean;
/** Seconds between status checks; null follows the global setting. */
statusCheckInterval?: number | null;
@@ -357,13 +345,11 @@ export interface Tab {
// ============================================================================
/**
* What core still keeps in ssh_data.terminal_config. The terminal's look and
* behavior are the ssh-terminal plugin's host settings, and the connection
* options have their own column (sshOptions).
* What 2.8 kept in ssh_data.terminal_config. Core reads none of it any more:
* the terminal's look and behavior and the startup command are plugins' host
* settings, and the connection options have their own column (sshOptions).
*/
export interface HostTerminalConfig {
startupSnippetId?: number | null;
}
export type HostTerminalConfig = Record<string, unknown>;
export type { HostSshOptions } from "@termix/plugin-sdk/frontend";
+7 -14
View File
@@ -7,23 +7,16 @@ export interface KeyCombo {
meta: boolean;
}
export type KeybindingActionType =
| "copy"
| "paste"
| "sendControlCode"
| "sendText"
| "runSnippet"
| "nextTab"
| "previousTab"
| "openCommandPalette"
| "reconnectSession";
/**
* The shell's own actions (nextTab, previousTab, openCommandPalette,
* reconnectSession) or one a plugin declared in contributes.keybindingActions.
*/
export type KeybindingActionType = string;
/** A bound action: its type plus the parameters its declaration lists. */
export interface KeybindingAction {
type: KeybindingActionType;
text?: string;
controlCode?: string;
snippetId?: string;
appendEnter?: boolean;
[param: string]: unknown;
}
export interface CustomKeybinding {
-1
View File
@@ -135,7 +135,6 @@ const CORE_HIDEABLE_RAIL_VIEWS = [
"credentials",
"connections",
"quick-connect",
"macros",
];
const SIMPLE_HIDDEN_RAIL_TABS = CORE_HIDEABLE_RAIL_VIEWS.filter(
+2 -16
View File
@@ -75,7 +75,6 @@ export type Host = {
statusCheckEnabled?: boolean;
/** Seconds between status checks; null follows the global setting. */
statusCheckInterval?: number | null;
quickActions: { name: string; snippetId: string }[];
enableSsh: boolean;
@@ -158,8 +157,6 @@ type KnownTabType =
| "host-manager"
| "user-profile"
| "admin-settings"
// Rail panels that can also open full-width in the main area.
| "macros"
| "split-screen";
/**
@@ -255,8 +252,8 @@ export type UiFontId =
| "source-code-pro"
| "caskaydia-cove";
/** A tools panel view: core's own, or a rail panel a plugin registered. */
export type ToolsTab = "macros" | (string & {});
/** A tools panel view: a rail panel a plugin registered. */
export type ToolsTab = string & {};
/** "row" lays children side by side, "column" stacks them. */
export type SplitDirection = "row" | "column";
@@ -339,17 +336,6 @@ export type WorkspacePayload = {
};
};
export type Snippet = {
id: number;
name: string;
description?: string;
content: string;
folder: string | null;
order: number;
hostIds?: number[];
isNote?: boolean;
};
const FOLDER_ICONS = [
"folder",
"server",
+15 -51
View File
@@ -60,6 +60,11 @@ import type {
CustomKeybinding,
KeybindingActionType,
} from "@/types/keybindings";
import {
GLOBAL_KEYBINDING_EVENT,
getKeybindingAction,
runKeybindingAction,
} from "@/shell/keybinding-registry";
// Shell surfaces that are not needed for first paint.
const CommandPalette = lazy(() =>
@@ -76,11 +81,6 @@ const QuickConnectPanel = lazy(() =>
})),
);
// Secondary rail panels — load on first open, not with the shell critical path.
const MacrosPanel = lazy(() =>
import("@/sidebar/MacrosPanel").then((m) => ({ default: m.MacrosPanel })),
);
const UserProfilePanel = lazy(() =>
import("@/sidebar/UserProfilePanel").then((m) => ({
default: m.UserProfilePanel,
@@ -483,16 +483,7 @@ export function AppShell({
if (cancelled) return;
globalKeybindingsRef.current = parseCustomKeybindings(
prefs.customKeybindings,
).filter(
(binding) =>
binding.enabled &&
[
"nextTab",
"previousTab",
"openCommandPalette",
"reconnectSession",
].includes(binding.action.type),
);
).filter((binding) => binding.enabled);
})
.catch(() => {});
};
@@ -535,14 +526,17 @@ export function AppShell({
event.target.closest("[data-keybinding-recorder]")
)
return;
// Only actions that run anywhere; a terminal's own are its to handle.
const binding = findMatchingKeybinding(
event,
globalKeybindingsRef.current,
globalKeybindingsRef.current.filter(
(entry) => getKeybindingAction(entry.action.type)?.scope === "global",
),
);
if (!binding) return;
event.preventDefault();
event.stopPropagation();
runAction(binding.action.type);
runKeybindingAction(binding.action);
};
const handleAction = (event: Event) =>
runAction(
@@ -550,10 +544,10 @@ export function AppShell({
);
window.addEventListener("keydown", handleKeyDown, true);
window.addEventListener("termix:global-keybinding", handleAction);
window.addEventListener(GLOBAL_KEYBINDING_EVENT, handleAction);
return () => {
window.removeEventListener("keydown", handleKeyDown, true);
window.removeEventListener("termix:global-keybinding", handleAction);
window.removeEventListener(GLOBAL_KEYBINDING_EVENT, handleAction);
};
}, []);
useEffect(() => {
@@ -648,7 +642,7 @@ export function AppShell({
setActiveTabId(splitTabId);
};
// Panels like history and snippets act on "the terminal you're working in".
// Panels that type into a terminal act on "the terminal you're working in".
// Once those panels can themselves be the active tab, activeTabId points at
// the panel and the lookup misses, so remember the last terminal instead.
// In a split, the tab being worked in is the focused pane's.
@@ -990,7 +984,6 @@ export function AppShell({
"hostTrayOnClick",
"pinAppRail",
"expandAppRailOnHover",
"confirmSnippetExecution",
"disableUpdateCheck",
"confirmTabClose",
"hiddenRailTabs",
@@ -1049,14 +1042,6 @@ export function AppShell({
);
window.dispatchEvent(new Event("expandAppRailOnHoverChanged"));
}
if (
prefs.confirmSnippetExecution !== null &&
prefs.confirmSnippetExecution !== undefined
)
localStorage.setItem(
"confirmSnippetExecution",
String(prefs.confirmSnippetExecution),
);
if (
prefs.disableUpdateCheck !== null &&
prefs.disableUpdateCheck !== undefined
@@ -2518,7 +2503,7 @@ export function AppShell({
]);
}
// What history, snippets and macros should act on. Falls back to the remembered
// What command-target panels act on. Falls back to the remembered
// terminal when the active tab isn't one, and drops it once it's closed.
const targetTerminalTabId = terminalTabs.some((t) => t.id === workingTabId)
? workingTabId
@@ -2641,18 +2626,6 @@ export function AppShell({
/>
)}
{railView === "macros" && (
<div className="flex-1 min-h-0 overflow-y-auto">
<MacrosPanel
terminalTabs={terminalTabs}
activeTabId={targetTerminalTabId}
storageMode={
userPrefs.storageMode === "cloud" ? "cloud" : "local"
}
/>
</div>
)}
{registeredPanels.map((panel) => {
const shown = railView === panel.id;
// A kept-mounted panel lives in the owning dock only, so two live
@@ -2743,7 +2716,6 @@ export function AppShell({
authType: "none",
enableSsh: false,
sshPort: 22,
quickActions: [],
};
void invokeAction("terminal.open", host, {
joinSharedSessionId: session.sessionId,
@@ -3123,14 +3095,6 @@ export function AppShell({
isVisible: inPane || activeInline,
isFocusedPane,
inSplit: inPane,
panelProps: {
terminalTabs,
targetTerminalTabId,
storageMode:
userPrefs.storageMode === "cloud"
? "cloud"
: "local",
},
}),
tabNode,
tab.id,
-2
View File
@@ -127,14 +127,12 @@ export interface UserPreferences {
pinAppRail?: boolean | null;
expandAppRailOnHover?: boolean | null;
showPinAppRailButton?: boolean | null;
confirmSnippetExecution?: boolean | null;
disableUpdateCheck?: boolean | null;
confirmTabClose?: boolean | null;
hiddenRailTabs?: string | null;
compactHostView?: boolean | null;
statusColorScheme?: string | null;
customKeybindings?: string | null;
terminalMacros?: string | null;
}
export function parseCustomKeybindings(
@@ -1,104 +0,0 @@
import { useState, useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/button";
import { Input } from "@/components/input";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@/components/dialog";
import {
extractSnippetInputs,
resolveSnippetContent,
type SnippetHostContext,
} from "@/lib/snippet-variables";
import type { Snippet } from "@/types/ui-types";
/**
* Shown before running a snippet that contains $INPUT_n placeholders --
* collects a value per placeholder and previews the fully resolved command
* (host vars + inputs) before handing the result back to the caller.
*/
export function SnippetVariablesDialog({
snippet,
host,
onCancel,
onConfirm,
}: {
snippet: Snippet;
host: SnippetHostContext | null;
onCancel: () => void;
onConfirm: (
resolvedContent: string,
inputValues: Record<string, string>,
) => void;
}) {
const { t } = useTranslation();
const inputs = useMemo(
() => extractSnippetInputs(snippet.content),
[snippet.content],
);
const [values, setValues] = useState<Record<string, string>>({});
useEffect(() => {
setValues({});
}, [snippet]);
const preview = resolveSnippetContent(snippet.content, host, values);
return (
<Dialog open onOpenChange={(v) => !v && onCancel()}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="text-lg font-bold">
{t("newUi.sidebar.snippets.variablesDialogTitle", {
name: snippet.name,
})}
</DialogTitle>
<DialogDescription className="text-xs text-muted-foreground">
{t("newUi.sidebar.snippets.variablesDialogDescription")}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 mt-1">
{inputs.map((input) => (
<div key={input.key} className="flex flex-col gap-1.5">
<label className="text-xs font-semibold">{input.label}</label>
<Input
autoFocus={inputs[0]?.key === input.key}
value={values[input.key] ?? ""}
onChange={(e) =>
setValues((prev) => ({
...prev,
[input.key]: e.target.value,
}))
}
/>
</div>
))}
<div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold text-muted-foreground">
{t("newUi.sidebar.snippets.variablesPreviewLabel")}
</label>
<span className="text-xs text-muted-foreground font-mono px-2.5 py-2 border border-border bg-muted/20 min-w-0 break-all whitespace-pre-wrap">
{preview}
</span>
</div>
</div>
<div className="flex items-center justify-end gap-2 mt-2">
<Button variant="ghost" onClick={onCancel}>
{t("newUi.sidebar.snippets.cancel")}
</Button>
<Button
variant="outline"
className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
onClick={() => onConfirm(preview, values)}
>
{t("newUi.sidebar.snippets.variablesConfirmButton")}
</Button>
</div>
</DialogContent>
</Dialog>
);
}
-130
View File
@@ -1,130 +0,0 @@
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useConfirmation } from "@/hooks/use-confirmation.ts";
import { invokeAction } from "@/shell/action-registry";
import { SnippetVariablesDialog } from "@/components/SnippetVariablesDialog";
import type { Snippet, Tab } from "@/types/ui-types";
interface ResolvedForTerminal {
needsInputs: boolean;
content: string;
isNote: boolean;
}
/**
* Shared "run this snippet against these terminal tabs" flow, for the few
* core surfaces (the command palette) that still reach for a snippet
* directly rather than through the snippets plugin's own panel. Variable
* resolution runs through the snippets plugin's "snippet.resolveForTerminal"
* action, and sending runs through ssh-terminal's "terminal.sendToSession",
* since core no longer owns snippet content or a terminal ref.
*/
export function useSnippetRunner() {
const { t } = useTranslation();
const { confirmWithToast } = useConfirmation();
const [runningSnippet, setRunningSnippet] = useState<{
snippet: Snippet;
host: Tab["host"] | null;
onConfirm: (
resolvedContent: string,
inputValues: Record<string, string>,
) => void;
} | null>(null);
const handleConfirmRun = useCallback(
(snippet: Snippet, execute: () => void) => {
const shouldConfirm =
localStorage.getItem("confirmSnippetExecution") === "true";
if (!shouldConfirm) {
execute();
return;
}
confirmWithToast(
t("newUi.sidebar.snippets.confirmRunMessage", { name: snippet.name }),
execute,
t("newUi.sidebar.snippets.confirmRunButton"),
t("newUi.sidebar.snippets.cancel"),
{ confirmOnEnter: true, duration: 6000 },
);
},
[confirmWithToast, t],
);
async function sendResolvedToTerminal(
tab: Tab,
snippet: Snippet,
inputValues: Record<string, string>,
) {
const resolved = (await invokeAction(
"snippet.resolveForTerminal",
snippet.id,
tab.host ?? null,
inputValues,
)) as ResolvedForTerminal | null | undefined;
if (!resolved || resolved.needsInputs) return;
await invokeAction("terminal.sendToSession", tab.id, resolved.content, {
run: !resolved.isNote,
});
}
const runSnippet = useCallback(
(snippet: Snippet, targets: Tab[]) => {
const runWithInputs = (inputValues: Record<string, string>) => {
const doSend = () => {
void Promise.all(
targets.map((tab) =>
sendResolvedToTerminal(tab, snippet, inputValues),
),
).then(() => {
toast.success(
t(
snippet.isNote
? "newUi.sidebar.snippets.pasteSuccess"
: "newUi.sidebar.snippets.runSuccess",
{ name: snippet.name, count: targets.length },
),
);
});
};
if (snippet.isNote) {
doSend();
} else {
handleConfirmRun(snippet, doSend);
}
};
void invokeAction(
"snippet.resolveForTerminal",
snippet.id,
targets[0]?.host ?? null,
).then((result) => {
const resolved = result as ResolvedForTerminal | null | undefined;
if (resolved?.needsInputs) {
setRunningSnippet({
snippet,
host: targets[0]?.host ?? null,
onConfirm: (_resolvedContent, inputValues) => {
setRunningSnippet(null);
runWithInputs(inputValues);
},
});
return;
}
runWithInputs({});
});
},
[handleConfirmRun, t],
);
const dialog = runningSnippet ? (
<SnippetVariablesDialog
snippet={runningSnippet.snippet}
host={runningSnippet.host}
onCancel={() => setRunningSnippet(null)}
onConfirm={runningSnippet.onConfirm}
/>
) : null;
return { runSnippet, handleConfirmRun, dialog };
}
-106
View File
@@ -1,106 +0,0 @@
import type { KeyCombo } from "@/types/keybindings";
export interface DefaultKeybindingInfo {
id: string;
combo: KeyCombo;
descriptionKey: string;
}
export const BUILT_IN_DEFAULTS: DefaultKeybindingInfo[] = [
{
id: "default-copy-ctrlc",
combo: {
key: "c",
isCode: false,
ctrl: true,
alt: false,
shift: false,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.copyCtrlC",
},
{
id: "default-copy-ctrlshiftc",
combo: {
key: "c",
isCode: false,
ctrl: true,
alt: false,
shift: true,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.copyCtrlShiftC",
},
{
id: "default-copy-cmdc",
combo: {
key: "c",
isCode: false,
ctrl: false,
alt: false,
shift: false,
meta: true,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.copyCmdC",
},
{
id: "default-paste-ctrlshiftv",
combo: {
key: "v",
isCode: false,
ctrl: true,
alt: false,
shift: true,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.pasteCtrlShiftV",
},
{
id: "default-ctrlaltw",
combo: {
key: "w",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.ctrlW",
},
{
id: "default-ctrlaltt",
combo: {
key: "t",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.ctrlT",
},
{
id: "default-ctrlaltn",
combo: {
key: "n",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.ctrlN",
},
{
id: "default-ctrlaltq",
combo: {
key: "q",
isCode: false,
ctrl: true,
alt: true,
shift: false,
meta: false,
},
descriptionKey: "newUi.sidebar.keybindings.builtIn.ctrlQ",
},
];
-22
View File
@@ -1,22 +0,0 @@
import { invokeAction } from "@/shell/action-registry";
/** A snippet as core's pickers show it. */
export interface SnippetRow {
id: number;
name: string;
content: string;
folder?: string | null;
isNote?: boolean;
}
/**
* The user's snippets, from whichever plugin provides them through the
* "snippet.list" action. Empty while no plugin does. An admin editing
* another user's host passes that user's id.
*/
export async function listSnippets(options?: {
targetUserId?: string;
}): Promise<SnippetRow[]> {
const rows = await invokeAction("snippet.list", options);
return Array.isArray(rows) ? (rows as SnippetRow[]) : [];
}
-70
View File
@@ -1,70 +0,0 @@
export interface SnippetInput {
key: string;
label: string;
}
export interface SnippetHostContext {
ip?: string;
username?: string;
port?: number | string;
name?: string;
}
const INPUT_PATTERN =
/\$\{INPUT_(\d+)(?::([^}$]+))?\}|\$INPUT_(\d+)(?![a-zA-Z0-9_])/g;
export function extractSnippetInputs(content: string): SnippetInput[] {
const seen = new Map<string, SnippetInput>();
let match: RegExpExecArray | null;
INPUT_PATTERN.lastIndex = 0;
while ((match = INPUT_PATTERN.exec(content)) !== null) {
const digits = match[1] ?? match[3];
const key = `INPUT_${digits}`;
if (!seen.has(key)) {
seen.set(key, { key, label: match[2]?.trim() || `Input ${digits}` });
}
}
return Array.from(seen.values());
}
function replaceVar(
content: string,
name: string,
value: string | undefined,
): string {
if (value === undefined) return content;
const pattern = new RegExp(`\\$\\{?${name}\\}?`, "g");
return content.replace(pattern, value);
}
export function resolveSnippetContent(
content: string,
host: SnippetHostContext | null,
inputValues: Record<string, string> = {},
): string {
let resolved = content;
resolved = replaceVar(resolved, "HOST", host?.ip);
resolved = replaceVar(resolved, "USER", host?.username);
resolved = replaceVar(
resolved,
"PORT",
host?.port !== undefined ? String(host.port) : undefined,
);
resolved = replaceVar(resolved, "NAME", host?.name);
resolved = resolved.replace(
INPUT_PATTERN,
(
fullMatch,
braceDigits: string | undefined,
_label,
plainDigits: string | undefined,
) => {
const key = `INPUT_${braceDigits ?? plainDigits}`;
return key in inputValues ? inputValues[key] : fullMatch;
},
);
return resolved;
}
-291
View File
@@ -1,291 +0,0 @@
export type MacroStep =
| { id: string; type: "send"; text: string; pressEnter: boolean }
| { id: string; type: "delay"; milliseconds: number }
| {
id: string;
type: "wait";
pattern: string;
isRegex?: boolean;
flags?: string;
timeoutMs: number;
onTimeout: "stop" | "continue";
}
| {
id: string;
type: "if";
pattern: string;
isRegex?: boolean;
flags?: string;
then: MacroStep[];
else: MacroStep[];
}
| { id: string; type: "repeat"; count: number; steps: MacroStep[] };
export interface TerminalMacro {
id: string;
name: string;
description?: string;
steps: MacroStep[];
createdAt: string;
updatedAt: string;
}
export interface MacroTerminalAdapter {
send(data: string): void;
subscribe(listener: (data: string) => void): () => void;
}
export type MacroRunEvent =
| { type: "step"; stepId: string }
| { type: "matched"; stepId: string; match: string }
| { type: "timeout"; stepId: string }
| { type: "complete" };
const MAX_OUTPUT = 100_000;
function bounded(value: number, minimum: number, maximum: number): number {
return Number.isFinite(value)
? Math.min(maximum, Math.max(minimum, Math.floor(value)))
: minimum;
}
function escapeLiteral(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function regex(pattern: string, flags = "", isRegex = true): RegExp {
const source = isRegex ? pattern : escapeLiteral(pattern);
return new RegExp(source, flags.replace(/[gy]/g, ""));
}
// Literal text is the default in the editor, so a bad regex should never kill a
// run. Fall back to matching the pattern literally instead.
function safeRegex(pattern: string, flags = "", isRegex = true): RegExp {
try {
return regex(pattern, flags, isRegex);
} catch {
return new RegExp(escapeLiteral(pattern), flags.replace(/[gy]/g, ""));
}
}
function abortError(): DOMException {
return new DOMException("Macro cancelled", "AbortError");
}
function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
if (signal?.aborted) return Promise.reject(abortError());
return new Promise((resolve, reject) => {
const timer = setTimeout(resolve, milliseconds);
signal?.addEventListener(
"abort",
() => {
clearTimeout(timer);
reject(abortError());
},
{ once: true },
);
});
}
export async function runTerminalMacro(
macro: TerminalMacro,
terminal: MacroTerminalAdapter,
options: {
signal?: AbortSignal;
onEvent?: (event: MacroRunEvent) => void;
} = {},
): Promise<void> {
let output = "";
let scanFrom = 0;
let wake: (() => void) | null = null;
const unsubscribe = terminal.subscribe((data) => {
const combined = output + data;
const removed = Math.max(0, combined.length - MAX_OUTPUT);
output = combined.slice(removed);
scanFrom = Math.max(0, scanFrom - removed);
wake?.();
});
const runSteps = async (steps: MacroStep[]): Promise<void> => {
for (const step of steps) {
if (options.signal?.aborted) throw abortError();
options.onEvent?.({ type: "step", stepId: step.id });
if (step.type === "send") {
scanFrom = output.length;
terminal.send(step.text + (step.pressEnter ? "\r" : ""));
continue;
}
if (step.type === "delay") {
await delay(bounded(step.milliseconds, 0, 300_000), options.signal);
continue;
}
if (step.type === "if") {
await runSteps(
safeRegex(step.pattern, step.flags, step.isRegex !== false).test(
output,
)
? step.then
: step.else,
);
continue;
}
if (step.type === "repeat") {
const count = bounded(step.count, 1, 100);
for (let index = 0; index < count; index += 1) {
await runSteps(step.steps);
}
continue;
}
const matcher = safeRegex(
step.pattern,
step.flags,
step.isRegex !== false,
);
const deadline = Date.now() + bounded(step.timeoutMs, 100, 300_000);
let match = matcher.exec(output.slice(scanFrom));
while (!match && Date.now() < deadline) {
await Promise.race([
new Promise<void>((resolve) => {
wake = resolve;
}),
delay(Math.min(250, deadline - Date.now()), options.signal),
]);
wake = null;
match = matcher.exec(output.slice(scanFrom));
}
if (match) {
scanFrom += match.index + match[0].length;
options.onEvent?.({
type: "matched",
stepId: step.id,
match: match[0],
});
} else {
options.onEvent?.({ type: "timeout", stepId: step.id });
if (step.onTimeout === "stop") {
throw new Error(`Timed out waiting for /${step.pattern}/`);
}
}
}
};
try {
await runSteps(macro.steps);
options.onEvent?.({ type: "complete" });
} finally {
unsubscribe();
}
}
export function parseTerminalMacros(value?: string | null): TerminalMacro[] {
if (!value) return [];
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) return [];
let remainingSteps = 500;
const sanitizeSteps = (input: unknown, depth: number): MacroStep[] => {
if (!Array.isArray(input) || depth > 4 || remainingSteps <= 0) return [];
return input.flatMap((raw): MacroStep[] => {
if (!raw || typeof raw !== "object" || remainingSteps-- <= 0) return [];
const step = raw as Record<string, unknown>;
const stepId =
typeof step.id === "string" ? step.id : `step-${remainingSteps}`;
if (step.type === "send") {
return [
{
id: stepId,
type: "send",
text:
typeof step.text === "string" ? step.text.slice(0, 65_536) : "",
pressEnter: step.pressEnter !== false,
},
];
}
if (step.type === "delay") {
return [
{
id: stepId,
type: "delay",
milliseconds: bounded(Number(step.milliseconds), 0, 300_000),
},
];
}
if (step.type === "wait") {
return [
{
id: stepId,
type: "wait",
pattern:
typeof step.pattern === "string"
? step.pattern.slice(0, 1000)
: "",
isRegex: step.isRegex !== false,
flags:
typeof step.flags === "string"
? step.flags.replace(/[^imsu]/g, "")
: undefined,
timeoutMs: bounded(Number(step.timeoutMs), 100, 300_000),
onTimeout: step.onTimeout === "continue" ? "continue" : "stop",
},
];
}
if (step.type === "if") {
return [
{
id: stepId,
type: "if",
pattern:
typeof step.pattern === "string"
? step.pattern.slice(0, 1000)
: "",
isRegex: step.isRegex !== false,
flags:
typeof step.flags === "string"
? step.flags.replace(/[^imsu]/g, "")
: undefined,
then: sanitizeSteps(step.then, depth + 1),
else: sanitizeSteps(step.else, depth + 1),
},
];
}
if (step.type === "repeat") {
return [
{
id: stepId,
type: "repeat",
count: bounded(Number(step.count), 1, 100),
steps: sanitizeSteps(step.steps, depth + 1),
},
];
}
return [];
});
};
return parsed.slice(0, 100).flatMap((raw): TerminalMacro[] => {
if (!raw || typeof raw !== "object") return [];
const macro = raw as Record<string, unknown>;
if (typeof macro.id !== "string" || typeof macro.name !== "string")
return [];
const now = new Date().toISOString();
return [
{
id: macro.id,
name: macro.name.slice(0, 200),
description:
typeof macro.description === "string"
? macro.description.slice(0, 2000)
: undefined,
steps: sanitizeSteps(macro.steps, 0),
createdAt:
typeof macro.createdAt === "string" ? macro.createdAt : now,
updatedAt:
typeof macro.updatedAt === "string" ? macro.updatedAt : now,
},
];
});
} catch {
return [];
}
}
+5 -240
View File
@@ -294,7 +294,6 @@
"multiPanelHintDockHow": "Right-click its rail icon, or use the panel icon in the tab bar.",
"sidebarSettings": "Sidebar Settings...",
"hosts": "Hosts",
"macros": "Macros",
"hostManager": "Host Manager",
"credentials": "Credentials",
"connections": "Connections",
@@ -711,7 +710,6 @@
"groupTags": "Tags & pin",
"groupProxy": "Proxy",
"groupJumpHosts": "Jump hosts",
"groupQuickActions": "Quick actions",
"groupFeatureFlags": "Feature flags",
"groupAdvanced": "Advanced config",
"preview": "Preview",
@@ -916,9 +914,6 @@
"userProfileDesc": "Manage your account and preferences",
"addCredential": "Add Credential",
"addCredentialDesc": "Store SSH keys or passwords",
"snippets": "Snippets",
"runSnippetDesc": "Run in the active terminal tab",
"noSnippetsFound": "No snippets found matching \"{{search}}\"",
"recentActivity": "Recent Activity",
"serversAndHosts": "Servers & Hosts",
"noHostsFound": "No hosts found matching \"{{search}}\"",
@@ -926,7 +921,8 @@
"navigate": "Navigate",
"select": "Select",
"toggleWith": "Toggle with",
"pluginActions": "More actions"
"pluginActions": "More actions",
"noTargetTab": "No terminal tab is open"
},
"splitScreen": {
"title": "Split screen",
@@ -1723,122 +1719,9 @@
"sshProtocol": "SSH",
"noTarget": "Nothing installed can connect to this host type"
},
"snippets": {
"title": "Snippets",
"createSnippetTitle": "Create Snippet",
"createSnippetDescription": "Create a new command snippet for quick execution",
"nameLabel": "Name",
"namePlaceholder": "e.g., Restart Nginx",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Optional description",
"optional": "Optional",
"folderLabel": "Folder",
"noFolder": "No folder (Uncategorized)",
"commandLabel": "Command",
"commandPlaceholder": "e.g., sudo systemctl restart nginx",
"typeLabel": "Type",
"typeCommand": "Command",
"typeNote": "Note",
"noteLabel": "Note",
"notePlaceholder": "e.g., a reusable prompt or reminder text",
"cancel": "Cancel",
"createSnippetButton": "Create Snippet",
"createFolderTitle": "Create Folder",
"createFolderDescription": "Organize your snippets into folders",
"folderNameLabel": "Folder Name",
"folderNamePlaceholder": "e.g., System Commands, Docker Scripts",
"folderColorLabel": "Folder Color",
"folderIconLabel": "Folder Icon",
"previewLabel": "Preview",
"folderNameFallback": "Folder Name",
"createFolderButton": "Create Folder",
"targetTerminals": "Target Terminals",
"selectAll": "All",
"selectNone": "None",
"noTerminalTabsOpen": "No terminal tabs open",
"searchPlaceholder": "Search snippets...",
"newSnippet": "New Snippet",
"newFolder": "New Folder",
"run": "Run",
"noSnippetsInFolder": "No snippets in this folder",
"uncategorized": "Uncategorized",
"editSnippetTitle": "Edit Snippet",
"editSnippetDescription": "Update this command snippet",
"saveSnippetButton": "Save Changes",
"createSuccess": "Snippet created successfully",
"createFailed": "Failed to create snippet",
"updateSuccess": "Snippet updated successfully",
"updateFailed": "Failed to update snippet",
"deleteFailed": "Failed to delete snippet",
"folderCreateSuccess": "Folder created successfully",
"folderCreateFailed": "Failed to create folder",
"editFolderTitle": "Edit Folder",
"editFolderDescription": "Rename or change the appearance of this folder",
"saveFolderButton": "Save Changes",
"editFolder": "Edit folder",
"deleteFolder": "Delete folder",
"folderDeleteSuccess": "Folder \"{{name}}\" deleted",
"folderDeleteFailed": "Failed to delete folder",
"folderEditSuccess": "Folder updated successfully",
"folderEditFailed": "Failed to update folder",
"confirmRunMessage": "Run \"{{name}}\"?",
"confirmRunButton": "Run",
"runSuccess": "Ran \"{{name}}\" in {{count}} terminal(s)",
"pasteSuccess": "Pasted \"{{name}}\" into {{count}} terminal(s)",
"pasteToTerminal": "Paste",
"copySuccess": "Copied \"{{name}}\" to clipboard",
"shareTitle": "Share Snippet",
"shareUser": "User",
"shareRole": "Role",
"selectUser": "Select a user...",
"selectRole": "Select a role...",
"shareSuccess": "Snippet shared successfully",
"shareFailed": "Failed to share snippet",
"revokeSuccess": "Access revoked",
"revokeFailed": "Failed to revoke access",
"currentAccess": "Current Access",
"shareLoadError": "Failed to load share data",
"loading": "Loading...",
"close": "Close",
"reorderFailed": "Failed to save snippet order",
"importExport": "Import / Export",
"exportBtn": "Export JSON",
"importBtn": "Import JSON",
"exportSuccess": "Snippets exported successfully",
"exportFailed": "Failed to export snippets",
"importTitle": "Import Snippets",
"importDescription": "Import snippets and folders from a JSON file exported by Termix",
"importDropOrClick": "Drop a JSON file here or click to browse",
"importSelectedFile": "Selected: {{name}}",
"importOverwrite": "Overwrite existing snippets with the same name and folder",
"importStartBtn": "Import",
"importSuccess": "Import complete: {{snippets}} snippet(s) added, {{updated}} updated, {{skipped}} skipped, {{folders}} folder(s) added",
"importFailed": "Failed to import snippets",
"importInvalidFile": "Invalid file: expected a JSON object with snippets or folders arrays",
"targetHostsLabel": "Target Hosts",
"targetHostsHint": "Assign hosts to run this snippet directly without an open terminal.",
"noHostsAvailable": "No hosts configured",
"clearTargetHosts": "Clear all",
"hasTargetHosts": "Has target hosts",
"runOnTargets": "Run on Targets",
"directRunSuccess": "Ran \"{{name}}\" on {{count}} host(s)",
"directRunPartialFail": "\"{{name}}\" failed on one or more hosts",
"executionResultTitle": "Execution Results: {{name}}",
"executionResultDescription": "Output from running the snippet on each target host.",
"executionSuccess": "Success",
"executionFailed": "Failed",
"expandAll": "Expand all folders",
"collapseAll": "Collapse all folders",
"settings": "Snippet settings",
"variablesHint": "Variables: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Run \"{{name}}\"",
"variablesDialogDescription": "Fill in the values this snippet needs before running it.",
"variablesPreviewLabel": "Preview",
"variablesConfirmButton": "Run"
},
"keybindings": {
"title": "Keyboard Shortcuts",
"description": "Customize terminal copy, paste, and control shortcuts, or bind keys to send text or run a snippet.",
"description": "Bind keys to actions in the terminal and the app, or change the built-in shortcuts.",
"defaultsHeading": "Built-in Shortcuts",
"customHeading": "Custom Shortcuts",
"addBinding": "Add Shortcut",
@@ -1859,37 +1742,14 @@
"pressKeysPlaceholder": "Click and press keys",
"recording": "Press keys...",
"comboRequiredError": "Press a key combination first",
"textRequiredError": "Enter the text to send",
"controlCodeRequiredError": "Enter a single letter for the control code",
"snippetRequiredError": "Select a snippet",
"conflictWarning": "This combination is already bound to {{combo}}. Saving will make both fire.",
"actionLabel": "Action",
"actionCopy": "Copy selection",
"actionPaste": "Paste",
"actionSendControlCode": "Send Ctrl+letter signal",
"actionSendText": "Send text",
"actionRunSnippet": "Run existing snippet",
"actionNextTab": "Switch to next tab",
"actionPreviousTab": "Switch to previous tab",
"actionOpenCommandPalette": "Open quick search",
"actionReconnectSession": "Reconnect current session",
"controlCodeLabel": "Letter",
"textLabel": "Text to send",
"appendEnterLabel": "Press Enter after sending",
"snippetLabel": "Snippet",
"selectSnippetPlaceholder": "Select a snippet",
"orphanedSnippetWarning": "snippet not found",
"clipboardPermissionNote": "May prompt for clipboard permission on first use in some browsers.",
"builtIn": {
"copyCtrlC": "Copy selection (Ctrl+C, only when text is selected)",
"copyCtrlShiftC": "Copy selection (Ctrl+Shift+C)",
"copyCmdC": "Copy selection (Cmd+C)",
"pasteCtrlShiftV": "Paste from clipboard (Ctrl+Shift+V)",
"ctrlW": "Send Ctrl+W to shell (blocked from closing browser tab)",
"ctrlT": "Send Ctrl+T to shell (blocked from opening browser tab)",
"ctrlN": "Send Ctrl+N to shell (blocked from opening browser window)",
"ctrlQ": "Send Ctrl+Q to shell (blocked from quitting browser)"
}
"unavailableAction": "{{type}} (not available)",
"actionRequiredError": "Pick an action first"
},
"userProfile": {
"donateTitle": "Keep Termix alive",
@@ -2085,101 +1945,6 @@
}
},
"terminalToolbar": {},
"macros": {
"title": "Macros",
"description": "Run a saved series of commands in a terminal, and react to what the server sends back.",
"target": "Target",
"noTerminal": "No terminal open",
"empty": "No macros yet",
"emptyHint": "Create a macro to automate a terminal flow, like logging in, restarting a service, or answering prompts.",
"create": "New macro",
"back": "Back to macros",
"name": "Name",
"namePlaceholder": "Restart web server",
"nameRequired": "Give the macro a name before saving",
"descriptionLabel": "Description",
"descriptionPlaceholder": "What this macro does (optional)",
"unsaved": "Unsaved changes",
"save": "Save",
"saved": "Macro saved",
"saveFailed": "Could not save the macro",
"delete": "Delete",
"deleteTitle": "Delete this macro?",
"deleteBody": "\"{{name}}\" will be permanently deleted. This cannot be undone.",
"deleted": "Macro deleted",
"cancel": "Cancel",
"run": "Run",
"stop": "Stop",
"running": "Running...",
"completed": "Macro finished",
"failed": "Macro failed",
"loadFailed": "Could not load macros",
"needTerminal": "Open and connect a terminal first",
"noSteps": "This macro has no steps yet. Add one below.",
"addStep": "Add step",
"stepNumber": "Step {{number}}",
"removeStep": "Remove step",
"confirmRunTitle": "Run this macro?",
"confirmRunBody": "\"{{name}}\" will send commands to {{host}}.",
"confirmRunAction": "Run macro",
"stepTypes": {
"send": {
"label": "Send command",
"help": "Types text into the terminal, the same as if you typed it."
},
"wait": {
"label": "Wait for output",
"help": "Pauses until the server prints the text you name, like a password prompt."
},
"delay": {
"label": "Pause",
"help": "Waits a fixed amount of time before the next step."
},
"if": {
"label": "If output contains",
"help": "Checks everything the server has printed so far, then runs one of two branches."
},
"repeat": {
"label": "Repeat",
"help": "Runs the steps inside it a set number of times."
}
},
"send": {
"text": "Command or text",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Press Enter after sending",
"pressEnterHelp": "Turn this off to type text without submitting it."
},
"wait": {
"pattern": "Wait until the output contains",
"patternPlaceholder": "password:",
"timeout": "Give up after",
"onTimeout": "If it never appears",
"stop": "Stop the macro",
"continue": "Keep going anyway"
},
"delay": {
"duration": "Wait for"
},
"ifStep": {
"pattern": "If the output contains",
"patternPlaceholder": "Active: active (running)",
"then": "Then do this",
"else": "Otherwise do this",
"emptyBranch": "No steps"
},
"repeat": {
"count": "Number of times",
"times": "times"
},
"match": {
"useRegex": "Match with a regular expression",
"useRegexHelp": "Leave this off to match the text exactly as written.",
"invalidRegex": "Not a valid regular expression, it will be matched as plain text"
},
"seconds": "seconds",
"secondsShort": "{{count}}s"
},
"plugins": {
"capabilities": {
"credentials:read": {
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Sybalkinstellings...",
"hosts": "Gashere",
"snippets": "Brokkies",
"macros": "Makro's",
"hostManager": "Gasheerbestuurder",
"credentials": "Geloofsbriewe",
"connections": "Verbindings",
@@ -869,7 +868,6 @@
"groupTags": "Etikette en pen",
"groupTunnels": "Tonnels",
"groupJumpHosts": "Spring-gashere",
"groupQuickActions": "Vinnige aksies",
"groupFeatureFlags": "Kenmerkvlae",
"groupAdvanced": "Gevorderde konfigurasie",
"preview": "Voorskou",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Stoor SSH-sleutels of wagwoorde",
"tmuxMonitor": "Tmux-monitor",
"tmuxMonitorDesc": "Monitor tmux-sessies oor jou gashere",
"snippets": "Brokkies",
"runSnippetDesc": "Begin in die aktiewe terminaal-oortjie",
"noSnippetsFound": "Geen brokkies gevind wat ooreenstem met \"{{search}}\"",
"recentActivity": "Onlangse Aktiwiteit",
"serversAndHosts": "Bedieners en gashere",
"noHostsFound": "Geen gashere gevind wat ooreenstem met \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Vorige oortjie",
"hotkeysJumpTab": "Spring na oortjie"
},
"snippets": {
"title": "Brokkies",
"createSnippetTitle": "Skep brokkie",
"createSnippetDescription": "Skep 'n nuwe opdragbrokkie vir vinnige uitvoering",
"nameLabel": "Naam",
"namePlaceholder": "bv., Herbegin Nginx",
"descriptionLabel": "Beskrywing",
"descriptionPlaceholder": "Opsionele beskrywing",
"optional": "Opsioneel",
"folderLabel": "Vouer",
"noFolder": "Geen vouer nie (Ongekategoriseerd)",
"commandLabel": "Bevel",
"commandPlaceholder": "bv. sudo systemctl herbegin nginx",
"typeLabel": "Tipe",
"typeCommand": "Bevel",
"typeNote": "Nota",
"noteLabel": "Nota",
"notePlaceholder": "bv. 'n herbruikbare aanwysings- of herinneringteks",
"cancel": "Kanselleer",
"createSnippetButton": "Skep brokkie",
"createFolderTitle": "Skep vouer",
"createFolderDescription": "Organiseer jou brokkies in dopgehou",
"folderNameLabel": "Vouernaam",
"folderNamePlaceholder": "bv. Stelselopdragte, Docker-skripte",
"folderColorLabel": "Vouerkleur",
"folderIconLabel": "Vouer-ikoon",
"previewLabel": "Voorskou",
"folderNameFallback": "Vouernaam",
"createFolderButton": "Skep vouer",
"targetTerminals": "Teikenterminale",
"selectAll": "Alles",
"selectNone": "Geen",
"noTerminalTabsOpen": "Geen terminaal-oortjies oop nie",
"searchPlaceholder": "Soek brokkies...",
"newSnippet": "Nuwe brokkie",
"newFolder": "Nuwe vouer",
"run": "Hardloop",
"noSnippetsInFolder": "Geen brokkies in hierdie vouer nie",
"uncategorized": "Ongekategoriseerd",
"editSnippetTitle": "Wysig brokkie",
"editSnippetDescription": "Dateer hierdie opdragbrokkie op",
"saveSnippetButton": "Stoor veranderinge",
"createSuccess": "Brokkie suksesvol geskep",
"createFailed": "Kon nie brokkie skep nie",
"updateSuccess": "Brokkie suksesvol opgedateer",
"updateFailed": "Kon nie die brokkie opdateer nie",
"deleteFailed": "Kon nie fragment uitvee nie",
"folderCreateSuccess": "Vouer suksesvol geskep",
"folderCreateFailed": "Kon nie vouer skep nie",
"editFolderTitle": "Wysig vouer",
"editFolderDescription": "Hernoem of verander die voorkoms van hierdie vouer",
"saveFolderButton": "Stoor veranderinge",
"editFolder": "Wysig vouer",
"deleteFolder": "Vee vouer uit",
"folderDeleteSuccess": "Lêer \"{{name}}\" is uitgevee",
"folderDeleteFailed": "Kon nie vouer uitvee nie",
"folderEditSuccess": "Vouer suksesvol opgedateer",
"folderEditFailed": "Kon nie vouer opdateer nie",
"confirmRunMessage": "Begin \"{{name}}\"?",
"confirmRunButton": "Hardloop",
"runSuccess": "Het \"{{name}}\" in {{count}} terminaal(e) uitgevoer",
"pasteSuccess": "Het \"{{name}}\" in {{count}} terminaal(e) geplak",
"pasteToTerminal": "Plak",
"copySuccess": "\"{{name}}\" na knipbord gekopieer",
"shareTitle": "Deel brokkie",
"shareUser": "Gebruiker",
"shareRole": "Rol",
"selectUser": "Kies 'n gebruiker...",
"selectRole": "Kies 'n rol...",
"shareSuccess": "Brokkie suksesvol gedeel",
"shareFailed": "Kon nie fragment deel nie",
"revokeSuccess": "Toegang herroep",
"revokeFailed": "Kon nie toegang herroep nie",
"currentAccess": "Huidige Toegang",
"shareLoadError": "Kon nie deeldata laai nie",
"loading": "Laai tans...",
"close": "Maak toe",
"reorderFailed": "Kon nie die volgorde van die brokkie stoor nie",
"importExport": "Invoer / Uitvoer",
"exportBtn": "Voer JSON uit",
"importBtn": "Voer JSON in",
"exportSuccess": "Brokkies suksesvol uitgevoer",
"exportFailed": "Kon nie brokkies uitvoer nie",
"importTitle": "Voer brokkies in",
"importDescription": "Voer brokkies en lêers in vanaf 'n JSON-lêer wat deur Termix uitgevoer is",
"importDropOrClick": "Plaas 'n JSON-lêer hier of klik om te blaai",
"importSelectedFile": "Gekies: {{name}}",
"importOverwrite": "Oorskryf bestaande brokkies met dieselfde naam en vouer",
"importStartBtn": "Invoer",
"importSuccess": "Invoer voltooi: {{snippets}} brokkie(s) bygevoeg, {{updated}} opgedateer, {{skipped}} oorgeslaan, {{folders}} vouer(s) bygevoeg",
"importFailed": "Kon nie brokkies invoer nie",
"importInvalidFile": "Ongeldige lêer: 'n JSON-objek met brokkies of vouerskikkings verwag",
"targetHostsLabel": "Teikengashere",
"targetHostsHint": "Ken gashere toe om hierdie brokkie direk uit te voer sonder 'n oop terminaal.",
"noHostsAvailable": "Geen gashere gekonfigureer nie",
"clearTargetHosts": "Vee alles uit",
"hasTargetHosts": "Het teikengashere",
"runOnTargets": "Hardloop op teikens",
"directRunSuccess": "Het \"{{name}}\" op {{count}} gasheer(s) uitgevoer",
"directRunPartialFail": "\"{{name}}\" het op een of meer gashere misluk",
"executionResultTitle": "Uitvoeringsresultate: {{name}}",
"executionResultDescription": "Uitvoer van die uitvoer van die brokkie op elke teikengasheer.",
"executionSuccess": "Sukses",
"executionFailed": "Misluk",
"expandAll": "Vou alle vouers uit",
"collapseAll": "Vou alle vouers in",
"settings": "Brokkie-instellings",
"variablesHint": "Veranderlikes: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Etiket}",
"variablesDialogTitle": "Begin \"{{name}}\"",
"variablesDialogDescription": "Vul die waardes in wat hierdie brokkie benodig voordat dit uitgevoer word.",
"variablesPreviewLabel": "Voorskou",
"variablesConfirmButton": "Hardloop"
},
"keybindings": {
"title": "Sleutelbordkortpaaie",
"description": "Pasmaak terminaalkopieer-, plak- en beheerkortpaaie, of bind sleutels om teks te stuur of 'n brokkie uit te voer.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klik en druk sleutels",
"recording": "Druk sleutels...",
"comboRequiredError": "Druk eers 'n sleutelkombinasie",
"textRequiredError": "Voer die teks in om te stuur",
"controlCodeRequiredError": "Voer 'n enkele letter vir die beheerkode in",
"snippetRequiredError": "Kies 'n uittreksel",
"conflictWarning": "Hierdie kombinasie is reeds gebind aan {{combo}}. As jy dit stoor, sal albei aktiveer.",
"actionLabel": "Aksie",
"actionCopy": "Kopieer seleksie",
"actionPaste": "Plak",
"actionSendControlCode": "Stuur Ctrl+letter sein",
"actionSendText": "Stuur SMS",
"actionRunSnippet": "Voer bestaande brokkie uit",
"actionNextTab": "Skakel oor na die volgende oortjie",
"actionPreviousTab": "Skakel oor na vorige oortjie",
"actionOpenCommandPalette": "Maak vinnige soektog oop",
"controlCodeLabel": "Brief",
"textLabel": "SMS om te stuur",
"appendEnterLabel": "Druk Enter na stuur",
"snippetLabel": "Brokkie",
"selectSnippetPlaceholder": "Kies 'n uittreksel",
"orphanedSnippetWarning": "brokkie nie gevind nie",
"clipboardPermissionNote": "Kan in sommige blaaiers met die eerste gebruik vir knipbordtoestemming vra."
"actionOpenCommandPalette": "Maak vinnige soektog oop"
},
"userProfile": {
"donateTitle": "Hou Termix lewendig",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Onderwerp is vereis",
"ntfyToken": "Toegangstoken (opsioneel)",
"channelSaveFailed": "Kon nie kanaal stoor nie"
},
"macros": {
"title": "Makro's",
"description": "Voer 'n gestoorde reeks opdragte in 'n terminaal uit en reageer op wat die bediener terugstuur.",
"target": "Teiken",
"noTerminal": "Geen terminaal oop nie",
"empty": "Geen makro's nog nie",
"emptyHint": "Skep 'n makro om 'n terminaalvloei te outomatiseer, soos om aan te meld, 'n diens te herbegin of aanwysings te beantwoord.",
"create": "Nuwe makro",
"back": "Terug na makro's",
"name": "Naam",
"namePlaceholder": "Herbegin die webbediener",
"nameRequired": "Gee die makro 'n naam voordat jy dit stoor",
"descriptionLabel": "Beskrywing",
"descriptionPlaceholder": "Wat hierdie makro doen (opsioneel)",
"unsaved": "Ongestoorde veranderinge",
"save": "Stoor",
"saved": "Makro gestoor",
"saveFailed": "Kon nie die makro stoor nie",
"delete": "Vee uit",
"deleteTitle": "Vee hierdie makro uit?",
"deleteBody": "\"{{name}}\" sal permanent verwyder word. Dit kan nie ongedaan gemaak word nie.",
"deleted": "Makro uitgevee",
"cancel": "Kanselleer",
"run": "Hardloop",
"stop": "Stop",
"running": "Hardloop...",
"completed": "Makro klaar",
"failed": "Makro het misluk",
"loadFailed": "Kon nie makro's laai nie",
"needTerminal": "Maak eers 'n terminaal oop en koppel dit",
"noSteps": "Hierdie makro het nog geen stappe nie. Voeg een hieronder by.",
"addStep": "Voeg stap by",
"stepNumber": "Stap {{number}}",
"removeStep": "Verwyder stap",
"confirmRunTitle": "Voer hierdie makro uit?",
"confirmRunBody": "\"{{name}}\" sal bevele stuur na {{host}}.",
"confirmRunAction": "Begin makro",
"stepTypes": {
"send": {
"label": "Stuur opdrag",
"help": "Tik teks in die terminaal, dieselfde asof jy dit getik het."
},
"wait": {
"label": "Wag vir uitvoer",
"help": "Pouseer totdat die bediener die teks wat jy noem, druk, soos 'n wagwoordaanwysing."
},
"delay": {
"label": "Pouseer",
"help": "Wag 'n vaste hoeveelheid tyd voor die volgende stap."
},
"if": {
"label": "Indien die uitvoer bevat",
"help": "Kontroleer alles wat die bediener tot dusver gedruk het, en voer dan een van twee takke uit."
},
"repeat": {
"label": "Herhaal",
"help": "Voer die stappe daarin 'n vasgestelde aantal kere uit."
}
},
"send": {
"text": "Opdrag of teks",
"textPlaceholder": "systemctl herbegin nginx",
"pressEnter": "Druk Enter na stuur",
"pressEnterHelp": "Skakel dit af om teks te tik sonder om dit in te dien."
},
"wait": {
"pattern": "Wag totdat die uitvoer bevat",
"patternPlaceholder": "wagwoord:",
"timeout": "Gee op daarna",
"onTimeout": "As dit nooit verskyn nie",
"stop": "Stop die makro",
"continue": "Gaan in elk geval voort"
},
"delay": {
"duration": "Wag vir"
},
"ifStep": {
"pattern": "Indien die uitvoer bevat",
"patternPlaceholder": "Aktief: aktief (hardloop)",
"then": "Doen dan hierdie",
"else": "Andersins doen dit",
"emptyBranch": "Geen stappe nie"
},
"repeat": {
"count": "Aantal kere",
"times": "tye"
},
"match": {
"useRegex": "Stem ooreen met 'n gereelde uitdrukking",
"useRegexHelp": "Los dit af om die teks presies soos geskryf te laat ooreenstem.",
"invalidRegex": "Nie 'n geldige gereelde uitdrukking nie, dit sal as gewone teks ooreenstem"
},
"seconds": "sekondes",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "إعدادات الشريط الجانبي...",
"hosts": "المضيفون",
"snippets": "المقتطفات",
"macros": "وحدات الماكرو",
"hostManager": "مدير المضيفين",
"credentials": "بيانات الاعتماد",
"connections": "الاتصالات",
@@ -869,7 +868,6 @@
"groupTags": "العلامات والدبابيس",
"groupTunnels": "الأنفاق",
"groupJumpHosts": "مضيفو القفز",
"groupQuickActions": "إجراءات سريعة",
"groupFeatureFlags": "ميزات مميزة",
"groupAdvanced": "الإعدادات المتقدمة",
"preview": "معاينة",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "تخزين مفاتيح SSH أو كلمات المرور",
"tmuxMonitor": "مراقب Tmux",
"tmuxMonitorDesc": "مراقبة جلسات tmux عبر مضيفيك",
"snippets": "مقتطفات",
"runSnippetDesc": "قم بتشغيله في علامة تبويب الطرفية النشطة",
"noSnippetsFound": "لم يتم العثور على أي مقتطفات مطابقة لـ \"{{search}}\"",
"recentActivity": "النشاط الأخير",
"serversAndHosts": "الخوادم والمضيفون",
"noHostsFound": "لم يتم العثور على مضيفين يطابقون \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "التبويب السابق",
"hotkeysJumpTab": "انتقل إلى علامة التبويب"
},
"snippets": {
"title": "المقتطفات",
"createSnippetTitle": "إنشاء مقتطفة",
"createSnippetDescription": "إنشاء مقتطفة أمر جديدة للتنفيذ السريع",
"nameLabel": "الاسم",
"namePlaceholder": "مثال: إعادة تشغيل Nginx",
"descriptionLabel": "الوصف",
"descriptionPlaceholder": "وصف اختياري",
"optional": "اختياري",
"folderLabel": "مجلد",
"noFolder": "بدون مجلد (غير مصنف)",
"commandLabel": "الأمر",
"commandPlaceholder": "مثلاً: sudo systemctl restart nginx",
"typeLabel": "يكتب",
"typeCommand": "يأمر",
"typeNote": "ملحوظة",
"noteLabel": "ملحوظة",
"notePlaceholder": "على سبيل المثال، نص تذكير أو مطالبة قابلة لإعادة الاستخدام",
"cancel": "إلغاء",
"createSnippetButton": "إنشاء مقتطفة",
"createFolderTitle": "إنشاء مجلد",
"createFolderDescription": "تنظيم المقتطفات في مجلدات",
"folderNameLabel": "اسم المجلد",
"folderNamePlaceholder": "مثلاً: أوامر النظام، نصوص Docker",
"folderColorLabel": "لون المجلد",
"folderIconLabel": "أيقونة المجلد",
"previewLabel": "معاينة",
"folderNameFallback": "اسم المجلد",
"createFolderButton": "إنشاء مجلد",
"targetTerminals": "الطرفيات المستهدفة",
"selectAll": "الكل",
"selectNone": "لا شيء",
"noTerminalTabsOpen": "لا توجد علامات تبويب طرفية مفتوحة",
"searchPlaceholder": "ابحث في المقتطفات...",
"newSnippet": "مقتطف جديد",
"newFolder": "مجلد جديد",
"run": "تشغيل",
"noSnippetsInFolder": "لا توجد مقتطفات في هذا المجلد",
"uncategorized": "غير مصنف",
"editSnippetTitle": "تحرير المقتطف",
"editSnippetDescription": "تحديث مقتطف الأمر هذا",
"saveSnippetButton": "حفظ التغييرات",
"createSuccess": "تم إنشاء المقتطف بنجاح",
"createFailed": "فشل إنشاء المقتطف",
"updateSuccess": "تم تحديث المقتطف بنجاح",
"updateFailed": "فشل تحديث المقتطف",
"deleteFailed": "فشل حذف المقتطف",
"folderCreateSuccess": "تم إنشاء المجلد بنجاح",
"folderCreateFailed": "فشل إنشاء المجلد",
"editFolderTitle": "تحرير المجلد",
"editFolderDescription": "إعادة تسمية أو تغيير مظهر هذا المجلد",
"saveFolderButton": "حفظ التغييرات",
"editFolder": "تحرير المجلد",
"deleteFolder": "حذف المجلد",
"folderDeleteSuccess": "تم حذف المجلد \"{{name}}\"",
"folderDeleteFailed": "فشل حذف المجلد",
"folderEditSuccess": "تم تحديث المجلد بنجاح",
"folderEditFailed": "فشل تحديث المجلد",
"confirmRunMessage": "تشغيل \"{{name}}\"؟",
"confirmRunButton": "تشغيل",
"runSuccess": "تم تشغيل \"{{name}}\" في {{count}} طرفية",
"pasteSuccess": "تم لصق \"{{name}}\" في {{count}} طرفية (أطراف)",
"pasteToTerminal": "لصق",
"copySuccess": "تم نسخ \"{{name}}\" إلى الحافظة",
"shareTitle": "مشاركة المقتطف",
"shareUser": "مستخدم",
"shareRole": "دور",
"selectUser": "اختر مستخدمًا...",
"selectRole": "اختر دورًا...",
"shareSuccess": "تمت مشاركة الجزء البرمجي بنجاح",
"shareFailed": "فشلت مشاركة الجزء البرمجي",
"revokeSuccess": "تم إبطال الوصول",
"revokeFailed": "فشل إبطال الوصول",
"currentAccess": "الوصول الحالي",
"shareLoadError": "فشل تحميل بيانات المشاركة",
"loading": "جارٍ التحميل...",
"close": "إغلاق",
"reorderFailed": "فشل حفظ ترتيب الأجزاء البرمجية",
"importExport": "استيراد / تصدير",
"exportBtn": "تصدير JSON",
"importBtn": "استيراد JSON",
"exportSuccess": "تم تصدير الأجزاء البرمجية بنجاح",
"exportFailed": "فشل تصدير الأجزاء البرمجية",
"importTitle": "استيراد الأجزاء البرمجية",
"importDescription": "استيراد الأجزاء البرمجية والمجلدات من ملف JSON تم تصديره بواسطة Termix",
"importDropOrClick": "أفلت ملف JSON هنا أو انقر للاستعراض",
"importSelectedFile": "المحدد: {{name}}",
"importOverwrite": "الكتابة فوق الأجزاء البرمجية الموجودة بنفس الاسم والمجلد",
"importStartBtn": "استيراد",
"importSuccess": "اكتمل الاستيراد: تمت إضافة {{snippets}} جزء برمجي، وتحديث {{updated}}، وتخطي {{skipped}}، وإضافة {{folders}} مجلد",
"importFailed": "فشل استيراد الأجزاء البرمجية",
"importInvalidFile": "ملف غير صالح: يُتوقع كائن JSON يحتوي على مصفوفات من الأجزاء البرمجية أو المجلدات",
"targetHostsLabel": "المضيفات المستهدفة",
"targetHostsHint": "تعيين مضيفات لتشغيل هذا الجزء البرمجي مباشرة بدون فتح طرفية.",
"noHostsAvailable": "لم يتم إعداد مضيفات",
"clearTargetHosts": "مسح الكل",
"hasTargetHosts": "لديه مضيفات مستهدفة",
"runOnTargets": "تشغيل على المضيفات",
"directRunSuccess": "تم تشغيل \"{{name}}\" على {{count}} مضيف",
"directRunPartialFail": "فشل \"{{name}}\" على واحد أو أكثر من المضيفات",
"executionResultTitle": "نتائج التنفيذ: {{name}}",
"executionResultDescription": "المخرجات الناتجة عن تشغيل المقتطف على كل مضيف مستهدف.",
"executionSuccess": "نجاح",
"executionFailed": "فشل",
"expandAll": "قم بتوسيع جميع المجلدات",
"collapseAll": "طي جميع المجلدات",
"settings": "إعدادات المقتطف",
"variablesHint": "المتغيرات: $HOST، $USER، $PORT، $NAME، ${INPUT_1:Label}",
"variablesDialogTitle": "تشغيل \"{{name}}\"",
"variablesDialogDescription": "أدخل القيم التي يحتاجها هذا المقتطف البرمجي قبل تشغيله.",
"variablesPreviewLabel": "معاينة",
"variablesConfirmButton": "يجري"
},
"keybindings": {
"title": "اختصارات لوحة المفاتيح",
"description": "قم بتخصيص اختصارات النسخ واللصق والتحكم في الطرفية، أو اربط المفاتيح لإرسال نص أو تشغيل جزء من التعليمات البرمجية.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "انقر واضغط على المفاتيح",
"recording": "اضغط على المفاتيح...",
"comboRequiredError": "اضغط على مجموعة مفاتيح أولاً",
"textRequiredError": "أدخل النص المراد إرساله",
"controlCodeRequiredError": "أدخل حرفًا واحدًا لرمز التحكم",
"snippetRequiredError": "حدد جزءًا من التعليمات البرمجية",
"conflictWarning": "هذا المزيج مرتبط بالفعل بـ {{combo}}. سيؤدي الحفظ إلى إطلاق النار من كليهما.",
"actionLabel": "فعل",
"actionCopy": "تحديد النص",
"actionPaste": "لصق",
"actionSendControlCode": "إرسال إشارة Ctrl+حرف",
"actionSendText": "أرسل رسالة نصية",
"actionRunSnippet": "تشغيل المقتطف البرمجي الحالي",
"actionNextTab": "انتقل إلى علامة التبويب التالية",
"actionPreviousTab": "انتقل إلى علامة التبويب السابقة",
"actionOpenCommandPalette": "افتح البحث السريع",
"controlCodeLabel": "خطاب",
"textLabel": "نص لإرساله",
"appendEnterLabel": "اضغط على زر الإدخال بعد الإرسال",
"snippetLabel": "مقتطف",
"selectSnippetPlaceholder": "حدد جزءًا من التعليمات البرمجية",
"orphanedSnippetWarning": "لم يتم العثور على المقتطف",
"clipboardPermissionNote": "قد يطلب منك بعض المتصفحات منح إذن الوصول إلى الحافظة عند الاستخدام الأول."
"actionOpenCommandPalette": "افتح البحث السريع"
},
"userProfile": {
"donateTitle": "ضمان استمرارية Termix",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "الموضوع مطلوب",
"ntfyToken": "رمز الوصول (اختياري)",
"channelSaveFailed": "فشل حفظ القناة"
},
"macros": {
"title": "وحدات الماكرو",
"description": "قم بتشغيل سلسلة من الأوامر المحفوظة في طرفية، وتفاعل مع ما يرسله الخادم.",
"target": "هدف",
"noTerminal": "لا يوجد طرفية مفتوحة",
"empty": "لا توجد وحدات ماكرو حتى الآن",
"emptyHint": "أنشئ ماكرو لأتمتة تدفق طرفية، مثل تسجيل الدخول أو إعادة تشغيل خدمة أو الإجابة على المطالبات.",
"create": "ماكرو جديد",
"back": "العودة إلى وحدات الماكرو",
"name": "اسم",
"namePlaceholder": "أعد تشغيل خادم الويب",
"nameRequired": "قم بتسمية الماكرو قبل حفظه",
"descriptionLabel": "وصف",
"descriptionPlaceholder": "ما يفعله هذا الماكرو (اختياري)",
"unsaved": "التغييرات غير المحفوظة",
"save": "يحفظ",
"saved": "تم حفظ الماكرو",
"saveFailed": "تعذر حفظ الماكرو",
"delete": "يمسح",
"deleteTitle": "هل تريد حذف هذا الماكرو؟",
"deleteBody": "سيتم حذف \"{{name}}\" نهائياً. لا يمكن التراجع عن هذا الإجراء.",
"deleted": "تم حذف الماكرو",
"cancel": "يلغي",
"run": "يجري",
"stop": "قف",
"running": "جري...",
"completed": "اللمسة النهائية الدقيقة",
"failed": "فشل الماكرو",
"loadFailed": "تعذر تحميل وحدات الماكرو",
"needTerminal": "افتح وقم بتوصيل طرفية أولاً",
"noSteps": "لا يحتوي هذا الماكرو على أي خطوات حتى الآن. أضف خطوة واحدة أدناه.",
"addStep": "خطوات إضافية",
"stepNumber": "الخطوة {{number}}",
"removeStep": "خطوة الإزالة",
"confirmRunTitle": "هل تريد تشغيل هذا الماكرو؟",
"confirmRunBody": "\"{{name}}\" سيرسل أوامر إلى {{host}}.",
"confirmRunAction": "تشغيل الماكرو",
"stepTypes": {
"send": {
"label": "إرسال الأمر",
"help": "يكتب النص في الطرفية، تمامًا كما لو كنت أنت من كتبته."
},
"wait": {
"label": "انتظر حتى يتم إخراج البيانات",
"help": "يتوقف مؤقتًا حتى يقوم الخادم بطباعة النص الذي تحدده، مثل مطالبة كلمة المرور."
},
"delay": {
"label": "يوقف",
"help": "ينتظر فترة زمنية محددة قبل الخطوة التالية."
},
"if": {
"label": "إذا احتوى الناتج",
"help": "يقوم بفحص كل ما طبعه الخادم حتى الآن، ثم يقوم بتشغيل أحد الفرعين."
},
"repeat": {
"label": "يكرر",
"help": "يقوم بتنفيذ الخطوات الموجودة بداخله لعدد محدد من المرات."
}
},
"send": {
"text": "أمر أو نص",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "اضغط على زر الإدخال بعد الإرسال",
"pressEnterHelp": "قم بإيقاف تشغيل هذا الخيار لكتابة نص دون إرساله."
},
"wait": {
"pattern": "انتظر حتى يحتوي الناتج على",
"patternPlaceholder": "كلمة المرور:",
"timeout": "الاستسلام بعد",
"onTimeout": "إذا لم يظهر أبداً",
"stop": "أوقف الماكرو",
"continue": "استمر على أي حال"
},
"delay": {
"duration": "انتظر"
},
"ifStep": {
"pattern": "إذا احتوى الناتج",
"patternPlaceholder": "نشط: نشط (قيد التشغيل)",
"then": "ثم افعل هذا",
"else": "وإلا فافعل هذا",
"emptyBranch": "لا توجد"
},
"repeat": {
"count": "عدد المرات",
"times": "أوقات"
},
"match": {
"useRegex": "مطابقة مع تعبير نمطي",
"useRegexHelp": "اترك هذا الخيار فارغًا لمطابقة النص تمامًا كما هو مكتوب.",
"invalidRegex": "هذا ليس تعبيرًا نمطيًا صالحًا، وسيتم مطابقته كنص عادي."
},
"seconds": "ثوانٍ",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Настройки на страничната лента...",
"hosts": "Домакини",
"snippets": "Откъси",
"macros": "Макроси",
"hostManager": "Мениджър на домакини",
"credentials": "Пълномощия",
"connections": "Връзки",
@@ -869,7 +868,6 @@
"groupTags": "Етикети и закачане",
"groupTunnels": "Тунели",
"groupJumpHosts": "Домакини за преходи",
"groupQuickActions": "Бързи действия",
"groupFeatureFlags": "Флагове на функциите",
"groupAdvanced": "Разширена конфигурация",
"preview": "Преглед",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Съхранявайте SSH ключове или пароли",
"tmuxMonitor": "Tmux монитор",
"tmuxMonitorDesc": "Следете tmux сесиите на вашите хостове",
"snippets": "Откъси",
"runSnippetDesc": "Изпълняване в активния раздел на терминала",
"noSnippetsFound": "Не са намерени фрагменти, съответстващи на „{{search}}“",
"recentActivity": "Последна активност",
"serversAndHosts": "Сървъри и хостове",
"noHostsFound": "Не са намерени хостове, съответстващи на „{{search}}“",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Предишен раздел",
"hotkeysJumpTab": "Преход към раздел"
},
"snippets": {
"title": "Откъси",
"createSnippetTitle": "Създаване на фрагмент",
"createSnippetDescription": "Създайте нов фрагмент от команда за бързо изпълнение",
"nameLabel": "Име",
"namePlaceholder": "например, рестартирайте Nginx",
"descriptionLabel": "Описание",
"descriptionPlaceholder": "Незадължително описание",
"optional": "По избор",
"folderLabel": "Папка",
"noFolder": "Няма папка (Без категория)",
"commandLabel": "Команда",
"commandPlaceholder": "например, sudo systemctl рестартиране на nginx",
"typeLabel": "Тип",
"typeCommand": "Команда",
"typeNote": "Забележка",
"noteLabel": "Забележка",
"notePlaceholder": "напр. текст за многократна употреба или напомняне",
"cancel": "Отказ",
"createSnippetButton": "Създаване на фрагмент",
"createFolderTitle": "Създаване на папка",
"createFolderDescription": "Организирайте фрагментите си в папки",
"folderNameLabel": "Име на папката",
"folderNamePlaceholder": "напр. системни команди, Docker скриптове",
"folderColorLabel": "Цвят на папката",
"folderIconLabel": "Икона на папка",
"previewLabel": "Преглед",
"folderNameFallback": "Име на папката",
"createFolderButton": "Създаване на папка",
"targetTerminals": "Целеви терминали",
"selectAll": "Всички",
"selectNone": "Няма",
"noTerminalTabsOpen": "Няма отворени раздели на терминала",
"searchPlaceholder": "Търсене на фрагменти...",
"newSnippet": "Нов фрагмент",
"newFolder": "Нова папка",
"run": "Бягане",
"noSnippetsInFolder": "Няма фрагменти в тази папка",
"uncategorized": "Некатегоризирано",
"editSnippetTitle": "Редактиране на фрагмент",
"editSnippetDescription": "Актуализирайте този фрагмент от командата",
"saveSnippetButton": "Запазване на промените",
"createSuccess": "Фрагментът е създаден успешно",
"createFailed": "Създаването на фрагмент не бе успешно",
"updateSuccess": "Фрагментът е актуализиран успешно",
"updateFailed": "Актуализирането на фрагмента не бе успешно",
"deleteFailed": "Изтриването на фрагмента не бе успешно",
"folderCreateSuccess": "Папката е създадена успешно",
"folderCreateFailed": "Създаването на папка не бе успешно",
"editFolderTitle": "Редактиране на папка",
"editFolderDescription": "Преименувайте или променете външния вид на тази папка",
"saveFolderButton": "Запазване на промените",
"editFolder": "Редактиране на папка",
"deleteFolder": "Изтриване на папка",
"folderDeleteSuccess": "Папка „{{name}}“ е изтрита",
"folderDeleteFailed": "Изтриването на папката не бе успешно",
"folderEditSuccess": "Папката е актуализирана успешно",
"folderEditFailed": "Актуализирането на папката не бе успешно",
"confirmRunMessage": "Изпълни „{{name}}“?",
"confirmRunButton": "Бягане",
"runSuccess": "Изпълни „{{name}}“ в {{count}} терминал(и)",
"pasteSuccess": "Поставено е „{{name}}“ в {{count}} терминал(и)",
"pasteToTerminal": "Паста",
"copySuccess": "Копирано „{{name}}“ в клипборда",
"shareTitle": "Споделяне на фрагмент",
"shareUser": "Потребител",
"shareRole": "Роля",
"selectUser": "Изберете потребител...",
"selectRole": "Изберете роля...",
"shareSuccess": "Фрагментът е споделен успешно",
"shareFailed": "Споделянето на фрагмента не бе успешно",
"revokeSuccess": "Достъпът е отменен",
"revokeFailed": "Неуспешно отменяне на достъпа",
"currentAccess": "Текущ достъп",
"shareLoadError": "Зареждането на данните за споделяне не бе успешно",
"loading": "Зареждане...",
"close": "Затвори",
"reorderFailed": "Запазването на реда на фрагмента не бе успешно",
"importExport": "Внос / Износ",
"exportBtn": "Експортиране на JSON",
"importBtn": "Импортиране на JSON",
"exportSuccess": "Фрагментите са експортирани успешно",
"exportFailed": "Експортирането на фрагменти не бе успешно",
"importTitle": "Импортиране на фрагменти",
"importDescription": "Импортиране на фрагменти и папки от JSON файл, експортиран от Termix",
"importDropOrClick": "Пуснете JSON файл тук или кликнете, за да разгледате",
"importSelectedFile": "Избрано: {{name}}",
"importOverwrite": "Презапишете съществуващите фрагменти със същото име и папка",
"importStartBtn": "Внос",
"importSuccess": "Импортирането е завършено: {{snippets}} добавени фрагмента, {{updated}} актуализирани, {{skipped}} пропуснати, {{folders}} добавени папки",
"importFailed": "Импортирането на фрагменти не бе успешно",
"importInvalidFile": "Невалиден файл: очаква се JSON обект с фрагменти или масиви от папки",
"targetHostsLabel": "Целеви хостове",
"targetHostsHint": "Задайте хостове, които да изпълняват този фрагмент директно, без да е отворен терминал.",
"noHostsAvailable": "Няма конфигурирани хостове",
"clearTargetHosts": "Изчисти всички",
"hasTargetHosts": "Има целеви хостове",
"runOnTargets": "Бягане по цели",
"directRunSuccess": "Изпълни „{{name}}“ на {{count}} хост(ове)",
"directRunPartialFail": "„{{name}}“ неуспешно на един или повече хостове",
"executionResultTitle": "Резултати от изпълнението: {{name}}",
"executionResultDescription": "Изход от изпълнението на фрагмента на всеки целеви хост.",
"executionSuccess": "Успех",
"executionFailed": "Неуспешно",
"expandAll": "Разгъване на всички папки",
"collapseAll": "Свиване на всички папки",
"settings": "Настройки на фрагмента",
"variablesHint": "Променливи: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Етикет}",
"variablesDialogTitle": "Изпълни „{{name}}“",
"variablesDialogDescription": "Попълнете стойностите, необходими на този фрагмент, преди да го изпълните.",
"variablesPreviewLabel": "Преглед",
"variablesConfirmButton": "Бягане"
},
"keybindings": {
"title": "Клавишни комбинации",
"description": "Персонализирайте клавишните комбинации за копиране, поставяне и управление на терминала или обвържете клавишите за изпращане на текст или изпълнение на фрагмент.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Щракнете и натиснете клавиши",
"recording": "Натиснете клавишите...",
"comboRequiredError": "Първо натиснете клавишна комбинация",
"textRequiredError": "Въведете текста за изпращане",
"controlCodeRequiredError": "Въведете една буква за контролния код",
"snippetRequiredError": "Изберете фрагмент",
"conflictWarning": "Тази комбинация вече е свързана с {{combo}}. Запазването ще доведе до задействане и на двете.",
"actionLabel": "Действие",
"actionCopy": "Копиране на селекцията",
"actionPaste": "Паста",
"actionSendControlCode": "Изпрати сигнал Ctrl+буква",
"actionSendText": "Изпращане на текст",
"actionRunSnippet": "Изпълнете съществуващия фрагмент",
"actionNextTab": "Превключване към следващия раздел",
"actionPreviousTab": "Превключване към предишния раздел",
"actionOpenCommandPalette": "Отваряне на бързо търсене",
"controlCodeLabel": "Писмо",
"textLabel": "Текст за изпращане",
"appendEnterLabel": "Натиснете Enter след изпращане",
"snippetLabel": "Фрагмент",
"selectSnippetPlaceholder": "Изберете фрагмент",
"orphanedSnippetWarning": "фрагмент не е намерен",
"clipboardPermissionNote": "Може да поиска разрешение за достъп до клипборда при първото използване в някои браузъри."
"actionOpenCommandPalette": "Отваряне на бързо търсене"
},
"userProfile": {
"donateTitle": "Пази Термикс жив",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Темата е задължителна",
"ntfyToken": "Токен за достъп (по избор)",
"channelSaveFailed": "Запазването на канала не бе успешно"
},
"macros": {
"title": "Макроси",
"description": "Изпълнете запазена серия от команди в терминал и реагирайте на това, което сървърът изпраща обратно.",
"target": "Цел",
"noTerminal": "Няма отворен терминал",
"empty": "Все още няма макроси",
"emptyHint": "Създайте макрос за автоматизиране на процес в терминал, като например влизане в системата, рестартиране на услуга или отговаряне на подкани.",
"create": "Нов макрос",
"back": "Обратно към макросите",
"name": "Име",
"namePlaceholder": "Рестартирайте уеб сървъра",
"nameRequired": "Дайте име на макроса, преди да го запазите",
"descriptionLabel": "Описание",
"descriptionPlaceholder": "Какво прави този макрос (по избор)",
"unsaved": "Незапазени промени",
"save": "Запазване",
"saved": "Макросът е запазен",
"saveFailed": "Макросът не можа да се запази",
"delete": "Изтриване",
"deleteTitle": "Да се изтрие ли този макрос?",
"deleteBody": "„{{name}}“ ще бъде изтрито за постоянно. Това не може да бъде отменено.",
"deleted": "Макросът е изтрит",
"cancel": "Отказ",
"run": "Бягане",
"stop": "Спри",
"running": "Бягане...",
"completed": "Макрото е завършено",
"failed": "Макросът не беше успешен",
"loadFailed": "Не можаха да се заредят макроси",
"needTerminal": "Първо отворете и свържете терминал",
"noSteps": "Този макрос все още няма стъпки. Добавете една по-долу.",
"addStep": "Добавяне на стъпка",
"stepNumber": "Стъпка {{number}}",
"removeStep": "Премахване на стъпка",
"confirmRunTitle": "Да се изпълни ли този макрос?",
"confirmRunBody": "„{{name}}“ ще изпраща команди до {{host}}.",
"confirmRunAction": "Изпълнете макрос",
"stepTypes": {
"send": {
"label": "Изпрати команда",
"help": "Въвежда текст в терминала, по същия начин, както ако сте го написали."
},
"wait": {
"label": "Изчакайте изхода",
"help": "Пауза, докато сървърът отпечата текста, който посочите, като например подкана за парола."
},
"delay": {
"label": "Пауза",
"help": "Изчаква фиксирано време преди следващата стъпка."
},
"if": {
"label": "Ако изходът съдържа",
"help": "Проверява всичко, което сървърът е отпечатал досега, след което изпълнява един от двата клона."
},
"repeat": {
"label": "Повторете",
"help": "Изпълнява стъпките вътре в него определен брой пъти."
}
},
"send": {
"text": "Команда или текст",
"textPlaceholder": "systemctl рестартиране на nginx",
"pressEnter": "Натиснете Enter след изпращане",
"pressEnterHelp": "Изключете това, за да пишете текст, без да го изпращате."
},
"wait": {
"pattern": "Изчакайте, докато изходът съдържа",
"patternPlaceholder": "парола:",
"timeout": "Откажи се след",
"onTimeout": "Ако никога не се появи",
"stop": "Спиране на макроса",
"continue": "Продължавай все пак"
},
"delay": {
"duration": "Изчакайте"
},
"ifStep": {
"pattern": "Ако изходът съдържа",
"patternPlaceholder": "Активен: активен (работи)",
"then": "Тогава направете това",
"else": "В противен случай направете това",
"emptyBranch": "Без стъпала"
},
"repeat": {
"count": "Брой пъти",
"times": "пъти"
},
"match": {
"useRegex": "Съвпадение с регулярен израз",
"useRegexHelp": "Не включвайте това, за да съвпада текстът точно както е написан.",
"invalidRegex": "Не е валиден регулярен израз, ще бъде съпоставен като обикновен текст"
},
"seconds": "секунди",
"secondsShort": "{{count}}с"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "সাইডবার সেটিংস...",
"hosts": "হোস্টরা",
"snippets": "খণ্ডাংশ",
"macros": "ম্যাক্রো",
"hostManager": "হোস্ট ম্যানেজার",
"credentials": "যোগ্যতা",
"connections": "সংযোগ",
@@ -869,7 +868,6 @@
"groupTags": "ট্যাগ ও পিন",
"groupTunnels": "টানেল",
"groupJumpHosts": "জাম্প হোস্ট",
"groupQuickActions": "দ্রুত পদক্ষেপ",
"groupFeatureFlags": "বৈশিষ্ট্য পতাকা",
"groupAdvanced": "উন্নত কনফিগারেশন",
"preview": "প্রিভিউ",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH কী বা পাসওয়ার্ড সংরক্ষণ করুন",
"tmuxMonitor": "Tmux মনিটর",
"tmuxMonitorDesc": "আপনার হোস্ট জুড়ে tmux সেশনগুলি নিরীক্ষণ করুন",
"snippets": "খণ্ডাংশ",
"runSnippetDesc": "সক্রিয় টার্মিনাল ট্যাবে চালান",
"noSnippetsFound": "\"{{search}} \" এর সাথে মেলে এমন কোনো স্নিপেট খুঁজে পাওয়া যায়নি।",
"recentActivity": "সাম্প্রতিক কার্যকলাপ",
"serversAndHosts": "সার্ভার এবং হোস্ট",
"noHostsFound": "\"{{search}} \" এর সাথে মেলে এমন কোনো হোস্ট খুঁজে পাওয়া যায়নি।",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "পূর্ববর্তী ট্যাব",
"hotkeysJumpTab": "ট্যাবে যান"
},
"snippets": {
"title": "খণ্ডাংশ",
"createSnippetTitle": "স্নিপেট তৈরি করুন",
"createSnippetDescription": "দ্রুত কার্যকর করার জন্য একটি নতুন কমান্ড স্নিপেট তৈরি করুন",
"nameLabel": "নাম",
"namePlaceholder": "যেমন, Nginx রিস্টার্ট করুন",
"descriptionLabel": "বর্ণনা",
"descriptionPlaceholder": "ঐচ্ছিক বিবরণ",
"optional": "ঐচ্ছিক",
"folderLabel": "ফোল্ডার",
"noFolder": "কোন ফোল্ডার নেই (শ্রেণীবিহীন)",
"commandLabel": "আদেশ",
"commandPlaceholder": "যেমন, sudo systemctl restart nginx",
"typeLabel": "প্রকার",
"typeCommand": "আদেশ",
"typeNote": "দ্রষ্টব্য",
"noteLabel": "দ্রষ্টব্য",
"notePlaceholder": "যেমন, একটি পুনঃব্যবহারযোগ্য প্রম্পট বা রিমাইন্ডার টেক্সট",
"cancel": "বাতিল করুন",
"createSnippetButton": "স্নিপেট তৈরি করুন",
"createFolderTitle": "ফোল্ডার তৈরি করুন",
"createFolderDescription": "আপনার স্নিপেটগুলো ফোল্ডারে সাজিয়ে নিন।",
"folderNameLabel": "ফোল্ডারের নাম",
"folderNamePlaceholder": "যেমন, সিস্টেম কমান্ড, ডকার স্ক্রিপ্ট",
"folderColorLabel": "ফোল্ডারের রঙ",
"folderIconLabel": "ফোল্ডার আইকন",
"previewLabel": "প্রিভিউ",
"folderNameFallback": "ফোল্ডারের নাম",
"createFolderButton": "ফোল্ডার তৈরি করুন",
"targetTerminals": "টার্গেট টার্মিনাল",
"selectAll": "সব",
"selectNone": "কোনোটিই না",
"noTerminalTabsOpen": "কোন টার্মিনাল ট্যাব খোলা নেই",
"searchPlaceholder": "অনুসন্ধানের অংশবিশেষ...",
"newSnippet": "নতুন স্নিপেট",
"newFolder": "নতুন ফোল্ডার",
"run": "দৌড়",
"noSnippetsInFolder": "এই ফোল্ডারে কোনো স্নিপেট নেই",
"uncategorized": "অশ্রেণীবদ্ধ",
"editSnippetTitle": "সম্পাদনা স্নিপেট",
"editSnippetDescription": "এই কমান্ড স্নিপেটটি আপডেট করুন",
"saveSnippetButton": "পরিবর্তনগুলি সংরক্ষণ করুন",
"createSuccess": "স্নিপেট সফলভাবে তৈরি করা হয়েছে",
"createFailed": "স্নিপেট তৈরি করতে ব্যর্থ হয়েছে",
"updateSuccess": "স্নিপেট সফলভাবে আপডেট করা হয়েছে",
"updateFailed": "স্নিপেট আপডেট করতে ব্যর্থ হয়েছে",
"deleteFailed": "স্নিপেট মুছে ফেলা ব্যর্থ হয়েছে",
"folderCreateSuccess": "ফোল্ডারটি সফলভাবে তৈরি করা হয়েছে।",
"folderCreateFailed": "ফোল্ডার তৈরি করতে ব্যর্থ হয়েছে",
"editFolderTitle": "ফোল্ডার সম্পাদনা করুন",
"editFolderDescription": "এই ফোল্ডারটির নাম পরিবর্তন করুন বা এর চেহারা বদলে দিন।",
"saveFolderButton": "পরিবর্তনগুলি সংরক্ষণ করুন",
"editFolder": "ফোল্ডার সম্পাদনা করুন",
"deleteFolder": "ফোল্ডারটি মুছে ফেলুন",
"folderDeleteSuccess": "ফোল্ডার \"{{name}}\" মুছে ফেলা হয়েছে",
"folderDeleteFailed": "ফোল্ডারটি মুছে ফেলা ব্যর্থ হয়েছে",
"folderEditSuccess": "ফোল্ডারটি সফলভাবে আপডেট করা হয়েছে",
"folderEditFailed": "ফোল্ডার আপডেট করতে ব্যর্থ হয়েছে",
"confirmRunMessage": "\"{{name}} \" চালান?",
"confirmRunButton": "দৌড়",
"runSuccess": "{{count}} টার্মিনালে \"{{name}}\" চালানো হয়েছে",
"pasteSuccess": "{{name}}\" {{count}} টার্মিনালে পেস্ট করা হয়েছে",
"pasteToTerminal": "পেস্ট",
"copySuccess": "\"{{name}}\" ক্লিপবোর্ডে কপি করা হয়েছে",
"shareTitle": "স্নিপেট শেয়ার করুন",
"shareUser": "ব্যবহারকারী",
"shareRole": "ভূমিকা",
"selectUser": "একজন ব্যবহারকারী নির্বাচন করুন...",
"selectRole": "একটি ভূমিকা নির্বাচন করুন...",
"shareSuccess": "স্নিপেট সফলভাবে শেয়ার করা হয়েছে",
"shareFailed": "স্নিপেট শেয়ার করতে ব্যর্থ হয়েছে",
"revokeSuccess": "প্রবেশাধিকার বাতিল করা হয়েছে",
"revokeFailed": "অ্যাক্সেস প্রত্যাহার করতে ব্যর্থ হয়েছে",
"currentAccess": "বর্তমান অ্যাক্সেস",
"shareLoadError": "শেয়ার ডেটা লোড করতে ব্যর্থ হয়েছে",
"loading": "লোড হচ্ছে...",
"close": "বন্ধ করুন",
"reorderFailed": "স্নিপেটের ক্রম সংরক্ষণ করতে ব্যর্থ হয়েছে",
"importExport": "আমদানি / রপ্তানি",
"exportBtn": "JSON রপ্তানি করুন",
"importBtn": "JSON আমদানি করুন",
"exportSuccess": "স্নিপেটগুলি সফলভাবে রপ্তানি করা হয়েছে",
"exportFailed": "স্নিপেট রপ্তানি করতে ব্যর্থ হয়েছে",
"importTitle": "আমদানি স্নিপেট",
"importDescription": "Termix দ্বারা এক্সপোর্ট করা একটি JSON ফাইল থেকে স্নিপেট এবং ফোল্ডার ইম্পোর্ট করুন।",
"importDropOrClick": "এখানে একটি JSON ফাইল ড্রপ করুন অথবা ব্রাউজ করতে ক্লিক করুন।",
"importSelectedFile": "নির্বাচিত: {{name}}",
"importOverwrite": "একই নাম এবং ফোল্ডার ব্যবহার করে বিদ্যমান স্নিপেটগুলো ওভাররাইট করুন।",
"importStartBtn": "আমদানি",
"importSuccess": "আমদানি সম্পন্ন: {{snippets}} টি স্নিপেট যোগ করা হয়েছে, {{updated}} টি আপডেট করা হয়েছে, {{skipped}} টি বাদ দেওয়া হয়েছে, {{folders}} টি ফোল্ডার যোগ করা হয়েছে",
"importFailed": "স্নিপেট আমদানি করতে ব্যর্থ হয়েছে",
"importInvalidFile": "অবৈধ ফাইল: স্নিপেট বা ফোল্ডার অ্যারে সহ একটি JSON অবজেক্ট প্রত্যাশিত ছিল",
"targetHostsLabel": "লক্ষ্য হোস্ট",
"targetHostsHint": "টার্মিনাল খোলা ছাড়াই সরাসরি এই কোডটুকু চালানোর জন্য হোস্ট নির্ধারণ করুন।",
"noHostsAvailable": "কোন হোস্ট কনফিগার করা নেই",
"clearTargetHosts": "সব পরিষ্কার করুন",
"hasTargetHosts": "লক্ষ্য হোস্ট আছে",
"runOnTargets": "লক্ষ্যমাত্রায় দৌড়",
"directRunSuccess": "{{count}} হোস্টে \"{{name}}\" চালানো হয়েছে",
"directRunPartialFail": "এক বা একাধিক হোস্টে \"{{name}}\" ব্যর্থ হয়েছে",
"executionResultTitle": "কার্য সম্পাদনের ফলাফল: {{name}}",
"executionResultDescription": "প্রতিটি টার্গেট হোস্টে কোড স্নিপেটটি চালানোর আউটপুট।",
"executionSuccess": "সাফল্য",
"executionFailed": "ব্যর্থ",
"expandAll": "সমস্ত ফোল্ডার প্রসারিত করুন",
"collapseAll": "সমস্ত ফোল্ডার সংকুচিত করুন",
"settings": "স্নিপেট সেটিংস",
"variablesHint": "ভেরিয়েবল: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "দৌড়াও \"{{name}}\"",
"variablesDialogDescription": "এই কোড স্নিপেটটি চালানোর আগে প্রয়োজনীয় মানগুলি পূরণ করুন।",
"variablesPreviewLabel": "প্রিভিউ",
"variablesConfirmButton": "দৌড়"
},
"keybindings": {
"title": "কীবোর্ড শর্টকাট",
"description": "টার্মিনালের কপি, পেস্ট ও কন্ট্রোল শর্টকাটগুলো নিজের মতো করে সাজিয়ে নিন, অথবা টেক্সট পাঠাতে বা কোনো স্নিপেট চালাতে কী-গুলো বাইন্ড করুন।",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "ক্লিক করুন এবং কী টিপুন",
"recording": "কীগুলো চাপুন...",
"comboRequiredError": "প্রথমে একটি কী-কম্বিনেশন চাপুন",
"textRequiredError": "পাঠানোর জন্য টেক্সটটি লিখুন।",
"controlCodeRequiredError": "কন্ট্রোল কোডের জন্য একটি অক্ষর লিখুন।",
"snippetRequiredError": "একটি স্নিপেট নির্বাচন করুন",
"conflictWarning": "এই সংমিশ্রণটি ইতিমধ্যেই {{combo}}এর সাথে আবদ্ধ। সংরক্ষণ করলে উভয়ই সক্রিয় হবে।",
"actionLabel": "পদক্ষেপ",
"actionCopy": "কপি নির্বাচন",
"actionPaste": "পেস্ট",
"actionSendControlCode": "Ctrl+অক্ষর সংকেত পাঠান",
"actionSendText": "টেক্সট পাঠান",
"actionRunSnippet": "বিদ্যমান কোড চালান",
"actionNextTab": "পরবর্তী ট্যাবে যান",
"actionPreviousTab": "পূর্ববর্তী ট্যাবে যান",
"actionOpenCommandPalette": "দ্রুত অনুসন্ধান খুলুন",
"controlCodeLabel": "চিঠি",
"textLabel": "পাঠানোর জন্য টেক্সট",
"appendEnterLabel": "পাঠানোর পর এন্টার চাপুন",
"snippetLabel": "স্নিপেট",
"selectSnippetPlaceholder": "একটি স্নিপেট নির্বাচন করুন",
"orphanedSnippetWarning": "স্নিপেটটি খুঁজে পাওয়া যায়নি",
"clipboardPermissionNote": "কিছু ব্রাউজারে প্রথমবার ব্যবহারের সময় ক্লিপবোর্ডের অনুমতি চাইতে পারে।"
"actionOpenCommandPalette": "দ্রুত অনুসন্ধান খুলুন"
},
"userProfile": {
"donateTitle": "টারমিক্সকে বাঁচিয়ে রাখুন",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "বিষয় আবশ্যক",
"ntfyToken": "অ্যাক্সেস টোকেন (ঐচ্ছিক)",
"channelSaveFailed": "চ্যানেল সংরক্ষণ করতে ব্যর্থ হয়েছে"
},
"macros": {
"title": "ম্যাক্রো",
"description": "টার্মিনালে সংরক্ষিত কমান্ডের একটি সিরিজ চালান এবং সার্ভার যা ফেরত পাঠায় তার উপর প্রতিক্রিয়া জানান।",
"target": "লক্ষ্য",
"noTerminal": "কোন টার্মিনাল খোলা নেই",
"empty": "এখনো কোনো ম্যাক্রো নেই",
"emptyHint": "টার্মিনালের কোনো কাজ, যেমন লগ ইন করা, কোনো সার্ভিস রিস্টার্ট করা বা প্রম্পটের উত্তর দেওয়া, স্বয়ংক্রিয় করতে একটি ম্যাক্রো তৈরি করুন।",
"create": "নতুন ম্যাক্রো",
"back": "ম্যাক্রোতে ফিরে যান",
"name": "নাম",
"namePlaceholder": "ওয়েব সার্ভার পুনরায় চালু করুন",
"nameRequired": "সংরক্ষণ করার আগে ম্যাক্রোটির একটি নাম দিন।",
"descriptionLabel": "বর্ণনা",
"descriptionPlaceholder": "এই ম্যাক্রোটি কী করে (ঐচ্ছিক)",
"unsaved": "সংরক্ষিত নয় এমন পরিবর্তন",
"save": "সংরক্ষণ করুন",
"saved": "ম্যাক্রো সংরক্ষিত",
"saveFailed": "ম্যাক্রোটি সংরক্ষণ করা যায়নি।",
"delete": "মুছে ফেলুন",
"deleteTitle": "এই ম্যাক্রোটি মুছে ফেলবেন?",
"deleteBody": "\"{{name}}\" স্থায়ীভাবে মুছে ফেলা হবে। এটি আর পূর্বাবস্থায় ফেরানো যাবে না।",
"deleted": "ম্যাক্রো মুছে ফেলা হয়েছে",
"cancel": "বাতিল করুন",
"run": "দৌড়",
"stop": "থামুন",
"running": "দৌড়াচ্ছে...",
"completed": "ম্যাক্রো শেষ হয়েছে",
"failed": "ম্যাক্রো ব্যর্থ হয়েছে",
"loadFailed": "ম্যাক্রো লোড করা যায়নি",
"needTerminal": "প্রথমে একটি টার্মিনাল খুলুন এবং সংযোগ করুন।",
"noSteps": "এই ম্যাক্রোতে এখনও কোনো ধাপ নেই। নিচে একটি যোগ করুন।",
"addStep": "ধাপ যোগ করুন",
"stepNumber": "ধাপ {{number}}",
"removeStep": "ধাপটি সরান",
"confirmRunTitle": "এই ম্যাক্রোটি চালাবেন?",
"confirmRunBody": "\"{{name}}\" {{host}} -এ কমান্ড পাঠাবে।",
"confirmRunAction": "ম্যাক্রো চালান",
"stepTypes": {
"send": {
"label": "কমান্ড পাঠান",
"help": "টার্মিনালে টেক্সট টাইপ করে, ঠিক যেন আপনি নিজেই টাইপ করেছেন।"
},
"wait": {
"label": "আউটপুটের জন্য অপেক্ষা করুন",
"help": "সার্ভার আপনার দেওয়া টেক্সটটি প্রিন্ট না করা পর্যন্ত এটি থেমে থাকে, অনেকটা পাসওয়ার্ড দেওয়ার সময়কার মতো।"
},
"delay": {
"label": "বিরতি",
"help": "পরবর্তী ধাপে যাওয়ার আগে একটি নির্দিষ্ট পরিমাণ সময় অপেক্ষা করে।"
},
"if": {
"label": "যদি আউটপুটে থাকে",
"help": "সার্ভার এ পর্যন্ত যা যা প্রিন্ট করেছে, তা যাচাই করে দুটি শাখার মধ্যে একটি চালায়।"
},
"repeat": {
"label": "পুনরাবৃত্তি",
"help": "এর ভেতরের ধাপগুলো একটি নির্দিষ্ট সংখ্যক বার চালায়।"
}
},
"send": {
"text": "কমান্ড বা টেক্সট",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "পাঠানোর পর এন্টার চাপুন",
"pressEnterHelp": "সাবমিট না করে টেক্সট টাইপ করতে এটি বন্ধ করুন।"
},
"wait": {
"pattern": "আউটপুটে থাকা পর্যন্ত অপেক্ষা করুন",
"patternPlaceholder": "পাসওয়ার্ড:",
"timeout": "হাল ছেড়ে দেওয়ার পর",
"onTimeout": "যদি এটি কখনও প্রদর্শিত না হয়",
"stop": "ম্যাক্রোটি বন্ধ করুন",
"continue": "তবুও এগিয়ে যান।"
},
"delay": {
"duration": "অপেক্ষা করুন"
},
"ifStep": {
"pattern": "যদি আউটপুটে থাকে",
"patternPlaceholder": "সক্রিয়: সক্রিয় (চলমান)",
"then": "তারপর এটা করুন",
"else": "অন্যথায় এটা করুন",
"emptyBranch": "কোন পদক্ষেপ নেই"
},
"repeat": {
"count": "সংখ্যা",
"times": "সময়"
},
"match": {
"useRegex": "রেগুলার এক্সপ্রেশনের সাথে মেলান",
"useRegexHelp": "লেখাটির সাথে হুবহু মিল রাখতে এটি বাদ দিন।",
"invalidRegex": "এটি একটি বৈধ রেগুলার এক্সপ্রেশন নয়, এটিকে সাধারণ টেক্সট হিসেবে মেলানো হবে।"
},
"seconds": "সেকেন্ড",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Configuració de la barra lateral...",
"hosts": "Amfitrions",
"snippets": "Fragments",
"macros": "Macros",
"hostManager": "Gestor d'amfitrions",
"credentials": "Credencials",
"connections": "Connexions",
@@ -869,7 +868,6 @@
"groupTags": "Etiquetes i pin",
"groupTunnels": "Túnels",
"groupJumpHosts": "Amfitrions de salt",
"groupQuickActions": "Accions ràpides",
"groupFeatureFlags": "Banderes de funcions",
"groupAdvanced": "Configuració avançada",
"preview": "Vista prèvia",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Emmagatzemar claus o contrasenyes SSH",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Supervisa les sessions tmux a tots els teus amfitrions",
"snippets": "Fragments",
"runSnippetDesc": "Executar a la pestanya activa del terminal",
"noSnippetsFound": "No s'han trobat fragments que coincideixin amb \"{{search}}\"",
"recentActivity": "Activitat recent",
"serversAndHosts": "Servidors i amfitrions",
"noHostsFound": "No s'han trobat hosts que coincideixin amb \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Pestanya anterior",
"hotkeysJumpTab": "Salta a la pestanya"
},
"snippets": {
"title": "Fragments",
"createSnippetTitle": "Crea un fragment",
"createSnippetDescription": "Crea un nou fragment d'ordre per a una execució ràpida",
"nameLabel": "Nom",
"namePlaceholder": "p. ex., reiniciar Nginx",
"descriptionLabel": "Descripció",
"descriptionPlaceholder": "Descripció opcional",
"optional": "Opcional",
"folderLabel": "Carpeta",
"noFolder": "Sense carpeta (Sense categoria)",
"commandLabel": "Comandament",
"commandPlaceholder": "p. ex., sudo systemctl reinicia nginx",
"typeLabel": "Tipus",
"typeCommand": "Comandament",
"typeNote": "Nota",
"noteLabel": "Nota",
"notePlaceholder": "p. ex., un text de recordatori o indicació reutilitzable",
"cancel": "Cancel·la",
"createSnippetButton": "Crea un fragment",
"createFolderTitle": "Crea una carpeta",
"createFolderDescription": "Organitza els teus fragments en carpetes",
"folderNameLabel": "Nom de la carpeta",
"folderNamePlaceholder": "p. ex., ordres del sistema, scripts de Docker",
"folderColorLabel": "Color de la carpeta",
"folderIconLabel": "Icona de carpeta",
"previewLabel": "Vista prèvia",
"folderNameFallback": "Nom de la carpeta",
"createFolderButton": "Crea una carpeta",
"targetTerminals": "Terminals de destinació",
"selectAll": "Tot",
"selectNone": "Cap",
"noTerminalTabsOpen": "No hi ha pestanyes de terminal obertes",
"searchPlaceholder": "Fragments de cerca...",
"newSnippet": "Nou fragment",
"newFolder": "Nova carpeta",
"run": "Corre",
"noSnippetsInFolder": "No hi ha fragments en aquesta carpeta",
"uncategorized": "Sense categoria",
"editSnippetTitle": "Edita el fragment",
"editSnippetDescription": "Actualitza aquest fragment d'ordres",
"saveSnippetButton": "Desa els canvis",
"createSuccess": "Fragment creat correctament",
"createFailed": "No s'ha pogut crear el fragment",
"updateSuccess": "Fragment actualitzat correctament",
"updateFailed": "No s'ha pogut actualitzar el fragment",
"deleteFailed": "No s'ha pogut suprimir el fragment",
"folderCreateSuccess": "Carpeta creada correctament",
"folderCreateFailed": "No s'ha pogut crear la carpeta",
"editFolderTitle": "Edita la carpeta",
"editFolderDescription": "Canvia el nom o l'aspecte d'aquesta carpeta",
"saveFolderButton": "Desa els canvis",
"editFolder": "Edita la carpeta",
"deleteFolder": "Suprimeix la carpeta",
"folderDeleteSuccess": "S'ha suprimit la carpeta \"{{name}}\"",
"folderDeleteFailed": "No s'ha pogut suprimir la carpeta",
"folderEditSuccess": "La carpeta s'ha actualitzat correctament",
"folderEditFailed": "No s'ha pogut actualitzar la carpeta",
"confirmRunMessage": "Executar \"{{name}}\"?",
"confirmRunButton": "Corre",
"runSuccess": "Ha executat \"{{name}}\" en terminals {{count}}",
"pasteSuccess": "S'ha enganxat \"{{name}}\" al(s) terminal(s) {{count}}",
"pasteToTerminal": "Enganxa",
"copySuccess": "S'ha copiat \"{{name}}\" al porta-retalls",
"shareTitle": "Compartir fragment",
"shareUser": "Usuari",
"shareRole": "Rol",
"selectUser": "Selecciona un usuari...",
"selectRole": "Selecciona un rol...",
"shareSuccess": "Fragment compartit correctament",
"shareFailed": "No s'ha pogut compartir el fragment",
"revokeSuccess": "Accés revocat",
"revokeFailed": "No s'ha pogut revocar l'accés",
"currentAccess": "Accés actual",
"shareLoadError": "No s'han pogut carregar les dades compartides",
"loading": "S'està carregant...",
"close": "Tanca",
"reorderFailed": "No s'ha pogut desar l'ordre del fragment",
"importExport": "Importació / Exportació",
"exportBtn": "Exporta JSON",
"importBtn": "Importa JSON",
"exportSuccess": "Fragments exportats correctament",
"exportFailed": "No s'han pogut exportar els fragments",
"importTitle": "Importa fragments",
"importDescription": "Importa fragments i carpetes des d'un fitxer JSON exportat per Termix",
"importDropOrClick": "Deixa anar un fitxer JSON aquí o fes clic per navegar",
"importSelectedFile": "Seleccionat: {{name}}",
"importOverwrite": "Sobreescriu els fragments existents amb el mateix nom i carpeta",
"importStartBtn": "Importa",
"importSuccess": "Importació completada: {{snippets}} fragment(s) afegit(s), {{updated}} actualitzat, {{skipped}} omès, {{folders}} carpeta(es) afegida(es)",
"importFailed": "No s'han pogut importar els fragments",
"importInvalidFile": "Fitxer no vàlid: s'esperava un objecte JSON amb fragments o matrius de carpetes.",
"targetHostsLabel": "Amfitrions de destinació",
"targetHostsHint": "Assigneu hosts per executar aquest fragment directament sense un terminal obert.",
"noHostsAvailable": "No hi ha cap amfitrió configurat",
"clearTargetHosts": "Esborra-ho tot",
"hasTargetHosts": "Té amfitrions de destinació",
"runOnTargets": "Executar en objectius",
"directRunSuccess": "Ha executat \"{{name}}\" en {{count}} host(s)",
"directRunPartialFail": "\"{{name}}\" ha fallat en un o més amfitrions",
"executionResultTitle": "Resultats de l'execució: {{name}}",
"executionResultDescription": "Sortida de l'execució del fragment a cada amfitrió de destinació.",
"executionSuccess": "Èxit",
"executionFailed": "Fallat",
"expandAll": "Expandir totes les carpetes",
"collapseAll": "Replega totes les carpetes",
"settings": "Configuració del fragment",
"variablesHint": "Variables: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Etiqueta}",
"variablesDialogTitle": "Executa \"{{name}}\"",
"variablesDialogDescription": "Empleneu els valors que necessita aquest fragment abans d'executar-lo.",
"variablesPreviewLabel": "Vista prèvia",
"variablesConfirmButton": "Corre"
},
"keybindings": {
"title": "Dreceres de teclat",
"description": "Personalitza les dreceres de copiar, enganxar i controlar del terminal, o vincula tecles per enviar text o executar un fragment.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Feu clic i premeu les tecles",
"recording": "Premeu les tecles...",
"comboRequiredError": "Premeu primer una combinació de tecles",
"textRequiredError": "Introduïu el text per enviar",
"controlCodeRequiredError": "Introduïu una sola lletra per al codi de control",
"snippetRequiredError": "Selecciona un fragment",
"conflictWarning": "Aquesta combinació ja està vinculada a {{combo}}. Si deseu, totes dues es dispararan.",
"actionLabel": "Acció",
"actionCopy": "Copia la selecció",
"actionPaste": "Enganxa",
"actionSendControlCode": "Envia el senyal Ctrl+lletra",
"actionSendText": "Enviar text",
"actionRunSnippet": "Executa el fragment existent",
"actionNextTab": "Canvia a la pestanya següent",
"actionPreviousTab": "Canvia a la pestanya anterior",
"actionOpenCommandPalette": "Obre la cerca ràpida",
"controlCodeLabel": "Carta",
"textLabel": "Text per enviar",
"appendEnterLabel": "Premeu Intro després d'enviar",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Selecciona un fragment",
"orphanedSnippetWarning": "fragment no trobat",
"clipboardPermissionNote": "Pot demanar permisos al porta-retalls en el primer ús en alguns navegadors."
"actionOpenCommandPalette": "Obre la cerca ràpida"
},
"userProfile": {
"donateTitle": "Mantingueu viva la Termix",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "El tema és obligatori",
"ntfyToken": "Token d'accés (opcional)",
"channelSaveFailed": "No s'ha pogut desar el canal"
},
"macros": {
"title": "Macros",
"description": "Executa una sèrie d'ordres desades en un terminal i reacciona al que el servidor envia de tornada.",
"target": "Objectiu",
"noTerminal": "Cap terminal obert",
"empty": "Encara no hi ha macros",
"emptyHint": "Crea una macro per automatitzar un flux de terminal, com ara iniciar la sessió, reiniciar un servei o respondre a les indicacions.",
"create": "Nova macro",
"back": "Torna a les macros",
"name": "Nom",
"namePlaceholder": "Reinicia el servidor web",
"nameRequired": "Doneu un nom a la macro abans de desar-la",
"descriptionLabel": "Descripció",
"descriptionPlaceholder": "Què fa aquesta macro (opcional)",
"unsaved": "Canvis no desats",
"save": "Desa",
"saved": "Macro desada",
"saveFailed": "No s'ha pogut desar la macro",
"delete": "Suprimeix",
"deleteTitle": "Voleu suprimir aquesta macro?",
"deleteBody": "«{{name}}» s'eliminarà permanentment. Això no es pot desfer.",
"deleted": "Macro suprimida",
"cancel": "Cancel·la",
"run": "Corre",
"stop": "Atura",
"running": "Corrent...",
"completed": "Macro acabada",
"failed": "La macro ha fallat",
"loadFailed": "No s'han pogut carregar les macros",
"needTerminal": "Obriu i connecteu primer un terminal",
"noSteps": "Aquesta macro encara no té cap pas. Afegeix-ne un a continuació.",
"addStep": "Afegeix un pas",
"stepNumber": "Pas {{number}}",
"removeStep": "Elimina el pas",
"confirmRunTitle": "Voleu executar aquesta macro?",
"confirmRunBody": "\"{{name}}\" enviarà ordres a {{host}}.",
"confirmRunAction": "Executa macro",
"stepTypes": {
"send": {
"label": "Envia l'ordre",
"help": "Escriu text al terminal, igual que si l'haguessis escrit."
},
"wait": {
"label": "Espera la sortida",
"help": "Es posa en pausa fins que el servidor imprimeix el text que anomeneu, com si fos una sol·licitud de contrasenya."
},
"delay": {
"label": "Pausa",
"help": "Espera un temps fix abans del següent pas."
},
"if": {
"label": "Si la sortida conté",
"help": "Comprova tot el que el servidor ha imprès fins ara i després executa una de les dues branques."
},
"repeat": {
"label": "Repetir",
"help": "Executa els passos que hi ha dins un nombre determinat de vegades."
}
},
"send": {
"text": "Ordre o text",
"textPlaceholder": "reinici de systemctl a nginx",
"pressEnter": "Premeu Intro després d'enviar",
"pressEnterHelp": "Desactiva això per escriure text sense enviar-lo."
},
"wait": {
"pattern": "Espereu fins que la sortida contingui",
"patternPlaceholder": "contrasenya:",
"timeout": "Renunciar després",
"onTimeout": "Si mai apareix",
"stop": "Atura la macro",
"continue": "Continua igualment"
},
"delay": {
"duration": "Espera"
},
"ifStep": {
"pattern": "Si la sortida conté",
"patternPlaceholder": "Actiu: actiu (en funcionament)",
"then": "Aleshores fes això",
"else": "Si no, fes això",
"emptyBranch": "Sense esglaons"
},
"repeat": {
"count": "Nombre de vegades",
"times": "vegades"
},
"match": {
"useRegex": "Coincideix amb una expressió regular",
"useRegexHelp": "Deixa això desactivat perquè el text coincideixi exactament com està escrit.",
"invalidRegex": "No és una expressió regular vàlida, es trobarà com a text sense format."
},
"seconds": "segons",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Nastavení postranního panelu...",
"hosts": "Hostitelé",
"snippets": "Úryvky",
"macros": "Makra",
"hostManager": "Hostitelský manažer",
"credentials": "Pověření",
"connections": "Připojení",
@@ -869,7 +868,6 @@
"groupTags": "Štítky a pin",
"groupTunnels": "Tunely",
"groupJumpHosts": "Hostitelé skoků",
"groupQuickActions": "Rychlé akce",
"groupFeatureFlags": "Vlajky funkcí",
"groupAdvanced": "Pokročilá konfigurace",
"preview": "Náhled",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Ukládání SSH klíčů nebo hesel",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Monitorujte relace tmux napříč vašimi hostiteli",
"snippets": "Úryvky",
"runSnippetDesc": "Spustit v aktivní kartě terminálu",
"noSnippetsFound": "Nebyly nalezeny žádné úryvky odpovídající „{{search}}“",
"recentActivity": "Nedávná aktivita",
"serversAndHosts": "Servery a hostitelé",
"noHostsFound": "Nenalezeny žádné hostitele odpovídající \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Předchozí karta",
"hotkeysJumpTab": "Přejít na kartu"
},
"snippets": {
"title": "Úryvky",
"createSnippetTitle": "Vytvořit úryvek",
"createSnippetDescription": "Vytvořte nový úryvek příkazu pro rychlé spuštění",
"nameLabel": "Jméno",
"namePlaceholder": "např. restartujte Nginx",
"descriptionLabel": "Popis",
"descriptionPlaceholder": "Volitelný popis",
"optional": "Volitelný",
"folderLabel": "Složka",
"noFolder": "Žádná složka (Nezařazené)",
"commandLabel": "Příkaz",
"commandPlaceholder": "např. sudo systemctl restart nginx",
"typeLabel": "Typ",
"typeCommand": "Příkaz",
"typeNote": "Poznámka",
"noteLabel": "Poznámka",
"notePlaceholder": "např. opakovaně použitelný text s výzvou nebo připomínkou",
"cancel": "Zrušit",
"createSnippetButton": "Vytvořit úryvek",
"createFolderTitle": "Vytvořit složku",
"createFolderDescription": "Uspořádejte si úryvky do složek",
"folderNameLabel": "Název složky",
"folderNamePlaceholder": "např. systémové příkazy, Docker skripty",
"folderColorLabel": "Barva složky",
"folderIconLabel": "Ikona složky",
"previewLabel": "Náhled",
"folderNameFallback": "Název složky",
"createFolderButton": "Vytvořit složku",
"targetTerminals": "Cílové terminály",
"selectAll": "Vše",
"selectNone": "Žádný",
"noTerminalTabsOpen": "Žádné otevřené karty terminálu",
"searchPlaceholder": "Vyhledat úryvky...",
"newSnippet": "Nový úryvek",
"newFolder": "Nová složka",
"run": "Běh",
"noSnippetsInFolder": "V této složce nejsou žádné úryvky",
"uncategorized": "Nezařazené",
"editSnippetTitle": "Upravit úryvek",
"editSnippetDescription": "Aktualizujte tento úryvek příkazu",
"saveSnippetButton": "Uložit změny",
"createSuccess": "Úryvek byl úspěšně vytvořen",
"createFailed": "Nepodařilo se vytvořit úryvek",
"updateSuccess": "Úryvek byl úspěšně aktualizován",
"updateFailed": "Nepodařilo se aktualizovat úryvek",
"deleteFailed": "Nepodařilo se smazat úryvek",
"folderCreateSuccess": "Složka byla úspěšně vytvořena",
"folderCreateFailed": "Nepodařilo se vytvořit složku",
"editFolderTitle": "Upravit složku",
"editFolderDescription": "Přejmenovat nebo změnit vzhled této složky",
"saveFolderButton": "Uložit změny",
"editFolder": "Upravit složku",
"deleteFolder": "Smazat složku",
"folderDeleteSuccess": "Složka „{{name}}“ smazána",
"folderDeleteFailed": "Složku se nepodařilo smazat",
"folderEditSuccess": "Složka byla úspěšně aktualizována",
"folderEditFailed": "Nepodařilo se aktualizovat složku",
"confirmRunMessage": "Spustit „{{name}}“?",
"confirmRunButton": "Běh",
"runSuccess": "Spustil „{{name}}“ v {{count}} terminálu(ů)",
"pasteSuccess": "Vloženo „{{name}}“ do terminálu(ů) {{count}}",
"pasteToTerminal": "Pasta",
"copySuccess": "Zkopírováno „{{name}}“ do schránky",
"shareTitle": "Sdílet úryvek",
"shareUser": "Uživatel",
"shareRole": "Role",
"selectUser": "Vyberte uživatele...",
"selectRole": "Vyberte roli...",
"shareSuccess": "Úryvek byl úspěšně sdílen",
"shareFailed": "Sdílení úryvku se nezdařilo",
"revokeSuccess": "Přístup zrušen",
"revokeFailed": "Zrušení přístupu se nezdařilo",
"currentAccess": "Aktuální přístup",
"shareLoadError": "Nepodařilo se načíst data sdílení",
"loading": "Načítání...",
"close": "Blízko",
"reorderFailed": "Nepodařilo se uložit pořadí úryvků",
"importExport": "Import / Export",
"exportBtn": "Exportovat JSON",
"importBtn": "Importovat JSON",
"exportSuccess": "Úryvky byly úspěšně exportovány",
"exportFailed": "Export úryvků se nezdařilo",
"importTitle": "Importovat úryvky",
"importDescription": "Import úryvků a složek ze souboru JSON exportovaného aplikací Termix",
"importDropOrClick": "Vložte sem soubor JSON nebo klikněte pro procházení",
"importSelectedFile": "Vybráno: {{name}}",
"importOverwrite": "Přepsat existující úryvky stejným názvem a složkou",
"importStartBtn": "Importovat",
"importSuccess": "Import dokončen: {{snippets}} přidán(y), {{updated}} aktualizováno, {{skipped}} přeskočeno, {{folders}} přidáno(y) složek",
"importFailed": "Import úryvků se nezdařilo",
"importInvalidFile": "Neplatný soubor: očekával se objekt JSON s úryvky kódu nebo poli složek.",
"targetHostsLabel": "Cílové hostitele",
"targetHostsHint": "Přiřaďte hostitele, aby spustili tento úryvek přímo bez otevřeného terminálu.",
"noHostsAvailable": "Žádní hostitelé nakonfigurováni",
"clearTargetHosts": "Vymazat vše",
"hasTargetHosts": "Má cílové hostitele",
"runOnTargets": "Běh na cíle",
"directRunSuccess": "Spuštěno „{{name}}“ na hostiteli {{count}}",
"directRunPartialFail": "„{{name}}“ selhalo na jednom nebo více hostitelích",
"executionResultTitle": "Výsledky provedení: {{name}}",
"executionResultDescription": "Výstup ze spuštění úryvku kódu na každém cílovém hostiteli.",
"executionSuccess": "Úspěch",
"executionFailed": "Selhalo",
"expandAll": "Rozbalit všechny složky",
"collapseAll": "Sbalit všechny složky",
"settings": "Nastavení úryvků",
"variablesHint": "Proměnné: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Popis}",
"variablesDialogTitle": "Spustit „{{name}}“",
"variablesDialogDescription": "Před spuštěním úryvku kódu vyplňte jeho potřebné hodnoty.",
"variablesPreviewLabel": "Náhled",
"variablesConfirmButton": "Běh"
},
"keybindings": {
"title": "Klávesové zkratky",
"description": "Přizpůsobte si klávesové zkratky pro kopírování, vkládání a ovládání v terminálu nebo propojte klávesy pro odeslání textu nebo spuštění úryvku kódu.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klikněte a stiskněte klávesy",
"recording": "Stiskněte klávesy...",
"comboRequiredError": "Nejprve stiskněte kombinaci kláves",
"textRequiredError": "Zadejte text k odeslání",
"controlCodeRequiredError": "Zadejte jedno písmeno pro kontrolní kód",
"snippetRequiredError": "Vyberte úryvek",
"conflictWarning": "Tato kombinace je již vázána na {{combo}}. Uložením se obě spustí.",
"actionLabel": "Akce",
"actionCopy": "Kopírovat výběr",
"actionPaste": "Pasta",
"actionSendControlCode": "Odeslat signál Ctrl+písmeno",
"actionSendText": "Odeslat text",
"actionRunSnippet": "Spustit existující úryvek kódu",
"actionNextTab": "Přepnout na další kartu",
"actionPreviousTab": "Přepnout na předchozí kartu",
"actionOpenCommandPalette": "Otevřít rychlé vyhledávání",
"controlCodeLabel": "Dopis",
"textLabel": "Text k odeslání",
"appendEnterLabel": "Po odeslání stiskněte Enter",
"snippetLabel": "Úryvek",
"selectSnippetPlaceholder": "Vyberte úryvek",
"orphanedSnippetWarning": "úryvek nenalezen",
"clipboardPermissionNote": "V některých prohlížečích se může při prvním použití zobrazit výzva k zadání povolení ke schránce."
"actionOpenCommandPalette": "Otevřít rychlé vyhledávání"
},
"userProfile": {
"donateTitle": "Udrž Termixe naživu",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Téma je povinné",
"ntfyToken": "Přístupový token (volitelné)",
"channelSaveFailed": "Uložení kanálu se nezdařilo"
},
"macros": {
"title": "Makra",
"description": "Spusťte v terminálu uloženou sérii příkazů a reagujte na to, co server odešle zpět.",
"target": "Cíl",
"noTerminal": "Žádný otevřený terminál",
"empty": "Zatím žádná makra",
"emptyHint": "Vytvořte makro pro automatizaci procesů v terminálu, jako je přihlašování, restartování služby nebo odpovídání na výzvy.",
"create": "Nové makro",
"back": "Zpět k makrům",
"name": "Jméno",
"namePlaceholder": "Restartujte webový server",
"nameRequired": "Před uložením zadejte makro název",
"descriptionLabel": "Popis",
"descriptionPlaceholder": "Co toto makro dělá (volitelné)",
"unsaved": "Neuložené změny",
"save": "Uložit",
"saved": "Makro uloženo",
"saveFailed": "Makro se nepodařilo uložit.",
"delete": "Vymazat",
"deleteTitle": "Smazat toto makro?",
"deleteBody": "Soubor „{{name}}“ bude trvale smazán. Tuto akci nelze vrátit zpět.",
"deleted": "Makro smazáno",
"cancel": "Zrušit",
"run": "Běh",
"stop": "Zastávka",
"running": "Běh...",
"completed": "Makro dokončeno",
"failed": "Makro selhalo",
"loadFailed": "Nepodařilo se načíst makra",
"needTerminal": "Nejprve otevřete a připojte terminál",
"noSteps": "Toto makro zatím nemá žádné kroky. Přidejte jeden níže.",
"addStep": "Přidat krok",
"stepNumber": "Krok {{number}}",
"removeStep": "Odebrat krok",
"confirmRunTitle": "Spustit toto makro?",
"confirmRunBody": "„{{name}}“ odešle příkazy pro {{host}}.",
"confirmRunAction": "Spustit makro",
"stepTypes": {
"send": {
"label": "Odeslat příkaz",
"help": "Zadá text do terminálu, stejně jako byste ho napsali."
},
"wait": {
"label": "Počkejte na výstup",
"help": "Pozastaví se, dokud server nevypíše text, který pojmenujete, například výzvu k zadání hesla."
},
"delay": {
"label": "Pauza",
"help": "Čeká pevně stanovenou dobu před dalším krokem."
},
"if": {
"label": "Pokud výstup obsahuje",
"help": "Zkontroluje vše, co server dosud vytiskl, a poté spustí jednu ze dvou větví."
},
"repeat": {
"label": "Opakovat",
"help": "Spustí kroky uvnitř něj tolikrát, kolikrát je nastaveno."
}
},
"send": {
"text": "Příkaz nebo text",
"textPlaceholder": "restartování systemctl v Nginxu",
"pressEnter": "Po odeslání stiskněte Enter",
"pressEnterHelp": "Vypněte tuto funkci, chcete-li psát text bez jeho odesílání."
},
"wait": {
"pattern": "Počkejte, dokud výstup neobsahuje",
"patternPlaceholder": "heslo:",
"timeout": "Vzdej se po",
"onTimeout": "Pokud se to nikdy neobjeví",
"stop": "Zastavit makro",
"continue": "Stejně pokračuj"
},
"delay": {
"duration": "Čekat na"
},
"ifStep": {
"pattern": "Pokud výstup obsahuje",
"patternPlaceholder": "Aktivní: aktivní (běží)",
"then": "Pak udělej tohle",
"else": "Jinak to udělej",
"emptyBranch": "Žádné schody"
},
"repeat": {
"count": "Počet opakování",
"times": "krát"
},
"match": {
"useRegex": "Porovnání s regulárním výrazem",
"useRegexHelp": "Nechte toto vypnuté, aby text přesně odpovídal napsanému textu.",
"invalidRegex": "Není to platný regulární výraz, bude nalezen jako prostý text."
},
"seconds": "sekundy",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Indstillinger for sidebjælke...",
"hosts": "Værter",
"snippets": "Uddrag",
"macros": "Makroer",
"hostManager": "Værtsadministrator",
"credentials": "Akkreditiver",
"connections": "Forbindelser",
@@ -869,7 +868,6 @@
"groupTags": "Tags og pin",
"groupTunnels": "Tunneler",
"groupJumpHosts": "Spring værter",
"groupQuickActions": "Hurtige handlinger",
"groupFeatureFlags": "Funktionsflag",
"groupAdvanced": "Avanceret konfiguration",
"preview": "Forhåndsvisning",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Gem SSH-nøgler eller adgangskoder",
"tmuxMonitor": "Tmux-skærm",
"tmuxMonitorDesc": "Overvåg tmux-sessioner på tværs af dine værter",
"snippets": "Uddrag",
"runSnippetDesc": "Kør i den aktive terminalfane",
"noSnippetsFound": "Ingen uddrag fundet, der matcher \"{{search}}\"",
"recentActivity": "Seneste aktivitet",
"serversAndHosts": "Servere og værter",
"noHostsFound": "Ingen værter fundet, der matcher \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Forrige fane",
"hotkeysJumpTab": "Gå til faneblad"
},
"snippets": {
"title": "Uddrag",
"createSnippetTitle": "Opret uddrag",
"createSnippetDescription": "Opret et nyt kommandosnip til hurtig udførelse",
"nameLabel": "Navn",
"namePlaceholder": "f.eks. Genstart Nginx",
"descriptionLabel": "Beskrivelse",
"descriptionPlaceholder": "Valgfri beskrivelse",
"optional": "Valgfri",
"folderLabel": "Folder",
"noFolder": "Ingen mappe (Ukategoriseret)",
"commandLabel": "Kommando",
"commandPlaceholder": "f.eks. sudo systemctl genstart nginx",
"typeLabel": "Type",
"typeCommand": "Kommando",
"typeNote": "Note",
"noteLabel": "Note",
"notePlaceholder": "f.eks. en genanvendelig prompt eller påmindelsestekst",
"cancel": "Ophæve",
"createSnippetButton": "Opret uddrag",
"createFolderTitle": "Opret mappe",
"createFolderDescription": "Organiser dine uddrag i mapper",
"folderNameLabel": "Mappenavn",
"folderNamePlaceholder": "f.eks. systemkommandoer, Docker-scripts",
"folderColorLabel": "Mappefarve",
"folderIconLabel": "Mappeikon",
"previewLabel": "Forhåndsvisning",
"folderNameFallback": "Mappenavn",
"createFolderButton": "Opret mappe",
"targetTerminals": "Target Terminals",
"selectAll": "Alle",
"selectNone": "Ingen",
"noTerminalTabsOpen": "Ingen terminalfaner åbne",
"searchPlaceholder": "Søg i uddrag...",
"newSnippet": "Nyt uddrag",
"newFolder": "Ny mappe",
"run": "Løbe",
"noSnippetsInFolder": "Ingen uddrag i denne mappe",
"uncategorized": "Ukategoriseret",
"editSnippetTitle": "Rediger uddrag",
"editSnippetDescription": "Opdater dette kommandosnip",
"saveSnippetButton": "Gem ændringer",
"createSuccess": "Kodestykket er oprettet",
"createFailed": "Kunne ikke oprette kodestykket",
"updateSuccess": "Uddraget er opdateret",
"updateFailed": "Kunne ikke opdatere uddraget",
"deleteFailed": "Kunne ikke slette uddraget",
"folderCreateSuccess": "Mappen er oprettet",
"folderCreateFailed": "Kunne ikke oprette en mappe",
"editFolderTitle": "Rediger mappe",
"editFolderDescription": "Omdøb eller skift udseende af denne mappe",
"saveFolderButton": "Gem ændringer",
"editFolder": "Rediger mappe",
"deleteFolder": "Slet mappe",
"folderDeleteSuccess": "Mappen \"{{name}}\" er slettet",
"folderDeleteFailed": "Kunne ikke slette mappen",
"folderEditSuccess": "Mappen er blevet opdateret",
"folderEditFailed": "Mappen kunne ikke opdateres",
"confirmRunMessage": "Kør \"{{name}}\"?",
"confirmRunButton": "Løbe",
"runSuccess": "Kørte \"{{name}}\" i {{count}} terminal(er)",
"pasteSuccess": "Indsatte \"{{name}}\" i {{count}} terminal(er)",
"pasteToTerminal": "Indsæt",
"copySuccess": "Kopieret \"{{name}}\" til udklipsholderen",
"shareTitle": "Del uddrag",
"shareUser": "Bruger",
"shareRole": "Rolle",
"selectUser": "Vælg en bruger...",
"selectRole": "Vælg en rolle...",
"shareSuccess": "Uddraget blev delt",
"shareFailed": "Kunne ikke dele uddraget",
"revokeSuccess": "Adgang tilbagekaldt",
"revokeFailed": "Adgang kunne ikke tilbagekaldes",
"currentAccess": "Nuværende adgang",
"shareLoadError": "Kunne ikke indlæse delingsdata",
"loading": "Indlæser...",
"close": "Tæt",
"reorderFailed": "Kunne ikke gemme rækkefølgen af uddrag",
"importExport": "Import/eksport",
"exportBtn": "Eksportér JSON",
"importBtn": "Importér JSON",
"exportSuccess": "Snippets eksporteret",
"exportFailed": "Kunne ikke eksportere uddrag",
"importTitle": "Importér uddrag",
"importDescription": "Importer snippets og mapper fra en JSON-fil eksporteret af Termix",
"importDropOrClick": "Slip en JSON-fil her, eller klik for at gennemse",
"importSelectedFile": "Valgt: {{name}}",
"importOverwrite": "Overskriv eksisterende kodestykker med samme navn og mappe",
"importStartBtn": "Importere",
"importSuccess": "Import fuldført: {{snippets}} kodestykke(r) tilføjet, {{updated}} opdateret, {{skipped}} sprunget over, {{folders}} mappe(r) tilføjet",
"importFailed": "Kunne ikke importere uddrag",
"importInvalidFile": "Ugyldig fil: forventede et JSON-objekt med snippets eller mapper",
"targetHostsLabel": "Målværter",
"targetHostsHint": "Tildel værter til at køre dette kodestykke direkte uden en åben terminal.",
"noHostsAvailable": "Ingen værter konfigureret",
"clearTargetHosts": "Ryd alt",
"hasTargetHosts": "Har målværter",
"runOnTargets": "Løb på mål",
"directRunSuccess": "Kørte \"{{name}}\" på {{count}} vært(er)",
"directRunPartialFail": "\"{{name}}\" mislykkedes på en eller flere værter",
"executionResultTitle": "Udførelsesresultater: {{name}}",
"executionResultDescription": "Output fra kørsel af kodestykket på hver målvært.",
"executionSuccess": "Succes",
"executionFailed": "Mislykkedes",
"expandAll": "Udvid alle mapper",
"collapseAll": "Skjul alle mapper",
"settings": "Indstillinger for uddrag",
"variablesHint": "Variabler: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Kør \"{{name}}\"",
"variablesDialogDescription": "Udfyld de værdier, som dette kodestykke skal bruge, før det køres.",
"variablesPreviewLabel": "Forhåndsvisning",
"variablesConfirmButton": "Løbe"
},
"keybindings": {
"title": "Tastaturgenveje",
"description": "Tilpas genveje til kopiering, indsættelse og styring af terminaler, eller bind taster for at sende tekst eller køre et kodestykke.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klik og tryk på tasterne",
"recording": "Tryk på tasterne...",
"comboRequiredError": "Tryk først på en tastekombination",
"textRequiredError": "Indtast den tekst, der skal sendes",
"controlCodeRequiredError": "Indtast et enkelt bogstav for kontrolkoden",
"snippetRequiredError": "Vælg et uddrag",
"conflictWarning": "Denne kombination er allerede bundet til {{combo}}. Hvis du gemmer dem, aktiveres begge.",
"actionLabel": "Handling",
"actionCopy": "Kopiér valg",
"actionPaste": "Indsæt",
"actionSendControlCode": "Send Ctrl+bogstavsignal",
"actionSendText": "Send sms",
"actionRunSnippet": "Kør eksisterende kodestykke",
"actionNextTab": "Skift til næste faneblad",
"actionPreviousTab": "Skift til forrige fane",
"actionOpenCommandPalette": "Åbn hurtigsøgning",
"controlCodeLabel": "Bogstav",
"textLabel": "Tekst der skal sendes",
"appendEnterLabel": "Tryk Enter efter afsendelse",
"snippetLabel": "Uddrag",
"selectSnippetPlaceholder": "Vælg et uddrag",
"orphanedSnippetWarning": "uddrag ikke fundet",
"clipboardPermissionNote": "Kan bede om tilladelse til udklipsholder ved første brug i nogle browsere."
"actionOpenCommandPalette": "Åbn hurtigsøgning"
},
"userProfile": {
"donateTitle": "Hold Termix i live",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Emne er påkrævet",
"ntfyToken": "Adgangstoken (valgfrit)",
"channelSaveFailed": "Kanalen kunne ikke gemmes"
},
"macros": {
"title": "Makroer",
"description": "Kør en gemt række kommandoer i en terminal, og reager på, hvad serveren sender tilbage.",
"target": "Mål",
"noTerminal": "Ingen terminal åben",
"empty": "Ingen makroer endnu",
"emptyHint": "Opret en makro for at automatisere et terminalflow, f.eks. at logge ind, genstarte en tjeneste eller besvare prompts.",
"create": "Ny makro",
"back": "Tilbage til makroer",
"name": "Navn",
"namePlaceholder": "Genstart webserveren",
"nameRequired": "Giv makroen et navn, før du gemmer den",
"descriptionLabel": "Beskrivelse",
"descriptionPlaceholder": "Hvad denne makro gør (valgfrit)",
"unsaved": "Ikke-gemte ændringer",
"save": "Spare",
"saved": "Makro gemt",
"saveFailed": "Makroen kunne ikke gemmes",
"delete": "Slet",
"deleteTitle": "Slette denne makro?",
"deleteBody": "\"{{name}}\" vil blive slettet permanent. Dette kan ikke fortrydes.",
"deleted": "Makro slettet",
"cancel": "Ophæve",
"run": "Løbe",
"stop": "Stop",
"running": "Løber...",
"completed": "Makro færdig",
"failed": "Makro mislykkedes",
"loadFailed": "Makroer kunne ikke indlæses",
"needTerminal": "Åbn og tilslut først en terminal",
"noSteps": "Denne makro har endnu ingen trin. Tilføj et nedenfor.",
"addStep": "Tilføj trin",
"stepNumber": "Trin {{number}}",
"removeStep": "Fjern trin",
"confirmRunTitle": "Køre denne makro?",
"confirmRunBody": "\"{{name}}\" sender kommandoer til {{host}}.",
"confirmRunAction": "Kør makro",
"stepTypes": {
"send": {
"label": "Send kommando",
"help": "Skriver tekst i terminalen, på samme måde som hvis du havde skrevet den."
},
"wait": {
"label": "Vent på output",
"help": "Holder pause, indtil serveren udskriver den tekst, du navngiver, f.eks. en adgangskodeprompt."
},
"delay": {
"label": "Pause",
"help": "Venter et fast tidsrum før næste trin."
},
"if": {
"label": "Hvis outputtet indeholder",
"help": "Kontrollerer alt, hvad serveren har udskrevet indtil videre, og kører derefter en af to grene."
},
"repeat": {
"label": "Gentage",
"help": "Kører trinnene inde i den et bestemt antal gange."
}
},
"send": {
"text": "Kommando eller tekst",
"textPlaceholder": "systemctl genstart nginx",
"pressEnter": "Tryk Enter efter afsendelse",
"pressEnterHelp": "Slå dette fra for at skrive tekst uden at indsende den."
},
"wait": {
"pattern": "Vent indtil outputtet indeholder",
"patternPlaceholder": "adgangskode:",
"timeout": "Giv op efter",
"onTimeout": "Hvis det aldrig dukker op",
"stop": "Stop makroen",
"continue": "Fortsæt alligevel"
},
"delay": {
"duration": "Vent på"
},
"ifStep": {
"pattern": "Hvis outputtet indeholder",
"patternPlaceholder": "Aktiv: aktiv (løber)",
"then": "Så gør dette",
"else": "Ellers gør dette",
"emptyBranch": "Ingen trin"
},
"repeat": {
"count": "Antal gange",
"times": "gange"
},
"match": {
"useRegex": "Match med et regulært udtryk",
"useRegexHelp": "Slå dette fra for at teksten skal være præcis som den er skrevet.",
"invalidRegex": "Ikke et gyldigt regulært udtryk, det vil blive matchet som almindelig tekst"
},
"seconds": "sekunder",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Seitenleisteneinstellungen...",
"hosts": "Hosts",
"snippets": "Snippets",
"macros": "Makros",
"hostManager": "Host-Manager",
"credentials": "Zugangsdaten",
"connections": "Verbindungen",
@@ -869,7 +868,6 @@
"groupTags": "Etiketten & Pins",
"groupTunnels": "Tunnel",
"groupJumpHosts": "Jump-Moderatoren",
"groupQuickActions": "Schnelle Aktionen",
"groupFeatureFlags": "Funktionsflaggen",
"groupAdvanced": "Erweiterte Konfiguration",
"preview": "Vorschau",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH-Schlüssel oder Passwörter speichern",
"tmuxMonitor": "Tmux-Monitor",
"tmuxMonitorDesc": "Tmux-Sitzungen hostübergreifend überwachen",
"snippets": "Ausschnitte",
"runSnippetDesc": "Im aktiven Terminal-Tab ausführen",
"noSnippetsFound": "Es wurden keine passenden Code-Snippets für \"{{search}}\" gefunden.",
"recentActivity": "Letzte Aktivität",
"serversAndHosts": "Server & Hosts",
"noHostsFound": "Keine Hosts gefunden, die \"{{search}}\" entsprechen",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Vorheriger Tab",
"hotkeysJumpTab": "Zum Tab springen"
},
"snippets": {
"title": "Snippets",
"createSnippetTitle": "Snippet erstellen",
"createSnippetDescription": "Einen neuen Befehlsschnipsel für die schnelle Ausführung erstellen",
"nameLabel": "Name",
"namePlaceholder": "z. B. Nginx neustarten",
"descriptionLabel": "Beschreibung",
"descriptionPlaceholder": "Optionale Beschreibung",
"optional": "Optional",
"folderLabel": "Ordner",
"noFolder": "Kein Ordner (Nicht kategorisiert)",
"commandLabel": "Befehl",
"commandPlaceholder": "z. B. sudo systemctl restart nginx",
"typeLabel": "Typ",
"typeCommand": "Befehl",
"typeNote": "Notiz",
"noteLabel": "Notiz",
"notePlaceholder": "z. B. ein wiederverwendbarer Hinweis oder Erinnerungstext",
"cancel": "Abbrechen",
"createSnippetButton": "Snippet erstellen",
"createFolderTitle": "Ordner erstellen",
"createFolderDescription": "Organisieren Sie Ihre Snippets in Ordnern",
"folderNameLabel": "Ordnername",
"folderNamePlaceholder": "z. B. Systembefehle, Docker-Skripte",
"folderColorLabel": "Ordnerfarbe",
"folderIconLabel": "Ordnersymbol",
"previewLabel": "Vorschau",
"folderNameFallback": "Ordnername",
"createFolderButton": "Ordner erstellen",
"targetTerminals": "Zielterminale",
"selectAll": "Alle",
"selectNone": "Keine",
"noTerminalTabsOpen": "Keine Terminal-Tabs geöffnet",
"searchPlaceholder": "Snippets durchsuchen...",
"newSnippet": "Neues Snippet",
"newFolder": "Neuer Ordner",
"run": "Ausführen",
"noSnippetsInFolder": "Keine Snippets in diesem Ordner",
"uncategorized": "Nicht kategorisiert",
"editSnippetTitle": "Snippet bearbeiten",
"editSnippetDescription": "Dieses Befehls-Snippet aktualisieren",
"saveSnippetButton": "Änderungen speichern",
"createSuccess": "Snippet erfolgreich erstellt",
"createFailed": "Erstellen des Snippets fehlgeschlagen",
"updateSuccess": "Snippet erfolgreich aktualisiert",
"updateFailed": "Aktualisieren des Snippets fehlgeschlagen",
"deleteFailed": "Löschen des Snippets fehlgeschlagen",
"folderCreateSuccess": "Ordner erfolgreich erstellt",
"folderCreateFailed": "Erstellen des Ordners fehlgeschlagen",
"editFolderTitle": "Ordner bearbeiten",
"editFolderDescription": "Diesen Ordner umbenennen oder das Aussehen ändern",
"saveFolderButton": "Änderungen speichern",
"editFolder": "Ordner bearbeiten",
"deleteFolder": "Ordner löschen",
"folderDeleteSuccess": "Ordner \"{{name}}\" gelöscht",
"folderDeleteFailed": "Löschen des Ordners fehlgeschlagen",
"folderEditSuccess": "Ordner erfolgreich aktualisiert",
"folderEditFailed": "Aktualisieren des Ordners fehlgeschlagen",
"confirmRunMessage": "„{{name}}“ ausführen?",
"confirmRunButton": "Ausführen",
"runSuccess": "„{{name}}“ in {{count}} Terminal(s) ausgeführt",
"pasteSuccess": "\"{{name}}\" wurde in das/die Terminal(s) {{count}} eingefügt",
"pasteToTerminal": "Paste",
"copySuccess": "„{{name}}“ in die Zwischenablage kopiert",
"shareTitle": "Snippet teilen",
"shareUser": "Benutzer",
"shareRole": "Rolle",
"selectUser": "Benutzer auswählen...",
"selectRole": "Rolle auswählen...",
"shareSuccess": "Snippet erfolgreich geteilt",
"shareFailed": "Snippet konnte nicht geteilt werden",
"revokeSuccess": "Zugriff widerrufen",
"revokeFailed": "Zugriff konnte nicht widerrufen werden",
"currentAccess": "Aktueller Zugriff",
"shareLoadError": "Teilungsdaten konnten nicht geladen werden",
"loading": "Laden...",
"close": "Schließen",
"reorderFailed": "Reihenfolge der Snippets konnte nicht gespeichert werden",
"importExport": "Import / Export",
"exportBtn": "JSON exportieren",
"importBtn": "JSON importieren",
"exportSuccess": "Snippets erfolgreich exportiert",
"exportFailed": "Snippets konnten nicht exportiert werden",
"importTitle": "Snippets importieren",
"importDescription": "Snippets und Ordner aus einer von Termix exportierten JSON-Datei importieren",
"importDropOrClick": "JSON-Datei hier ablegen oder zum Durchsuchen klicken",
"importSelectedFile": "Ausgewählt: {{name}}",
"importOverwrite": "Bestehende Snippets mit gleichem Namen und Ordner überschreiben",
"importStartBtn": "Importieren",
"importSuccess": "Import abgeschlossen: {{snippets}} Snippet(s) hinzugefügt, {{updated}} aktualisiert, {{skipped}} übersprungen, {{folders}} Ordner hinzugefügt",
"importFailed": "Snippets konnten nicht importiert werden",
"importInvalidFile": "Ungültige Datei: Ein JSON-Objekt mit Snippets- oder Ordner-Arrays erwartet",
"targetHostsLabel": "Zielhosts",
"targetHostsHint": "Hosts zuweisen, um dieses Snippet direkt ohne offenes Terminal auszuführen.",
"noHostsAvailable": "Keine Hosts konfiguriert",
"clearTargetHosts": "Alle löschen",
"hasTargetHosts": "Hat Zielhosts",
"runOnTargets": "Auf Zielen ausführen",
"directRunSuccess": "\"{{name}}\" auf {{count}} Host(s) ausgeführt",
"directRunPartialFail": "\"{{name}}\" auf einem oder mehreren Hosts fehlgeschlagen",
"executionResultTitle": "Ausführungsergebnisse: {{name}}",
"executionResultDescription": "Ausgabe der Snippet-Ausführung auf jedem Zielhost.",
"executionSuccess": "Erfolgreich",
"executionFailed": "Fehlgeschlagen",
"expandAll": "Alle Ordner erweitern",
"collapseAll": "Alle Ordner ausblenden",
"settings": "Snippet-Einstellungen",
"variablesHint": "Variablen: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Run \"{{name}}\"",
"variablesDialogDescription": "Füllen Sie die für diesen Codeabschnitt erforderlichen Werte aus, bevor Sie ihn ausführen.",
"variablesPreviewLabel": "Vorschau",
"variablesConfirmButton": "Laufen"
},
"keybindings": {
"title": "Tastenkombinationen",
"description": "Passen Sie die Tastenkombinationen für Kopieren, Einfügen und Steuerung im Terminal an oder belegen Sie Tasten mit Befehlen zum Senden von Text oder zum Ausführen eines Code-Snippets.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klicken und drücken Sie die Tasten",
"recording": "Tasten drücken...",
"comboRequiredError": "Drücken Sie zuerst eine Tastenkombination",
"textRequiredError": "Geben Sie den zu sendenden Text ein.",
"controlCodeRequiredError": "Geben Sie einen einzelnen Buchstaben als Kontrollcode ein.",
"snippetRequiredError": "Wählen Sie einen Ausschnitt aus",
"conflictWarning": "Diese Kombination ist bereits an {{combo}}gebunden. Durch Speichern werden beide ausgelöst.",
"actionLabel": "Aktion",
"actionCopy": "Auswahl kopieren",
"actionPaste": "Paste",
"actionSendControlCode": "Sende das Signal Strg+Buchstabe",
"actionSendText": "SMS senden",
"actionRunSnippet": "Vorhandenen Codeausschnitt ausführen",
"actionNextTab": "Zum nächsten Tab wechseln",
"actionPreviousTab": "Zum vorherigen Tab wechseln",
"actionOpenCommandPalette": "Schnellsuche öffnen",
"controlCodeLabel": "Brief",
"textLabel": "Text zum Senden",
"appendEnterLabel": "Nach dem Absenden die Eingabetaste drücken",
"snippetLabel": "Ausschnitt",
"selectSnippetPlaceholder": "Wählen Sie einen Ausschnitt aus",
"orphanedSnippetWarning": "Ausschnitt nicht gefunden",
"clipboardPermissionNote": "Bei einigen Browsern kann es bei der ersten Verwendung zu einer Anfrage nach der Berechtigung zum Ablegen der Zwischenablage kommen."
"actionOpenCommandPalette": "Schnellsuche öffnen"
},
"userProfile": {
"donateTitle": "Termix unterstützen",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Topic ist erforderlich",
"ntfyToken": "Zugriffstoken (optional)",
"channelSaveFailed": "Kanal konnte nicht gespeichert werden"
},
"macros": {
"title": "Makros",
"description": "Führe eine gespeicherte Befehlsfolge in einem Terminal aus und reagiere auf die Antwort des Servers.",
"target": "Ziel",
"noTerminal": "Kein Terminal geöffnet",
"empty": "Noch keine Makros",
"emptyHint": "Erstellen Sie ein Makro, um einen Terminal-Workflow zu automatisieren, z. B. das Anmelden, das Neustarten eines Dienstes oder das Beantworten von Eingabeaufforderungen.",
"create": "Neues Makro",
"back": "Zurück zu den Makros",
"name": "Name",
"namePlaceholder": "Webserver neu starten",
"nameRequired": "Gib dem Makro vor dem Speichern einen Namen.",
"descriptionLabel": "Beschreibung",
"descriptionPlaceholder": "Was dieses Makro bewirkt (optional)",
"unsaved": "Nicht gespeicherte Änderungen",
"save": "Speichern",
"saved": "Makro gespeichert",
"saveFailed": "Makro konnte nicht gespeichert werden",
"delete": "Löschen",
"deleteTitle": "Dieses Makro löschen?",
"deleteBody": "\"{{name}}\" wird endgültig gelöscht. Dies kann nicht rückgängig gemacht werden.",
"deleted": "Makro gelöscht",
"cancel": "Stornieren",
"run": "Laufen",
"stop": "Stoppen",
"running": "Läuft...",
"completed": "Makro beendet",
"failed": "Makro fehlgeschlagen",
"loadFailed": "Makros konnten nicht geladen werden.",
"needTerminal": "Öffnen und verbinden Sie zuerst ein Terminal.",
"noSteps": "Dieses Makro hat noch keine Schritte. Füge unten einen hinzu.",
"addStep": "Schritt hinzufügen",
"stepNumber": "Schritt {{number}}",
"removeStep": "Schritt entfernen",
"confirmRunTitle": "Dieses Makro ausführen?",
"confirmRunBody": "\"{{name}}\" sendet Befehle an {{host}}.",
"confirmRunAction": "Makro ausführen",
"stepTypes": {
"send": {
"label": "Befehl senden",
"help": "Gibt den Text im Terminal ein, genau so, als ob Sie ihn eingetippt hätten."
},
"wait": {
"label": "Auf Ausgabe warten",
"help": "Die Ausgabe wird so lange unterbrochen, bis der Server den von Ihnen angegebenen Text ausgibt, beispielsweise eine Passwortabfrage."
},
"delay": {
"label": "Pause",
"help": "Wartet eine festgelegte Zeitspanne, bevor der nächste Schritt ausgeführt wird."
},
"if": {
"label": "Wenn die Ausgabe enthält",
"help": "Prüft alles, was der Server bisher ausgegeben hat, und führt dann einen von zwei Zweigen aus."
},
"repeat": {
"label": "Wiederholen",
"help": "Führt die darin enthaltenen Schritte eine festgelegte Anzahl von Malen aus."
}
},
"send": {
"text": "Befehl oder Text",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Nach dem Absenden die Eingabetaste drücken",
"pressEnterHelp": "Deaktivieren Sie diese Option, um Text einzugeben, ohne ihn abzusenden."
},
"wait": {
"pattern": "Warten Sie, bis die Ausgabe Folgendes enthält",
"patternPlaceholder": "Passwort:",
"timeout": "Aufgeben nach",
"onTimeout": "Wenn es nie erscheint",
"stop": "Stoppe das Makro",
"continue": "Mach trotzdem weiter."
},
"delay": {
"duration": "Warten Sie auf"
},
"ifStep": {
"pattern": "Wenn die Ausgabe enthält",
"patternPlaceholder": "Aktiv: aktiv (läuft)",
"then": "Dann tu Folgendes:",
"else": "Andernfalls tun Sie Folgendes:",
"emptyBranch": "Keine Stufen"
},
"repeat": {
"count": "Anzahl der Male",
"times": "mal"
},
"match": {
"useRegex": "Übereinstimmung mit einem regulären Ausdruck",
"useRegexHelp": "Lassen Sie dies weg, damit der Text genau so bleibt, wie er geschrieben steht.",
"invalidRegex": "Kein gültiger regulärer Ausdruck, wird als Klartext erkannt."
},
"seconds": "Sekunden",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Ρυθμίσεις πλευρικής γραμμής...",
"hosts": "Οικοδεσπότες",
"snippets": "Αποσπάσματα",
"macros": "Μακροεντολές",
"hostManager": "Διευθυντής Υποδοχής",
"credentials": "Διαπιστευτήρια",
"connections": "Συνδέσεις",
@@ -869,7 +868,6 @@
"groupTags": "Ετικέτες & καρφίτσα",
"groupTunnels": "Σήραγγες",
"groupJumpHosts": "Jump hosts",
"groupQuickActions": "Γρήγορες ενέργειες",
"groupFeatureFlags": "Σημαίες χαρακτηριστικών",
"groupAdvanced": "Προηγμένη διαμόρφωση",
"preview": "Πρεμιέρα",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Αποθήκευση κλειδιών SSH ή κωδικών πρόσβασης",
"tmuxMonitor": "Παρακολούθηση Tmux",
"tmuxMonitorDesc": "Παρακολουθήστε τις συνεδρίες tmux σε όλους τους κεντρικούς υπολογιστές σας",
"snippets": "Αποσπάσματα",
"runSnippetDesc": "Εκτέλεση στην ενεργή καρτέλα τερματικού",
"noSnippetsFound": "Δεν βρέθηκαν αποσπάσματα που να ταιριάζουν με το \"{{search}}\"",
"recentActivity": "Πρόσφατη δραστηριότητα",
"serversAndHosts": "Διακομιστές και κεντρικοί υπολογιστές",
"noHostsFound": "Δεν βρέθηκαν κεντρικοί υπολογιστές που να ταιριάζουν με το \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Προηγούμενη καρτέλα",
"hotkeysJumpTab": "Μετάβαση στην καρτέλα"
},
"snippets": {
"title": "Αποσπάσματα",
"createSnippetTitle": "Δημιουργία αποσπάσματος",
"createSnippetDescription": "Δημιουργήστε ένα νέο απόσπασμα εντολής για γρήγορη εκτέλεση",
"nameLabel": "Ονομα",
"namePlaceholder": "π.χ., Επανεκκίνηση Nginx",
"descriptionLabel": "Περιγραφή",
"descriptionPlaceholder": "Προαιρετική περιγραφή",
"optional": "Προαιρετικός",
"folderLabel": "Ντοσιέ",
"noFolder": "Δεν υπάρχει φάκελος (Χωρίς κατηγορία)",
"commandLabel": "Εντολή",
"commandPlaceholder": "π.χ., sudo systemctl επανεκκίνηση nginx",
"typeLabel": "Τύπος",
"typeCommand": "Εντολή",
"typeNote": "Σημείωμα",
"noteLabel": "Σημείωμα",
"notePlaceholder": "π.χ., ένα επαναχρησιμοποιήσιμο κείμενο προτροπής ή υπενθύμισης",
"cancel": "Ματαίωση",
"createSnippetButton": "Δημιουργία αποσπάσματος",
"createFolderTitle": "Δημιουργία φακέλου",
"createFolderDescription": "Οργανώστε τα αποσπάσματά σας σε φακέλους",
"folderNameLabel": "Όνομα φακέλου",
"folderNamePlaceholder": "π.χ., Εντολές συστήματος, Σενάρια Docker",
"folderColorLabel": "Χρώμα φακέλου",
"folderIconLabel": "Εικονίδιο φακέλου",
"previewLabel": "Πρεμιέρα",
"folderNameFallback": "Όνομα φακέλου",
"createFolderButton": "Δημιουργία φακέλου",
"targetTerminals": "Τερματικά Target",
"selectAll": "Ολοι",
"selectNone": "Κανένας",
"noTerminalTabsOpen": "Δεν υπάρχουν ανοιχτές καρτέλες τερματικού",
"searchPlaceholder": "Αναζήτηση αποσπασμάτων...",
"newSnippet": "Νέο απόσπασμα",
"newFolder": "Νέος φάκελος",
"run": "Τρέξιμο",
"noSnippetsInFolder": "Δεν υπάρχουν αποσπάσματα σε αυτόν τον φάκελο",
"uncategorized": "Χωρίς κατηγορία",
"editSnippetTitle": "Επεξεργασία αποσπάσματος",
"editSnippetDescription": "Ενημέρωση αυτού του αποσπάσματος εντολής",
"saveSnippetButton": "Αποθήκευση αλλαγών",
"createSuccess": "Το απόσπασμα δημιουργήθηκε με επιτυχία",
"createFailed": "Η δημιουργία αποσπάσματος απέτυχε",
"updateSuccess": "Το απόσπασμα ενημερώθηκε με επιτυχία",
"updateFailed": "Η ενημέρωση του αποσπάσματος απέτυχε",
"deleteFailed": "Η διαγραφή του αποσπάσματος απέτυχε",
"folderCreateSuccess": "Ο φάκελος δημιουργήθηκε με επιτυχία",
"folderCreateFailed": "Η δημιουργία φακέλου απέτυχε",
"editFolderTitle": "Επεξεργασία φακέλου",
"editFolderDescription": "Μετονομασία ή αλλαγή της εμφάνισης αυτού του φακέλου",
"saveFolderButton": "Αποθήκευση αλλαγών",
"editFolder": "Επεξεργασία φακέλου",
"deleteFolder": "Διαγραφή φακέλου",
"folderDeleteSuccess": "Ο φάκελος \"{{name}}\" διαγράφηκε",
"folderDeleteFailed": "Η διαγραφή του φακέλου απέτυχε",
"folderEditSuccess": "Ο φάκελος ενημερώθηκε με επιτυχία.",
"folderEditFailed": "Η ενημέρωση του φακέλου απέτυχε",
"confirmRunMessage": "Εκτέλεση \"{{name}}\";",
"confirmRunButton": "Τρέξιμο",
"runSuccess": "Εκτέλεση \"{{name}}\" σε {{count}} τερματικό(α)",
"pasteSuccess": "Επικολλήθηκε το \"{{name}}\" σε {{count}} τερματικό(α)",
"pasteToTerminal": "Πάστα",
"copySuccess": "Αντιγράφηκε το \"{{name}}\" στο πρόχειρο",
"shareTitle": "Κοινή χρήση αποσπάσματος",
"shareUser": "Μεταχειριζόμενος",
"shareRole": "Ρόλος",
"selectUser": "Επιλέξτε έναν χρήστη...",
"selectRole": "Επιλέξτε έναν ρόλο...",
"shareSuccess": "Το απόσπασμα κοινοποιήθηκε με επιτυχία",
"shareFailed": "Η κοινοποίηση του αποσπάσματος απέτυχε",
"revokeSuccess": "Η πρόσβαση ανακλήθηκε",
"revokeFailed": "Η ανάκληση της πρόσβασης απέτυχε",
"currentAccess": "Τρέχουσα πρόσβαση",
"shareLoadError": "Η φόρτωση των δεδομένων κοινής χρήσης απέτυχε",
"loading": "Φόρτωση...",
"close": "Κοντά",
"reorderFailed": "Η αποθήκευση της σειράς αποσπάσματος απέτυχε",
"importExport": "Εισαγωγή / Εξαγωγή",
"exportBtn": "Εξαγωγή JSON",
"importBtn": "Εισαγωγή JSON",
"exportSuccess": "Τα αποσπάσματα εξήχθησαν με επιτυχία",
"exportFailed": "Η εξαγωγή αποσπασμάτων απέτυχε",
"importTitle": "Εισαγωγή αποσπασμάτων",
"importDescription": "Εισαγωγή αποσπασμάτων και φακέλων από ένα αρχείο JSON που εξάγεται από το Termix",
"importDropOrClick": "Αποθέστε ένα αρχείο JSON εδώ ή κάντε κλικ για περιήγηση",
"importSelectedFile": "Επιλεγμένα: {{name}}",
"importOverwrite": "Αντικατάσταση υπαρχόντων αποσπασμάτων με το ίδιο όνομα και φάκελο",
"importStartBtn": "Εισαγωγή",
"importSuccess": "Η εισαγωγή ολοκληρώθηκε: Προστέθηκαν {{snippets}} αποσπάσματα, ενημερώθηκε {{updated}} , παραλείφθηκε {{skipped}} , προστέθηκαν {{folders}} φάκελοι",
"importFailed": "Η εισαγωγή αποσπασμάτων απέτυχε",
"importInvalidFile": "Μη έγκυρο αρχείο: αναμενόταν ένα αντικείμενο JSON με τμήματα κώδικα ή πίνακες φακέλων",
"targetHostsLabel": "Στοχευμένοι οικοδεσπότες",
"targetHostsHint": "Αναθέστε σε κεντρικούς υπολογιστές να εκτελούν αυτό το τμήμα κώδικα απευθείας χωρίς ανοιχτό τερματικό.",
"noHostsAvailable": "Δεν έχουν ρυθμιστεί κεντρικοί υπολογιστές",
"clearTargetHosts": "Διαγραφή όλων",
"hasTargetHosts": "Έχει κεντρικούς υπολογιστές-στόχους",
"runOnTargets": "Τρέξιμο σε στόχους",
"directRunSuccess": "Εκτέλεση \"{{name}}\" σε {{count}} κεντρικό υπολογιστή (ή κεντρικούς υπολογιστές)",
"directRunPartialFail": "Το \"{{name}}\" απέτυχε σε έναν ή περισσότερους κεντρικούς υπολογιστές",
"executionResultTitle": "Αποτελέσματα εκτέλεσης: {{name}}",
"executionResultDescription": "Έξοδος από την εκτέλεση του τμήματος κώδικα σε κάθε κεντρικό υπολογιστή προορισμού.",
"executionSuccess": "Επιτυχία",
"executionFailed": "Αποτυχημένος",
"expandAll": "Ανάπτυξη όλων των φακέλων",
"collapseAll": "Σύμπτυξη όλων των φακέλων",
"settings": "Ρυθμίσεις αποσπάσματος",
"variablesHint": "Μεταβλητές: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Εκτέλεση \"{{name}}\"",
"variablesDialogDescription": "Συμπληρώστε τις τιμές που χρειάζεται αυτό το τμήμα κώδικα πριν το εκτελέσετε.",
"variablesPreviewLabel": "Πρεμιέρα",
"variablesConfirmButton": "Τρέξιμο"
},
"keybindings": {
"title": "Συντομεύσεις πληκτρολογίου",
"description": "Προσαρμόστε τις συντομεύσεις αντιγραφής, επικόλλησης και ελέγχου τερματικού ή συνδέστε τα πλήκτρα για αποστολή κειμένου ή εκτέλεση ενός αποσπάσματος.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Κάντε κλικ και πατήστε τα πλήκτρα",
"recording": "Πατήστε τα πλήκτρα...",
"comboRequiredError": "Πατήστε πρώτα έναν συνδυασμό πλήκτρων",
"textRequiredError": "Εισαγάγετε το κείμενο που θέλετε να στείλετε",
"controlCodeRequiredError": "Εισαγάγετε ένα μόνο γράμμα για τον κωδικό ελέγχου",
"snippetRequiredError": "Επιλέξτε ένα απόσπασμα",
"conflictWarning": "Αυτός ο συνδυασμός είναι ήδη συνδεδεμένος με {{combo}}. Η αποθήκευση θα ενεργοποιήσει και τα δύο.",
"actionLabel": "Δράση",
"actionCopy": "Αντιγραφή επιλογής",
"actionPaste": "Πάστα",
"actionSendControlCode": "Αποστολή σήματος Ctrl+γράμμα",
"actionSendText": "Αποστολή κειμένου",
"actionRunSnippet": "Εκτέλεση υπάρχοντος αποσπάσματος",
"actionNextTab": "Μετάβαση στην επόμενη καρτέλα",
"actionPreviousTab": "Μετάβαση στην προηγούμενη καρτέλα",
"actionOpenCommandPalette": "Άνοιγμα γρήγορης αναζήτησης",
"controlCodeLabel": "Επιστολή",
"textLabel": "Μήνυμα προς αποστολή",
"appendEnterLabel": "Πατήστε Enter μετά την αποστολή",
"snippetLabel": "Απόσπασμα",
"selectSnippetPlaceholder": "Επιλέξτε ένα απόσπασμα",
"orphanedSnippetWarning": "το απόσπασμα δεν βρέθηκε",
"clipboardPermissionNote": "Ενδέχεται να ζητηθεί άδεια για το πρόχειρο κατά την πρώτη χρήση σε ορισμένα προγράμματα περιήγησης."
"actionOpenCommandPalette": "Άνοιγμα γρήγορης αναζήτησης"
},
"userProfile": {
"donateTitle": "Κρατήστε το Termix ζωντανό",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Το θέμα είναι υποχρεωτικό",
"ntfyToken": "Διακριτικό πρόσβασης (προαιρετικό)",
"channelSaveFailed": "Η αποθήκευση του καναλιού απέτυχε"
},
"macros": {
"title": "Μακροεντολές",
"description": "Εκτελέστε μια αποθηκευμένη σειρά εντολών σε ένα τερματικό και αντιδράστε σε ό,τι στέλνει πίσω ο διακομιστής.",
"target": "Στόχος",
"noTerminal": "Δεν υπάρχει ανοιχτό τερματικό",
"empty": "Δεν υπάρχουν ακόμη μακροεντολές",
"emptyHint": "Δημιουργήστε μια μακροεντολή για να αυτοματοποιήσετε μια ροή τερματικού, όπως σύνδεση, επανεκκίνηση μιας υπηρεσίας ή απάντηση σε μηνύματα.",
"create": "Νέα μακροεντολή",
"back": "Επιστροφή στις μακροεντολές",
"name": "Ονομα",
"namePlaceholder": "Επανεκκίνηση διακομιστή ιστού",
"nameRequired": "Δώστε ένα όνομα στη μακροεντολή πριν από την αποθήκευση",
"descriptionLabel": "Περιγραφή",
"descriptionPlaceholder": "Τι κάνει αυτή η μακροεντολή (προαιρετικό)",
"unsaved": "Μη αποθηκευμένες αλλαγές",
"save": "Εκτός",
"saved": "Η μακροεντολή αποθηκεύτηκε",
"saveFailed": "Δεν ήταν δυνατή η αποθήκευση της μακροεντολής",
"delete": "Διαγράφω",
"deleteTitle": "Διαγραφή αυτής της μακροεντολής;",
"deleteBody": "Το \"{{name}}\" θα διαγραφεί οριστικά. Δεν είναι δυνατή η αναίρεση αυτής της ενέργειας.",
"deleted": "Η μακροεντολή διαγράφηκε",
"cancel": "Ματαίωση",
"run": "Τρέξιμο",
"stop": "Στάση",
"running": "Τρέξιμο...",
"completed": "Η μακροεντολή ολοκληρώθηκε",
"failed": "Η μακροεντολή απέτυχε.",
"loadFailed": "Δεν ήταν δυνατή η φόρτωση μακροεντολών",
"needTerminal": "Ανοίξτε και συνδέστε πρώτα ένα τερματικό",
"noSteps": "Αυτή η μακροεντολή δεν έχει ακόμη βήματα. Προσθέστε ένα παρακάτω.",
"addStep": "Προσθήκη βήματος",
"stepNumber": "Βήμα {{number}}",
"removeStep": "Αφαίρεση βήματος",
"confirmRunTitle": "Εκτέλεση αυτής της μακροεντολής;",
"confirmRunBody": "Η εντολή \"{{name}}\" θα στείλει εντολές στον χρήστη {{host}}.",
"confirmRunAction": "Εκτέλεση μακροεντολής",
"stepTypes": {
"send": {
"label": "Αποστολή εντολής",
"help": "Πληκτρολογεί κείμενο στο τερματικό, όπως θα το γράφατε."
},
"wait": {
"label": "Περιμένετε για έξοδο",
"help": "Παύει μέχρι ο διακομιστής να εκτυπώσει το κείμενο που ονομάζετε, σαν να σας ζητείται κωδικός πρόσβασης."
},
"delay": {
"label": "Παύση",
"help": "Περιμένει ένα συγκεκριμένο χρονικό διάστημα πριν από το επόμενο βήμα."
},
"if": {
"label": "Εάν η έξοδος περιέχει",
"help": "Ελέγχει όλα όσα έχει εκτυπώσει μέχρι στιγμής ο διακομιστής και, στη συνέχεια, εκτελεί μία από τις δύο διακλαδώσεις."
},
"repeat": {
"label": "Επαναλαμβάνω",
"help": "Εκτελεί τα βήματα στο εσωτερικό του έναν καθορισμένο αριθμό φορών."
}
},
"send": {
"text": "Εντολή ή κείμενο",
"textPlaceholder": "επανεκκίνηση systemctl nginx",
"pressEnter": "Πατήστε Enter μετά την αποστολή",
"pressEnterHelp": "Απενεργοποιήστε αυτήν τη λειτουργία για να πληκτρολογείτε κείμενο χωρίς να το υποβάλλετε."
},
"wait": {
"pattern": "Περιμένετε μέχρι η έξοδος να περιέχει",
"patternPlaceholder": "σύνθημα:",
"timeout": "Παραιτήσου μετά",
"onTimeout": "Αν δεν εμφανιστεί ποτέ",
"stop": "Διακοπή της μακροεντολής",
"continue": "Συνέχισε ούτως ή άλλως"
},
"delay": {
"duration": "Περιμένω για"
},
"ifStep": {
"pattern": "Εάν η έξοδος περιέχει",
"patternPlaceholder": "Ενεργό: ενεργό (σε λειτουργία)",
"then": "Στη συνέχεια, κάντε αυτό",
"else": "Διαφορετικά, κάντε αυτό",
"emptyBranch": "Χωρίς σκαλοπάτια"
},
"repeat": {
"count": "Αριθμός φορών",
"times": "φορές"
},
"match": {
"useRegex": "Ταίριασμα με μια κανονική έκφραση",
"useRegexHelp": "Αφήστε το απενεργοποιημένο για να ταιριάζει ακριβώς με το κείμενο όπως είναι γραμμένο.",
"invalidRegex": "Δεν είναι έγκυρη κανονική έκφραση, θα αντιστοιχιστεί ως απλό κείμενο"
},
"seconds": "δευτερόλεπτα",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Configuración de la barra lateral...",
"hosts": "Hosts",
"snippets": "Fragmentos",
"macros": "Macros",
"hostManager": "Gestor de hosts",
"credentials": "Credenciales",
"connections": "Conexiones",
@@ -869,7 +868,6 @@
"groupTags": "Etiquetas y pines",
"groupTunnels": "Túneles",
"groupJumpHosts": "Anfitriones de salto",
"groupQuickActions": "Acciones rápidas",
"groupFeatureFlags": "Banderas de características",
"groupAdvanced": "Configuración avanzada",
"preview": "Avance",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Almacenar claves SSH o contraseñas",
"tmuxMonitor": "Monitor de Tmux",
"tmuxMonitorDesc": "Supervisar sesiones de tmux en sus hosts",
"snippets": "Fragmentos",
"runSnippetDesc": "Ejecutar en la pestaña de terminal activa",
"noSnippetsFound": "No se encontraron fragmentos que coincidieran con \"{{search}}\"",
"recentActivity": "Actividad reciente",
"serversAndHosts": "Servidores y hosts",
"noHostsFound": "No se encontraron hosts que coincidan con \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Pestaña anterior",
"hotkeysJumpTab": "Ir a la pestaña"
},
"snippets": {
"title": "Fragmentos",
"createSnippetTitle": "Crear fragmento",
"createSnippetDescription": "Crear un nuevo fragmento de comando para ejecución rápida",
"nameLabel": "Nombre",
"namePlaceholder": "p. ej., Reiniciar Nginx",
"descriptionLabel": "Descripción",
"descriptionPlaceholder": "Descripción opcional",
"optional": "Opcional",
"folderLabel": "Carpeta",
"noFolder": "Sin carpeta (Sin categoría)",
"commandLabel": "Comando",
"commandPlaceholder": "p. ej., sudo systemctl restart nginx",
"typeLabel": "Tipo",
"typeCommand": "Dominio",
"typeNote": "Nota",
"noteLabel": "Nota",
"notePlaceholder": "Por ejemplo, un texto de recordatorio o aviso reutilizable.",
"cancel": "Cancelar",
"createSnippetButton": "Crear fragmento",
"createFolderTitle": "Crear carpeta",
"createFolderDescription": "Organiza tus fragmentos en carpetas",
"folderNameLabel": "Nombre de la carpeta",
"folderNamePlaceholder": "p. ej., Comandos del sistema, Scripts Docker",
"folderColorLabel": "Color de la carpeta",
"folderIconLabel": "Icono de la carpeta",
"previewLabel": "Vista previa",
"folderNameFallback": "Nombre de la carpeta",
"createFolderButton": "Crear carpeta",
"targetTerminals": "Terminales de destino",
"selectAll": "Todos",
"selectNone": "Ninguno",
"noTerminalTabsOpen": "No hay pestañas de terminal abiertas",
"searchPlaceholder": "Buscar fragmentos...",
"newSnippet": "Nuevo fragmento",
"newFolder": "Nueva carpeta",
"run": "Ejecutar",
"noSnippetsInFolder": "No hay fragmentos en esta carpeta",
"uncategorized": "Sin categorizar",
"editSnippetTitle": "Editar fragmento",
"editSnippetDescription": "Actualizar este fragmento de comando",
"saveSnippetButton": "Guardar cambios",
"createSuccess": "Fragmento creado correctamente",
"createFailed": "No se pudo crear el fragmento",
"updateSuccess": "Fragmento actualizado correctamente",
"updateFailed": "No se pudo actualizar el fragmento",
"deleteFailed": "No se pudo eliminar el fragmento",
"folderCreateSuccess": "Carpeta creada correctamente",
"folderCreateFailed": "No se pudo crear la carpeta",
"editFolderTitle": "Editar carpeta",
"editFolderDescription": "Renombrar o cambiar la apariencia de esta carpeta",
"saveFolderButton": "Guardar cambios",
"editFolder": "Editar carpeta",
"deleteFolder": "Eliminar carpeta",
"folderDeleteSuccess": "Carpeta \"{{name}}\" eliminada",
"folderDeleteFailed": "No se pudo eliminar la carpeta",
"folderEditSuccess": "Carpeta actualizada correctamente",
"folderEditFailed": "No se pudo actualizar la carpeta",
"confirmRunMessage": "¿Ejecutar \"{{name}}\"?",
"confirmRunButton": "Ejecutar",
"runSuccess": "Se ejecutó \"{{name}}\" en {{count}} terminal(es)",
"pasteSuccess": "Se pegó \"{{name}}\" en {{count}} terminal(es)",
"pasteToTerminal": "Pasta",
"copySuccess": "\"{{name}}\" copiado al portapapeles",
"shareTitle": "Compartir fragmento",
"shareUser": "Usuario",
"shareRole": "Rol",
"selectUser": "Seleccionar un usuario...",
"selectRole": "Seleccionar un rol...",
"shareSuccess": "Fragmento compartido correctamente",
"shareFailed": "Error al compartir el fragmento",
"revokeSuccess": "Acceso revocado",
"revokeFailed": "Error al revocar el acceso",
"currentAccess": "Acceso actual",
"shareLoadError": "Error al cargar los datos de compartición",
"loading": "Cargando...",
"close": "Cerrar",
"reorderFailed": "Error al guardar el orden de los fragmentos",
"importExport": "Importar / Exportar",
"exportBtn": "Exportar JSON",
"importBtn": "Importar JSON",
"exportSuccess": "Fragmentos exportados correctamente",
"exportFailed": "Error al exportar los fragmentos",
"importTitle": "Importar fragmentos",
"importDescription": "Importar fragmentos y carpetas desde un archivo JSON exportado por Termix",
"importDropOrClick": "Suelta un archivo JSON aquí o haz clic para explorar",
"importSelectedFile": "Seleccionado: {{name}}",
"importOverwrite": "Sobrescribir fragmentos existentes con el mismo nombre y carpeta",
"importStartBtn": "Importar",
"importSuccess": "Importación completa: {{snippets}} fragmento(s) añadido(s), {{updated}} actualizado(s), {{skipped}} omitido(s), {{folders}} carpeta(s) añadida(s)",
"importFailed": "Error al importar los fragmentos",
"importInvalidFile": "Archivo no válido: se esperaba un objeto JSON con arrays de fragmentos o carpetas",
"targetHostsLabel": "Hosts de destino",
"targetHostsHint": "Asignar hosts para ejecutar este fragmento directamente sin una terminal abierta.",
"noHostsAvailable": "No hay hosts configurados",
"clearTargetHosts": "Limpiar todo",
"hasTargetHosts": "Tiene hosts de destino",
"runOnTargets": "Ejecutar en destinos",
"directRunSuccess": "Se ejecutó \"{{name}}\" en {{count}} host(s)",
"directRunPartialFail": "\"{{name}}\" falló en uno o más hosts",
"executionResultTitle": "Resultados de ejecución: {{name}}",
"executionResultDescription": "Salida de la ejecución del snippet en cada host de destino.",
"executionSuccess": "Éxito",
"executionFailed": "Fallido",
"expandAll": "Expandir todas las carpetas",
"collapseAll": "Contraer todas las carpetas",
"settings": "Configuración del fragmento",
"variablesHint": "Variables: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Ejecutar \"{{name}}\"",
"variablesDialogDescription": "Rellena los valores que necesita este fragmento de código antes de ejecutarlo.",
"variablesPreviewLabel": "Avance",
"variablesConfirmButton": "Correr"
},
"keybindings": {
"title": "Atajos de teclado",
"description": "Personaliza los atajos de teclado para copiar, pegar y controlar funciones del terminal, o asigna teclas para enviar texto o ejecutar un fragmento de código.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Haz clic y pulsa las teclas",
"recording": "Pulsa las teclas...",
"comboRequiredError": "Primero, pulse una combinación de teclas.",
"textRequiredError": "Introduce el texto que deseas enviar",
"controlCodeRequiredError": "Introduzca una sola letra para el código de control.",
"snippetRequiredError": "Seleccione un fragmento",
"conflictWarning": "Esta combinación ya está vinculada a {{combo}}. Guardar hará que ambos se activen.",
"actionLabel": "Acción",
"actionCopy": "Copiar selección",
"actionPaste": "Pasta",
"actionSendControlCode": "Enviar señal Ctrl+letra",
"actionSendText": "Enviar mensaje de texto",
"actionRunSnippet": "Ejecutar el fragmento existente",
"actionNextTab": "Cambiar a la siguiente pestaña",
"actionPreviousTab": "Volver a la pestaña anterior",
"actionOpenCommandPalette": "Abrir búsqueda rápida",
"controlCodeLabel": "Carta",
"textLabel": "Texto para enviar",
"appendEnterLabel": "Presione Enter después de enviar",
"snippetLabel": "Retazo",
"selectSnippetPlaceholder": "Seleccione un fragmento",
"orphanedSnippetWarning": "Fragmento no encontrado",
"clipboardPermissionNote": "En algunos navegadores, puede solicitar permiso para copiar y pegar el contenido del portapapeles la primera vez que se utilice."
"actionOpenCommandPalette": "Abrir búsqueda rápida"
},
"userProfile": {
"donateTitle": "Mantener Termix vivo",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "El tema es obligatorio",
"ntfyToken": "Token de acceso (opcional)",
"channelSaveFailed": "No se pudo guardar el canal"
},
"macros": {
"title": "Macros",
"description": "Ejecuta una serie de comandos guardados en una terminal y reacciona a la respuesta que envíe el servidor.",
"target": "Objetivo",
"noTerminal": "No hay terminal abierta",
"empty": "Aún no hay macros",
"emptyHint": "Crea una macro para automatizar un flujo de terminal, como iniciar sesión, reiniciar un servicio o responder a preguntas.",
"create": "Nueva macro",
"back": "Volver a las macros",
"name": "Nombre",
"namePlaceholder": "Reiniciar el servidor web",
"nameRequired": "Dale un nombre a la macro antes de guardarla.",
"descriptionLabel": "Descripción",
"descriptionPlaceholder": "Qué hace esta macro (opcional)",
"unsaved": "Cambios no guardados",
"save": "Ahorrar",
"saved": "Macro guardada",
"saveFailed": "No se pudo guardar la macro",
"delete": "Borrar",
"deleteTitle": "¿Eliminar esta macro?",
"deleteBody": "\"{{name}}\" se eliminará permanentemente. Esto no se puede deshacer.",
"deleted": "Macro eliminada",
"cancel": "Cancelar",
"run": "Correr",
"stop": "Detener",
"running": "Correr...",
"completed": "Macro terminado",
"failed": "La macro falló",
"loadFailed": "No se pudieron cargar las macros",
"needTerminal": "Primero, abre y conecta una terminal.",
"noSteps": "Esta macro aún no tiene pasos. Añada uno a continuación.",
"addStep": "Agregar paso",
"stepNumber": "Paso {{number}}",
"removeStep": "Eliminar paso",
"confirmRunTitle": "¿Ejecutar esta macro?",
"confirmRunBody": "\"{{name}}\" enviará comandos a {{host}}.",
"confirmRunAction": "Ejecutar macro",
"stepTypes": {
"send": {
"label": "Enviar comando",
"help": "Escribe el texto en la terminal, igual que si lo hubieras escrito tú."
},
"wait": {
"label": "Espere la salida",
"help": "Hace una pausa hasta que el servidor imprime el texto que usted especifique, como si fuera una solicitud de contraseña."
},
"delay": {
"label": "Pausa",
"help": "Espera un tiempo fijo antes de pasar al siguiente paso."
},
"if": {
"label": "Si la salida contiene",
"help": "Comprueba todo lo que el servidor ha impreso hasta el momento y, a continuación, ejecuta una de las dos ramas."
},
"repeat": {
"label": "Repetir",
"help": "Reproduce los pasos que contiene un número determinado de veces."
}
},
"send": {
"text": "Comando o texto",
"textPlaceholder": "systemctl reiniciar nginx",
"pressEnter": "Presione Enter después de enviar",
"pressEnterHelp": "Desactive esta opción para escribir texto sin enviarlo."
},
"wait": {
"pattern": "Espere hasta que la salida contenga",
"patternPlaceholder": "contraseña:",
"timeout": "Rendirse después",
"onTimeout": "Si nunca aparece",
"stop": "Detén la macro",
"continue": "Sigue adelante de todos modos."
},
"delay": {
"duration": "Esperar"
},
"ifStep": {
"pattern": "Si la salida contiene",
"patternPlaceholder": "Activo: activo (corriendo)",
"then": "Entonces haz esto",
"else": "De lo contrario, haz esto",
"emptyBranch": "Sin escalones"
},
"repeat": {
"count": "Número de veces",
"times": "veces"
},
"match": {
"useRegex": "Coincidencia con una expresión regular",
"useRegexHelp": "Omita esta opción para que coincida exactamente con el texto escrito.",
"invalidRegex": "No es una expresión regular válida, se interpretará como texto plano."
},
"seconds": "artículos de segunda clase",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Sivupalkin asetukset...",
"hosts": "Isännät",
"snippets": "Katkelmat",
"macros": "Makrot",
"hostManager": "Isäntäpäällikkö",
"credentials": "Valtakirjat",
"connections": "Yhteydet",
@@ -869,7 +868,6 @@
"groupTags": "Tunnisteet ja kiinnitys",
"groupTunnels": "Tunnelit",
"groupJumpHosts": "Jump-isännät",
"groupQuickActions": "Pikatoiminnot",
"groupFeatureFlags": "Ominaisuusliput",
"groupAdvanced": "Lisäasetukset",
"preview": "Esikatselu",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH-avainten tai salasanojen tallentaminen",
"tmuxMonitor": "Tmux-näyttö",
"tmuxMonitorDesc": "Valvo tmux-istuntoja eri isännilläsi",
"snippets": "Katkelmat",
"runSnippetDesc": "Suorita aktiivisessa päätevälilehdessä",
"noSnippetsFound": "Ei löytynyt hakutuloksia, jotka vastaavat \"{{search}}\"",
"recentActivity": "Viimeaikaiset toiminnot",
"serversAndHosts": "Palvelimet ja isännät",
"noHostsFound": "Ei löytynyt isäntiä, jotka vastaavat \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Edellinen välilehti",
"hotkeysJumpTab": "Siirry välilehdelle"
},
"snippets": {
"title": "Katkelmat",
"createSnippetTitle": "Luo katkelma",
"createSnippetDescription": "Luo uusi komentokehotteen nopeaa suorittamista varten",
"nameLabel": "Nimi",
"namePlaceholder": "esim. Käynnistä Nginx uudelleen",
"descriptionLabel": "Kuvaus",
"descriptionPlaceholder": "Valinnainen kuvaus",
"optional": "Valinnainen",
"folderLabel": "Kansio",
"noFolder": "Ei kansiota (Luokittelematon)",
"commandLabel": "Komento",
"commandPlaceholder": "esim. sudo systemctl käynnistä nginx uudelleen",
"typeLabel": "Tyyppi",
"typeCommand": "Komento",
"typeNote": "Huomautus",
"noteLabel": "Huomautus",
"notePlaceholder": "esim. uudelleenkäytettävä kehote tai muistutusteksti",
"cancel": "Peruuttaa",
"createSnippetButton": "Luo katkelma",
"createFolderTitle": "Luo kansio",
"createFolderDescription": "Järjestä katkelmasi kansioihin",
"folderNameLabel": "Kansion nimi",
"folderNamePlaceholder": "esim. järjestelmäkomennot, Docker-skriptit",
"folderColorLabel": "Kansion väri",
"folderIconLabel": "Kansiokuvake",
"previewLabel": "Esikatselu",
"folderNameFallback": "Kansion nimi",
"createFolderButton": "Luo kansio",
"targetTerminals": "Kohdepäätteet",
"selectAll": "Kaikki",
"selectNone": "Ei mitään",
"noTerminalTabsOpen": "Ei auki olevia päätevälilehtiä",
"searchPlaceholder": "Haku katkelmia...",
"newSnippet": "Uusi katkelma",
"newFolder": "Uusi kansio",
"run": "Juokse",
"noSnippetsInFolder": "Tässä kansiossa ei ole katkelmia",
"uncategorized": "Luokittelematon",
"editSnippetTitle": "Muokkaa katkelmaa",
"editSnippetDescription": "Päivitä tämä komentokehotteen koodi",
"saveSnippetButton": "Tallenna muutokset",
"createSuccess": "Katkelma luotu onnistuneesti",
"createFailed": "Katkelman luominen epäonnistui",
"updateSuccess": "Katkelman päivitys onnistui",
"updateFailed": "Katkelman päivittäminen epäonnistui",
"deleteFailed": "Katkelman poistaminen epäonnistui",
"folderCreateSuccess": "Kansio luotu onnistuneesti",
"folderCreateFailed": "Kansion luominen epäonnistui",
"editFolderTitle": "Muokkaa kansiota",
"editFolderDescription": "Nimeä tämä kansio uudelleen tai muuta sen ulkoasua",
"saveFolderButton": "Tallenna muutokset",
"editFolder": "Muokkaa kansiota",
"deleteFolder": "Poista kansio",
"folderDeleteSuccess": "Kansio \"{{name}}\" poistettu",
"folderDeleteFailed": "Kansion poistaminen epäonnistui",
"folderEditSuccess": "Kansio päivitetty onnistuneesti",
"folderEditFailed": "Kansion päivittäminen epäonnistui",
"confirmRunMessage": "Suorittaa \"{{name}}\"?",
"confirmRunButton": "Juokse",
"runSuccess": "Suoritti komennon \"{{name}}\" {{count}} päätteessä/päätteissä",
"pasteSuccess": "Liitti \"{{name}}\" {{count}} terminaaliin/terminaaleihin",
"pasteToTerminal": "Liitä",
"copySuccess": "Kopioitu \"{{name}}\" leikepöydälle",
"shareTitle": "Jaa katkelma",
"shareUser": "Käyttäjä",
"shareRole": "Rooli",
"selectUser": "Valitse käyttäjä...",
"selectRole": "Valitse rooli...",
"shareSuccess": "Katkelman jakaminen onnistui",
"shareFailed": "Katkelman jakaminen epäonnistui",
"revokeSuccess": "Pääsy peruutettu",
"revokeFailed": "Käyttöoikeuden peruuttaminen epäonnistui",
"currentAccess": "Nykyinen käyttöoikeus",
"shareLoadError": "Jaettujen tietojen lataaminen epäonnistui",
"loading": "Ladataan...",
"close": "Lähellä",
"reorderFailed": "Katkelmien järjestyksen tallentaminen epäonnistui",
"importExport": "Tuonti / vienti",
"exportBtn": "Vie JSON",
"importBtn": "Tuo JSON",
"exportSuccess": "Katkelmien vienti onnistui",
"exportFailed": "Katkelmien vienti epäonnistui",
"importTitle": "Tuo koodikatkelmia",
"importDescription": "Tuo koodikatkelmia ja kansioita Termixin viemästä JSON-tiedostosta",
"importDropOrClick": "Pudota JSON-tiedosto tähän tai napsauta selataksesi",
"importSelectedFile": "Valittu: {{name}}",
"importOverwrite": "Korvaa olemassa olevat koodinpätkät samalla nimellä ja kansiolla",
"importStartBtn": "Tuoda",
"importSuccess": "Tuonti valmis: {{snippets}} katkelma(a) lisätty, {{updated}} päivitetty, {{skipped}} ohitettu, {{folders}} kansio(a) lisätty",
"importFailed": "Katkelmien tuonti epäonnistui",
"importInvalidFile": "Virheellinen tiedosto: odotettiin JSON-objektia, jossa on koodikatkelmia tai kansiotaulukoita",
"targetHostsLabel": "Kohdepalvelimet",
"targetHostsHint": "Määritä isännät suorittamaan tämä koodinpätkä suoraan ilman avointa päätettä.",
"noHostsAvailable": "Ei määritettyjä isäntiä",
"clearTargetHosts": "Tyhjennä kaikki",
"hasTargetHosts": "Sisältää kohdeisäntiä",
"runOnTargets": "Juokse tavoitteilla",
"directRunSuccess": "Suoritti komennon \"{{name}}\" {{count}} isäntäkoneella/isännillä",
"directRunPartialFail": "\"{{name}}\" epäonnistui yhdellä tai useammalla isännällä",
"executionResultTitle": "Suorituksen tulokset: {{name}}",
"executionResultDescription": "Tuloste koodinpätkän suorittamisesta kullakin kohdeisännöllä.",
"executionSuccess": "Menestys",
"executionFailed": "Epäonnistui",
"expandAll": "Laajenna kaikki kansiot",
"collapseAll": "Pienennä kaikki kansiot",
"settings": "Katkelman asetukset",
"variablesHint": "Muuttujat: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Suorita \"{{name}}\"",
"variablesDialogDescription": "Täytä tämän koodinpätkän tarvitsemat arvot ennen sen suorittamista.",
"variablesPreviewLabel": "Esikatselu",
"variablesConfirmButton": "Juokse"
},
"keybindings": {
"title": "Pikanäppäimet",
"description": "Mukauta päätelaitteen kopiointi-, liittämis- ja ohjauspikakuvakkeita tai sido näppäimiä tekstin lähettämiseen tai koodinpätkän suorittamiseen.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Napsauta ja paina näppäimiä",
"recording": "Paina näppäimiä...",
"comboRequiredError": "Paina ensin näppäinyhdistelmää",
"textRequiredError": "Kirjoita lähetettävä teksti",
"controlCodeRequiredError": "Syötä yksi kirjain ohjauskoodille",
"snippetRequiredError": "Valitse katkelma",
"conflictWarning": "Tämä yhdistelmä on jo sidottu kohtaan {{combo}}. Tallentaminen käynnistää molemmat.",
"actionLabel": "Toiminta",
"actionCopy": "Kopioi valinta",
"actionPaste": "Liitä",
"actionSendControlCode": "Lähetä Ctrl+kirjain-signaali",
"actionSendText": "Lähetä tekstiviesti",
"actionRunSnippet": "Suorita olemassa oleva koodinpätkä",
"actionNextTab": "Vaihda seuraavaan välilehteen",
"actionPreviousTab": "Vaihda edelliseen välilehteen",
"actionOpenCommandPalette": "Avaa pikahaku",
"controlCodeLabel": "Kirje",
"textLabel": "Lähetettävä tekstiviesti",
"appendEnterLabel": "Paina Enter lähetyksen jälkeen",
"snippetLabel": "Katkelma",
"selectSnippetPlaceholder": "Valitse katkelma",
"orphanedSnippetWarning": "katkelmaa ei löytynyt",
"clipboardPermissionNote": "Saattaa pyytää leikepöydän käyttöoikeutta ensimmäisellä käyttökerralla joissakin selaimissa."
"actionOpenCommandPalette": "Avaa pikahaku"
},
"userProfile": {
"donateTitle": "Pidä Termix hengissä",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Aihe on pakollinen",
"ntfyToken": "Käyttöoikeustunnus (valinnainen)",
"channelSaveFailed": "Kanavan tallentaminen epäonnistui"
},
"macros": {
"title": "Makrot",
"description": "Suorita tallennettu komentosarja terminaalissa ja reagoi palvelimen lähettämiin komentoihin.",
"target": "Kohde",
"noTerminal": "Ei auki olevaa terminaalia",
"empty": "Ei vielä makroja",
"emptyHint": "Luo makro automatisoidaksesi päätetyönkulun, kuten sisäänkirjautumisen, palvelun uudelleenkäynnistyksen tai kehotteisiin vastaamisen.",
"create": "Uusi makro",
"back": "Takaisin makroihin",
"name": "Nimi",
"namePlaceholder": "Käynnistä web-palvelin uudelleen",
"nameRequired": "Anna makrolle nimi ennen tallentamista",
"descriptionLabel": "Kuvaus",
"descriptionPlaceholder": "Mitä tämä makro tekee (valinnainen)",
"unsaved": "Tallentamattomat muutokset",
"save": "Tallentaa",
"saved": "Makro tallennettu",
"saveFailed": "Makroa ei voitu tallentaa",
"delete": "Poistaa",
"deleteTitle": "Poistetaanko tämä makro?",
"deleteBody": "\"{{name}}\" poistetaan pysyvästi. Tätä ei voi perua.",
"deleted": "Makro poistettu",
"cancel": "Peruuttaa",
"run": "Juokse",
"stop": "Stop",
"running": "Juoksemassa...",
"completed": "Makro valmis",
"failed": "Makro epäonnistui",
"loadFailed": "Makrojen lataaminen epäonnistui",
"needTerminal": "Avaa ja kytke ensin päätelaite",
"noSteps": "Tällä makrolla ei ole vielä vaiheita. Lisää yksi alle.",
"addStep": "Lisää vaihe",
"stepNumber": "Vaihe {{number}}",
"removeStep": "Poista vaihe",
"confirmRunTitle": "Suoritetaanko tämä makro?",
"confirmRunBody": "\"{{name}}\" lähettää komentoja {{host}}:een.",
"confirmRunAction": "Suorita makro",
"stepTypes": {
"send": {
"label": "Lähetä komento",
"help": "Kirjoittaa tekstiä terminaaliin aivan kuin olisit kirjoittanut sen."
},
"wait": {
"label": "Odota tulostetta",
"help": "Pysähtyy, kunnes palvelin tulostaa nimeämäsi tekstin, kuten salasanakehotteen."
},
"delay": {
"label": "Tauko",
"help": "Odottaa tietyn ajan ennen seuraavaa vaihetta."
},
"if": {
"label": "Jos tuloste sisältää",
"help": "Tarkistaa kaiken, mitä palvelin on tähän mennessä tulostanut, ja suorittaa sitten toisen kahdesta haarasta."
},
"repeat": {
"label": "Toistaa",
"help": "Suorittaa sen sisällä olevat vaiheet tietyn määrän kertoja."
}
},
"send": {
"text": "Komento tai teksti",
"textPlaceholder": "systemctl käynnistä nginx uudelleen",
"pressEnter": "Paina Enter lähetyksen jälkeen",
"pressEnterHelp": "Poista tämä käytöstä kirjoittaaksesi tekstiä lähettämättä sitä."
},
"wait": {
"pattern": "Odota, kunnes tuloste sisältää",
"patternPlaceholder": "salasana:",
"timeout": "Luovuta jälkeenpäin",
"onTimeout": "Jos se ei koskaan ilmesty",
"stop": "Pysäytä makro",
"continue": "Jatka joka tapauksessa"
},
"delay": {
"duration": "Odota"
},
"ifStep": {
"pattern": "Jos tuloste sisältää",
"patternPlaceholder": "Aktiivinen: aktiivinen (käynnissä)",
"then": "Tee sitten tämä",
"else": "Muuten tee näin",
"emptyBranch": "Ei portaita"
},
"repeat": {
"count": "Kuinka monta kertaa",
"times": "kertaa"
},
"match": {
"useRegex": "Säännöllisen lausekkeen mukainen osuma",
"useRegexHelp": "Jätä tämä pois, jotta teksti vastaa täysin kirjoitettua tekstiä.",
"invalidRegex": "Ei kelvollinen säännöllinen lauseke, se tulkitaan pelkkänä tekstinä"
},
"seconds": "sekuntia",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Paramètres de la barre latérale...",
"hosts": "Hôtes",
"snippets": "Extraits",
"macros": "Macros",
"hostManager": "Gestionnaire d'hôtes",
"credentials": "Identifiants",
"connections": "Connexions",
@@ -869,7 +868,6 @@
"groupTags": "Étiquettes et épingles",
"groupTunnels": "Tunnels",
"groupJumpHosts": "Hôtes de saut",
"groupQuickActions": "Actions rapides",
"groupFeatureFlags": "drapeaux de fonctionnalités",
"groupAdvanced": "Configuration avancée",
"preview": "Aperçu",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Stocker des clés SSH ou des mots de passe",
"tmuxMonitor": "Moniteur Tmux",
"tmuxMonitorDesc": "Surveiller les sessions tmux sur vos hôtes",
"snippets": "Extraits",
"runSnippetDesc": "Exécuter dans l'onglet du terminal actif",
"noSnippetsFound": "Aucun extrait trouvé correspondant à \"{{search}}\"",
"recentActivity": "Activité récente",
"serversAndHosts": "Serveurs et hôtes",
"noHostsFound": "Aucun hôte trouvé correspondant à \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Onglet précédent",
"hotkeysJumpTab": "Aller à l'onglet"
},
"snippets": {
"title": "Extraits",
"createSnippetTitle": "Créer un extrait",
"createSnippetDescription": "Créer un nouvel extrait de commande pour une exécution rapide",
"nameLabel": "Nom",
"namePlaceholder": "par ex., Redémarrer Nginx",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Description facultative",
"optional": "Facultatif",
"folderLabel": "Dossier",
"noFolder": "Aucun dossier (Non classé)",
"commandLabel": "Commande",
"commandPlaceholder": "par ex., sudo systemctl restart nginx",
"typeLabel": "Taper",
"typeCommand": "Commande",
"typeNote": "Note",
"noteLabel": "Note",
"notePlaceholder": "par exemple, un texte d'invite ou de rappel réutilisable",
"cancel": "Annuler",
"createSnippetButton": "Créer l'extrait",
"createFolderTitle": "Créer un dossier",
"createFolderDescription": "Organisez vos extraits dans des dossiers",
"folderNameLabel": "Nom du dossier",
"folderNamePlaceholder": "par ex., Commandes système, Scripts Docker",
"folderColorLabel": "Couleur du dossier",
"folderIconLabel": "Icône du dossier",
"previewLabel": "Aperçu",
"folderNameFallback": "Nom du dossier",
"createFolderButton": "Créer un dossier",
"targetTerminals": "Terminaux cibles",
"selectAll": "Tous",
"selectNone": "Aucun",
"noTerminalTabsOpen": "Aucun onglet de terminal ouvert",
"searchPlaceholder": "Rechercher des snippets...",
"newSnippet": "Nouveau snippet",
"newFolder": "Nouveau dossier",
"run": "Exécuter",
"noSnippetsInFolder": "Aucun snippet dans ce dossier",
"uncategorized": "Non classé",
"editSnippetTitle": "Modifier le snippet",
"editSnippetDescription": "Mettre à jour ce snippet de commande",
"saveSnippetButton": "Enregistrer les modifications",
"createSuccess": "Snippet créé avec succès",
"createFailed": "Échec de la création du snippet",
"updateSuccess": "Snippet mis à jour avec succès",
"updateFailed": "Échec de la mise à jour du snippet",
"deleteFailed": "Échec de la suppression du snippet",
"folderCreateSuccess": "Dossier créé avec succès",
"folderCreateFailed": "Échec de la création du dossier",
"editFolderTitle": "Modifier le dossier",
"editFolderDescription": "Renommer ou modifier l'apparence de ce dossier",
"saveFolderButton": "Enregistrer les modifications",
"editFolder": "Modifier le dossier",
"deleteFolder": "Supprimer le dossier",
"folderDeleteSuccess": "Dossier « {{name}} » supprimé",
"folderDeleteFailed": "Échec de la suppression du dossier",
"folderEditSuccess": "Dossier mis à jour avec succès",
"folderEditFailed": "Échec de la mise à jour du dossier",
"confirmRunMessage": "Exécuter « {{name}} » ?",
"confirmRunButton": "Exécuter",
"runSuccess": "Exécuté « {{name}} » dans {{count}} terminal(s)",
"pasteSuccess": "Collé \"{{name}}\" dans le(s) terminal(s) {{count}}",
"pasteToTerminal": "Coller",
"copySuccess": "Copié « {{name}} » dans le presse-papiers",
"shareTitle": "Partager le snippet",
"shareUser": "Utilisateur",
"shareRole": "Rôle",
"selectUser": "Sélectionner un utilisateur...",
"selectRole": "Sélectionner un rôle...",
"shareSuccess": "Snippet partagé avec succès",
"shareFailed": "Échec du partage du snippet",
"revokeSuccess": "Accès révoqué",
"revokeFailed": "Échec de la révocation de l’accès",
"currentAccess": "Accès actuel",
"shareLoadError": "Échec du chargement des données de partage",
"loading": "Chargement...",
"close": "Fermer",
"reorderFailed": "Échec de l’enregistrement de l’ordre des snippets",
"importExport": "Importer / Exporter",
"exportBtn": "Exporter JSON",
"importBtn": "Importer JSON",
"exportSuccess": "Snippets exportés avec succès",
"exportFailed": "Échec de l’exportation des snippets",
"importTitle": "Importer des snippets",
"importDescription": "Importer des snippets et des dossiers depuis un fichier JSON exporté par Termix",
"importDropOrClick": "Déposez un fichier JSON ici ou cliquez pour parcourir",
"importSelectedFile": "Sélectionné : {{name}}",
"importOverwrite": "Écraser les snippets existants ayant le même nom et le même dossier",
"importStartBtn": "Importer",
"importSuccess": "Importation terminée : {{snippets}} snippet(s) ajouté(s), {{updated}} mis à jour, {{skipped}} ignoré(s), {{folders}} dossier(s) ajouté(s)",
"importFailed": "Échec de l’importation des snippets",
"importInvalidFile": "Fichier non valide : un objet JSON avec des tableaux « snippets » ou « folders » est attendu",
"targetHostsLabel": "Hôtes cibles",
"targetHostsHint": "Attribuez des hôtes pour exécuter ce snippet directement sans terminal ouvert.",
"noHostsAvailable": "Aucun hôte configuré",
"clearTargetHosts": "Tout effacer",
"hasTargetHosts": "Avec hôtes cibles",
"runOnTargets": "Exécuter sur les cibles",
"directRunSuccess": "« {{name}} » exécuté sur {{count}} hôte(s)",
"directRunPartialFail": "« {{name}} » a échoué sur un ou plusieurs hôtes",
"executionResultTitle": "Résultats d'exécution : {{name}}",
"executionResultDescription": "Sortie de l'exécution du snippet sur chaque hôte cible.",
"executionSuccess": "Succès",
"executionFailed": "Échec",
"expandAll": "Développer tous les dossiers",
"collapseAll": "Réduire tous les dossiers",
"settings": "Paramètres de l'extrait",
"variablesHint": "Variables : $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Exécutez \"{{name}}\"",
"variablesDialogDescription": "Renseignez les valeurs nécessaires à cet extrait de code avant de l'exécuter.",
"variablesPreviewLabel": "Aperçu",
"variablesConfirmButton": "Courir"
},
"keybindings": {
"title": "Raccourcis clavier",
"description": "Personnalisez les raccourcis de copie, de collage et de contrôle du terminal, ou associez des touches pour envoyer du texte ou exécuter un extrait de code.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Cliquez et appuyez sur les touches",
"recording": "Appuyez sur les touches...",
"comboRequiredError": "Appuyez d'abord sur une combinaison de touches",
"textRequiredError": "Saisissez le texte à envoyer",
"controlCodeRequiredError": "Saisissez une seule lettre pour le code de contrôle",
"snippetRequiredError": "Sélectionnez un extrait",
"conflictWarning": "Cette combinaison est déjà liée à {{combo}}. Sauvegarder déclenchera les deux.",
"actionLabel": "Action",
"actionCopy": "Copier la sélection",
"actionPaste": "Coller",
"actionSendControlCode": "Envoyer le signal Ctrl+lettre",
"actionSendText": "Envoyer un SMS",
"actionRunSnippet": "Exécuter l'extrait de code existant",
"actionNextTab": "Passer à l'onglet suivant",
"actionPreviousTab": "Passer à l'onglet précédent",
"actionOpenCommandPalette": "Ouvrir la recherche rapide",
"controlCodeLabel": "Lettre",
"textLabel": "Texte à envoyer",
"appendEnterLabel": "Appuyez sur Entrée après l'envoi",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Sélectionnez un extrait",
"orphanedSnippetWarning": "extrait introuvable",
"clipboardPermissionNote": "Il se peut que certains navigateurs demandent l'autorisation d'accéder au presse-papiers lors de la première utilisation."
"actionOpenCommandPalette": "Ouvrir la recherche rapide"
},
"userProfile": {
"donateTitle": "Soutenir Termix",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Le sujet est obligatoire",
"ntfyToken": "Jeton d'accès (facultatif)",
"channelSaveFailed": "Échec de l'enregistrement du canal"
},
"macros": {
"title": "Macros",
"description": "Exécutez une série de commandes enregistrées dans un terminal et réagissez à la réponse du serveur.",
"target": "Cible",
"noTerminal": "Aucun terminal ouvert",
"empty": "Pas encore de macros",
"emptyHint": "Créez une macro pour automatiser un flux de terminal, comme la connexion, le redémarrage d'un service ou la réponse à des invites.",
"create": "Nouvelle macro",
"back": "Retour aux macros",
"name": "Nom",
"namePlaceholder": "Redémarrer le serveur web",
"nameRequired": "Donnez un nom à la macro avant de l'enregistrer.",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Fonctionnement de cette macro (facultatif)",
"unsaved": "Modifications non enregistrées",
"save": "Sauvegarder",
"saved": "Macro enregistrée",
"saveFailed": "Impossible d'enregistrer la macro",
"delete": "Supprimer",
"deleteTitle": "Supprimer cette macro ?",
"deleteBody": "\"{{name}}\" sera définitivement supprimé. Cette action est irréversible.",
"deleted": "Macro supprimée",
"cancel": "Annuler",
"run": "Courir",
"stop": "Arrêt",
"running": "En cours d'exécution...",
"completed": "Macro terminé",
"failed": "Échec de la macro",
"loadFailed": "Impossible de charger les macros",
"needTerminal": "Ouvrez et connectez d'abord un terminal.",
"noSteps": "Cette macro ne comporte pas encore d'étapes. Ajoutez-en une ci-dessous.",
"addStep": "Ajouter une étape",
"stepNumber": "Étape {{number}}",
"removeStep": "Supprimer l'étape",
"confirmRunTitle": "Exécuter cette macro ?",
"confirmRunBody": "\"{{name}}\" enverra des commandes à {{host}}.",
"confirmRunAction": "Exécuter une macro",
"stepTypes": {
"send": {
"label": "Envoyer la commande",
"help": "Saisit du texte dans le terminal, comme si vous l'aviez saisi manuellement."
},
"wait": {
"label": "Attendez la sortie",
"help": "Il y a une pause jusqu'à ce que le serveur affiche le texte que vous avez indiqué, comme une invite de mot de passe."
},
"delay": {
"label": "Pause",
"help": "Attendre un laps de temps fixe avant de passer à l'étape suivante."
},
"if": {
"label": "Si la sortie contient",
"help": "Vérifie tout ce que le serveur a imprimé jusqu'à présent, puis exécute l'une des deux branches."
},
"repeat": {
"label": "Répéter",
"help": "Exécute les étapes qu'il contient un nombre de fois défini."
}
},
"send": {
"text": "Commande ou texte",
"textPlaceholder": "systemctl redémarrer nginx",
"pressEnter": "Appuyez sur Entrée après l'envoi",
"pressEnterHelp": "Désactivez cette option pour saisir du texte sans le soumettre."
},
"wait": {
"pattern": "Attendez que la sortie contienne",
"patternPlaceholder": "mot de passe:",
"timeout": "Abandonner après",
"onTimeout": "S'il n'apparaît jamais",
"stop": "Arrêtez la macro",
"continue": "Continuez quand même"
},
"delay": {
"duration": "Attendre"
},
"ifStep": {
"pattern": "Si la sortie contient",
"patternPlaceholder": "Actif : actif (en cours d'exécution)",
"then": "Ensuite, faites ceci",
"else": "Sinon, faites ceci.",
"emptyBranch": "Pas d'étapes"
},
"repeat": {
"count": "Nombre de fois",
"times": "fois"
},
"match": {
"useRegex": "Correspondance avec une expression régulière",
"useRegexHelp": "Laissez cette section de côté pour que le texte corresponde exactement à ce qui est écrit.",
"invalidRegex": "Il ne s'agit pas d'une expression régulière valide ; elle sera interprétée comme du texte brut."
},
"seconds": "secondes",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "הגדרות סרגל צד...",
"hosts": "מארחים",
"snippets": "קטעי טקסט",
"macros": "פקודות מאקרו",
"hostManager": "מנהל מארח",
"credentials": "אישורים",
"connections": "חיבורים",
@@ -869,7 +868,6 @@
"groupTags": "תגיות וסיכה",
"groupTunnels": "מנהרות",
"groupJumpHosts": "מארחים קפיציים",
"groupQuickActions": "פעולות מהירות",
"groupFeatureFlags": "דגלי תכונה",
"groupAdvanced": "תצורה מתקדמת",
"preview": "תצוגה מקדימה",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "אחסון מפתחות או סיסמאות SSH",
"tmuxMonitor": "צג Tmux",
"tmuxMonitorDesc": "ניטור הפעלות tmux בין המארחים שלך",
"snippets": "קטעי טקסט",
"runSnippetDesc": "הפעל בלשונית הטרמינל הפעילה",
"noSnippetsFound": "לא נמצאו קטעי טקסט תואמים ל-\"{{search}}\"",
"recentActivity": "פעילות אחרונה",
"serversAndHosts": "שרתים ומארחים",
"noHostsFound": "לא נמצאו מארחים התואמים ל-\"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "הכרטיסייה הקודמת",
"hotkeysJumpTab": "קפיצה לכרטיסייה"
},
"snippets": {
"title": "קטעי טקסט",
"createSnippetTitle": "צור קטע",
"createSnippetDescription": "צור קטע פקודה חדש לביצוע מהיר",
"nameLabel": "שֵׁם",
"namePlaceholder": "לדוגמה, הפעל מחדש את Nginx",
"descriptionLabel": "תֵאוּר",
"descriptionPlaceholder": "תיאור אופציונלי",
"optional": "אופציונלי",
"folderLabel": "תיקייה",
"noFolder": "אין תיקייה (ללא קטגוריה)",
"commandLabel": "פְּקוּדָה",
"commandPlaceholder": "לדוגמה, sudo systemctl הפעל מחדש את nginx",
"typeLabel": "סוּג",
"typeCommand": "פְּקוּדָה",
"typeNote": "פֶּתֶק",
"noteLabel": "פֶּתֶק",
"notePlaceholder": "לדוגמה, טקסט הנחיה או תזכורת לשימוש חוזר",
"cancel": "לְבַטֵל",
"createSnippetButton": "צור קטע",
"createFolderTitle": "צור תיקייה",
"createFolderDescription": "ארגנו את הקטעים שלכם בתיקיות",
"folderNameLabel": "שם התיקייה",
"folderNamePlaceholder": "לדוגמה, פקודות מערכת, סקריפטים של Docker",
"folderColorLabel": "צבע התיקייה",
"folderIconLabel": "סמל תיקייה",
"previewLabel": "תצוגה מקדימה",
"folderNameFallback": "שם התיקייה",
"createFolderButton": "צור תיקייה",
"targetTerminals": "טרמינלים של יעד",
"selectAll": "כֹּל",
"selectNone": "אַף לֹא אֶחָד",
"noTerminalTabsOpen": "אין לשוניות מסוף פתוחות",
"searchPlaceholder": "חיפוש קטעי טקסט...",
"newSnippet": "קטע חדש",
"newFolder": "תיקייה חדשה",
"run": "לָרוּץ",
"noSnippetsInFolder": "אין קטעי טקסט בתיקייה זו",
"uncategorized": "ללא קטגוריה",
"editSnippetTitle": "עריכת קטע",
"editSnippetDescription": "עדכן את קטע הפקודה הזה",
"saveSnippetButton": "שמירת שינויים",
"createSuccess": "קטע הקוד נוצר בהצלחה",
"createFailed": "נכשלה יצירת קטע הקוד",
"updateSuccess": "קטע הקוד עודכן בהצלחה",
"updateFailed": "נכשל עדכון הקטע",
"deleteFailed": "מחיקת קטע הקוד נכשלה",
"folderCreateSuccess": "תיקייה נוצרה בהצלחה",
"folderCreateFailed": "יצירת התיקייה נכשלה",
"editFolderTitle": "עריכת תיקייה",
"editFolderDescription": "שינוי שם או שינוי מראה של תיקייה זו",
"saveFolderButton": "שמירת שינויים",
"editFolder": "עריכת תיקייה",
"deleteFolder": "מחיקת תיקייה",
"folderDeleteSuccess": "תיקייה \"{{name}}\" נמחקה",
"folderDeleteFailed": "מחיקת התיקייה נכשלה",
"folderEditSuccess": "התיקייה עודכנה בהצלחה",
"folderEditFailed": "עדכון התיקייה נכשל",
"confirmRunMessage": "להפעיל את \"{{name}}\"?",
"confirmRunButton": "לָרוּץ",
"runSuccess": "הריץ את \"{{name}}\" בטרמינל/ים {{count}}",
"pasteSuccess": "הודבק \"{{name}}\" לתוך הטרמינל/ים {{count}}",
"pasteToTerminal": "לְהַדבִּיק",
"copySuccess": "הועתק \"{{name}}\" ללוח",
"shareTitle": "שתף קטע",
"shareUser": "מִשׁתַמֵשׁ",
"shareRole": "תַפְקִיד",
"selectUser": "בחר משתמש...",
"selectRole": "בחר תפקיד...",
"shareSuccess": "קטע הטקסט שותף בהצלחה",
"shareFailed": "נכשל שיתוף הקטע",
"revokeSuccess": "הגישה בוטלה",
"revokeFailed": "ביטול הגישה נכשל",
"currentAccess": "גישה נוכחית",
"shareLoadError": "טעינת נתוני השיתוף נכשלה",
"loading": "טְעִינָה...",
"close": "לִסְגוֹר",
"reorderFailed": "שמירת סדר הקטעים נכשלה",
"importExport": "ייבוא / ייצוא",
"exportBtn": "ייצוא JSON",
"importBtn": "ייבוא JSON",
"exportSuccess": "קטעי הטקסט יוצאו בהצלחה",
"exportFailed": "ייצוא קטעי הקוד נכשל",
"importTitle": "ייבוא קטעי טקסט",
"importDescription": "ייבוא קטעי טקסט ותיקיות מקובץ JSON שיוצא על ידי Termix",
"importDropOrClick": "שחררו קובץ JSON כאן או לחצו כדי לדפדף",
"importSelectedFile": "נבחר: {{name}}",
"importOverwrite": "החלפת קטעי טקסט קיימים עם אותו שם ותיקייה",
"importStartBtn": "יְבוּא",
"importSuccess": "ייבוא הושלם: קטעי טקסט {{snippets}} נוספו, {{updated}} עודכנו, {{skipped}} דילגו, {{folders}} תיקיות נוספו",
"importFailed": "ייבוא קטעי הקוד נכשל",
"importInvalidFile": "קובץ לא חוקי: ציפיתי לאובייקט JSON עם קטעי טקסט או מערכי תיקיות",
"targetHostsLabel": "מארחי יעד",
"targetHostsHint": "הקצה מארחים להפעלת קטע זה ישירות ללא טרמינל פתוח.",
"noHostsAvailable": "לא הוגדרו מארחים",
"clearTargetHosts": "נקה הכל",
"hasTargetHosts": "יש מארחים יעד",
"runOnTargets": "רוץ על מטרות",
"directRunSuccess": "הריץ את \"{{name}}\" על מארחים {{count}}",
"directRunPartialFail": "\"{{name}}\" נכשל במחשב מארח אחד או יותר",
"executionResultTitle": "תוצאות ביצוע: {{name}}",
"executionResultDescription": "פלט מהפעלת קטע הקוד על כל מארח יעד.",
"executionSuccess": "הַצלָחָה",
"executionFailed": "נִכשָׁל",
"expandAll": "הרחב את כל התיקיות",
"collapseAll": "כווץ את כל התיקיות",
"settings": "הגדרות קטע",
"variablesHint": "משתנים: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "הפעל \"{{name}}\"",
"variablesDialogDescription": "מלא את הערכים הדרושים לקטע זה לפני הפעלתו.",
"variablesPreviewLabel": "תצוגה מקדימה",
"variablesConfirmButton": "לָרוּץ"
},
"keybindings": {
"title": "קיצורי מקלדת",
"description": "התאם אישית קיצורי דרך להעתקה, הדבקה ובקרה של הטרמינל, או חבר מקשים לשליחת טקסט או להפעלת קטע קוד.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "לחיצה ולחיצה על המקשים",
"recording": "לחץ על מקשים...",
"comboRequiredError": "לחץ תחילה על צירוף מקשים",
"textRequiredError": "הזן את הטקסט לשליחה",
"controlCodeRequiredError": "הזן אות אחת עבור קוד הבקרה",
"snippetRequiredError": "בחר קטע",
"conflictWarning": "צירוף זה כבר קשור ל- {{combo}}. שמירה תגרום לשניהם לפעול.",
"actionLabel": "פְּעוּלָה",
"actionCopy": "העתקת הבחירה",
"actionPaste": "לְהַדבִּיק",
"actionSendControlCode": "שלח אות Ctrl+אות",
"actionSendText": "שלח טקסט",
"actionRunSnippet": "הפעלת קטע קוד קיים",
"actionNextTab": "עבור לכרטיסייה הבאה",
"actionPreviousTab": "מעבר לכרטיסייה הקודמת",
"actionOpenCommandPalette": "פתח חיפוש מהיר",
"controlCodeLabel": "מִכְתָב",
"textLabel": "טקסט לשליחה",
"appendEnterLabel": "לחץ על Enter לאחר השליחה",
"snippetLabel": "קטע",
"selectSnippetPlaceholder": "בחר קטע",
"orphanedSnippetWarning": "קטע לא נמצא",
"clipboardPermissionNote": "ייתכן שתבקש הרשאה ללוח בעת השימוש הראשון בדפדפנים מסוימים."
"actionOpenCommandPalette": "פתח חיפוש מהיר"
},
"userProfile": {
"donateTitle": "לשמור על טרמיקס בחיים",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "נושא נדרש",
"ntfyToken": "אסימון גישה (אופציונלי)",
"channelSaveFailed": "שמירת הערוץ נכשלה"
},
"macros": {
"title": "פקודות מאקרו",
"description": "הפעל סדרה שמורה של פקודות בטרמינל, והגב למה שהשרת שולח בחזרה.",
"target": "יַעַד",
"noTerminal": "אין טרמינל פתוח",
"empty": "עדיין אין פקודות מאקרו",
"emptyHint": "צור מאקרו כדי להפוך זרימת מסוף לאוטומטית, כמו כניסה, הפעלה מחדש של שירות או מענה לבקשות.",
"create": "מאקרו חדש",
"back": "חזרה למאקרו",
"name": "שֵׁם",
"namePlaceholder": "הפעל מחדש את שרת האינטרנט",
"nameRequired": "תן שם למאקרו לפני השמירה",
"descriptionLabel": "תֵאוּר",
"descriptionPlaceholder": "מה המאקרו הזה עושה (אופציונלי)",
"unsaved": "שינויים שלא נשמרו",
"save": "לְהַצִיל",
"saved": "מאקרו נשמר",
"saveFailed": "לא ניתן היה לשמור את המאקרו",
"delete": "לִמְחוֹק",
"deleteTitle": "למחוק את המאקרו הזה?",
"deleteBody": "\"{{name}}\" יימחק לצמיתות. לא ניתן לבטל פעולה זו.",
"deleted": "מאקרו נמחק",
"cancel": "לְבַטֵל",
"run": "לָרוּץ",
"stop": "לְהַפְסִיק",
"running": "רִיצָה...",
"completed": "מאקרו הסתיים",
"failed": "מאקרו נכשל",
"loadFailed": "לא ניתן היה לטעון פקודות מאקרו",
"needTerminal": "פתח וחבר תחילה טרמינל",
"noSteps": "למאקרו הזה עדיין אין שלבים. הוסף אחד למטה.",
"addStep": "הוסף שלב",
"stepNumber": "שלב {{number}}",
"removeStep": "הסר שלב",
"confirmRunTitle": "להפעיל את המאקרו הזה?",
"confirmRunBody": "\"{{name}}\" ישלח פקודות אל {{host}}.",
"confirmRunAction": "הפעל מאקרו",
"stepTypes": {
"send": {
"label": "שלח פקודה",
"help": "מקליד טקסט לתוך הטרמינל, אותו הדבר כאילו הקלדת אותו."
},
"wait": {
"label": "המתן לפלט",
"help": "מושהה עד שהשרת ידפיס את הטקסט שציינת, כמו בקשת סיסמה."
},
"delay": {
"label": "הַפסָקָה",
"help": "ממתין פרק זמן קבוע לפני השלב הבא."
},
"if": {
"label": "אם הפלט מכיל",
"help": "בודק את כל מה שהשרת הדפיס עד כה, ולאחר מכן מפעיל אחד משני ענפים."
},
"repeat": {
"label": "לַחֲזוֹר עַל",
"help": "מריץ את השלבים בתוכו מספר קבוע של פעמים."
}
},
"send": {
"text": "פקודה או טקסט",
"textPlaceholder": "הפעל מחדש את nginx של systemctl",
"pressEnter": "לחץ על Enter לאחר השליחה",
"pressEnterHelp": "כבה אפשרות זו כדי להקליד טקסט מבלי לשלוח אותו."
},
"wait": {
"pattern": "המתן עד שהפלט יכיל",
"patternPlaceholder": "סִיסמָה:",
"timeout": "לוותר אחרי",
"onTimeout": "אם זה אף פעם לא מופיע",
"stop": "עצור את המאקרו",
"continue": "המשיכו בכל מקרה"
},
"delay": {
"duration": "חכה ל"
},
"ifStep": {
"pattern": "אם הפלט מכיל",
"patternPlaceholder": "פעיל: פעיל (רץ)",
"then": "אז תעשה את זה",
"else": "אחרת תעשו את זה",
"emptyBranch": "אין מדרגות"
},
"repeat": {
"count": "מספר פעמים",
"times": "פִּי"
},
"match": {
"useRegex": "התאמה עם ביטוי רגולרי",
"useRegexHelp": "השאר את זה כבוי כדי שהטקסט יתאים בדיוק כפי שנכתב.",
"invalidRegex": "ביטוי רגולרי לא תקין, הוא יתאים כטקסט רגיל"
},
"seconds": "שניות",
"secondsShort": "{{count}}ים"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "साइडबार सेटिंग्स...",
"hosts": "होस्ट्स",
"snippets": "स्निपेट्स",
"macros": "मैक्रो",
"hostManager": "होस्ट प्रबंधक",
"credentials": "क्रेडेंशियल्स",
"connections": "कनेक्शन्स",
@@ -869,7 +868,6 @@
"groupTags": "टैग और पिन",
"groupTunnels": "सुरंगों",
"groupJumpHosts": "जंप होस्ट",
"groupQuickActions": "त्वरित कार्रवाइयां",
"groupFeatureFlags": "फ़ीचर फ़्लैग",
"groupAdvanced": "उन्नत कॉन्फ़िगरेशन",
"preview": "पूर्व दर्शन",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH कुंजी या पासवर्ड संग्रहीत करें",
"tmuxMonitor": "Tmux मॉनिटर",
"tmuxMonitorDesc": "अपने होस्ट में tmux सत्रों की निगरानी करें",
"snippets": "स्निपेट्स",
"runSnippetDesc": "सक्रिय टर्मिनल टैब में चलाएँ",
"noSnippetsFound": "\"{{search}} \" से मेल खाने वाले कोई अंश नहीं मिले",
"recentActivity": "हाल की गतिविधि",
"serversAndHosts": "सर्वर और होस्ट",
"noHostsFound": "\"{{search}}\" से मेल खाने वाला कोई होस्ट नहीं मिला",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "पिछला टैब",
"hotkeysJumpTab": "टैब पर जाएं"
},
"snippets": {
"title": "स्निपेट्स",
"createSnippetTitle": "स्निपेट बनाएं",
"createSnippetDescription": "त्वरित निष्पादन के लिए एक नया कमांड स्निपेट बनाएं",
"nameLabel": "नाम",
"namePlaceholder": "जैसे, Nginx पुनरारंभ करें",
"descriptionLabel": "विवरण",
"descriptionPlaceholder": "वैकल्पिक विवरण",
"optional": "वैकल्पिक",
"folderLabel": "फ़ोल्डर",
"noFolder": "कोई फ़ोल्डर नहीं (अवर्गीकृत)",
"commandLabel": "कमांड",
"commandPlaceholder": "जैसे, sudo systemctl restart nginx",
"typeLabel": "प्रकार",
"typeCommand": "आज्ञा",
"typeNote": "टिप्पणी",
"noteLabel": "टिप्पणी",
"notePlaceholder": "उदाहरण के लिए, एक पुन: उपयोग योग्य संकेत या अनुस्मारक पाठ",
"cancel": "रद्द करें",
"createSnippetButton": "स्निपेट बनाएं",
"createFolderTitle": "फ़ोल्डर बनाएं",
"createFolderDescription": "अपने स्निपेट्स को फ़ोल्डर में व्यवस्थित करें",
"folderNameLabel": "फ़ोल्डर का नाम",
"folderNamePlaceholder": "जैसे, सिस्टम कमांड्स, Docker स्क्रिप्ट",
"folderColorLabel": "फ़ोल्डर का रंग",
"folderIconLabel": "फ़ोल्डर आइकन",
"previewLabel": "पूर्वावलोकन",
"folderNameFallback": "फ़ोल्डर का नाम",
"createFolderButton": "फ़ोल्डर बनाएं",
"targetTerminals": "लक्ष्य टर्मिनल",
"selectAll": "सभी",
"selectNone": "कोई नहीं",
"noTerminalTabsOpen": "कोई टर्मिनल टैब खुला नहीं है",
"searchPlaceholder": "स्निपेट खोजें...",
"newSnippet": "नया स्निपेट",
"newFolder": "नया फ़ोल्डर",
"run": "चलाएँ",
"noSnippetsInFolder": "इस फ़ोल्डर में कोई स्निपेट नहीं है",
"uncategorized": "अवर्गीकृत",
"editSnippetTitle": "स्निपेट संपादित करें",
"editSnippetDescription": "इस कमांड स्निपेट को अपडेट करें",
"saveSnippetButton": "परिवर्तन सहेजें",
"createSuccess": "स्निपेट सफलतापूर्वक बनाया गया",
"createFailed": "स्निपेट बनाने में विफल",
"updateSuccess": "स्निपेट सफलतापूर्वक अपडेट किया गया",
"updateFailed": "स्निपेट अपडेट करने में विफल",
"deleteFailed": "स्निपेट हटाने में विफल",
"folderCreateSuccess": "फ़ोल्डर सफलतापूर्वक बनाया गया",
"folderCreateFailed": "फ़ोल्डर बनाने में विफल",
"editFolderTitle": "फ़ोल्डर संपादित करें",
"editFolderDescription": "इस फ़ोल्डर का नाम बदलें या उसका स्वरूप बदलें",
"saveFolderButton": "परिवर्तन सहेजें",
"editFolder": "फ़ोल्डर संपादित करें",
"deleteFolder": "फ़ोल्डर हटाएं",
"folderDeleteSuccess": "फ़ोल्डर \"{{name}}\" हटाया गया",
"folderDeleteFailed": "फ़ोल्डर हटाने में विफल",
"folderEditSuccess": "फ़ोल्डर सफलतापूर्वक अपडेट किया गया",
"folderEditFailed": "फ़ोल्डर अपडेट करने में विफल",
"confirmRunMessage": "\"{{name}}\" चलाएँ?",
"confirmRunButton": "चलाएँ",
"runSuccess": "\"{{name}}\" को {{count}} टर्मिनल में चलाया गया",
"pasteSuccess": "\"{{name}}\" को {{count}} टर्मिनल(ओं) में पेस्ट किया गया",
"pasteToTerminal": "पेस्ट करें",
"copySuccess": "\"{{name}}\" को क्लिपबोर्ड पर कॉपी किया गया",
"shareTitle": "स्निपेट साझा करें",
"shareUser": "उपयोगकर्ता",
"shareRole": "भूमिका",
"selectUser": "एक उपयोगकर्ता चुनें...",
"selectRole": "एक भूमिका चुनें...",
"shareSuccess": "स्निपेट सफलतापूर्वक साझा किया गया",
"shareFailed": "स्निपेट साझा करने में विफल",
"revokeSuccess": "पहुँच रद्द की गई",
"revokeFailed": "पहुँच रद्द करने में विफल",
"currentAccess": "वर्तमान पहुँच",
"shareLoadError": "साझा डेटा लोड करने में विफल",
"loading": "लोड हो रहा है...",
"close": "बंद करें",
"reorderFailed": "स्निपेट क्रम सहेजने में विफल",
"importExport": "आयात / निर्यात",
"exportBtn": "JSON निर्यात करें",
"importBtn": "JSON आयात करें",
"exportSuccess": "स्निपेट सफलतापूर्वक निर्यात किए गए",
"exportFailed": "स्निपेट निर्यात करने में विफल",
"importTitle": "स्निपेट आयात करें",
"importDescription": "Termix द्वारा निर्यात की गई JSON फ़ाइल से स्निपेट और फ़ोल्डर आयात करें",
"importDropOrClick": "यहाँ JSON फ़ाइल ड्रॉप करें या ब्राउज़ करने के लिए क्लिक करें",
"importSelectedFile": "चयनित: {{name}}",
"importOverwrite": "समान नाम और फ़ोल्डर वाले मौजूदा स्निपेट को अधिलेखित करें",
"importStartBtn": "आयात करें",
"importSuccess": "आयात पूर्ण: {{snippets}} स्निपेट जोड़े गए, {{updated}} अपडेट किए गए, {{skipped}} छोड़े गए, {{folders}} फ़ोल्डर जोड़े गए",
"importFailed": "स्निपेट आयात करने में विफल",
"importInvalidFile": "अमान्य फ़ाइल: स्निपेट या फ़ोल्डर सरणियों के साथ एक JSON ऑब्जेक्ट अपेक्षित था",
"targetHostsLabel": "लक्ष्य होस्ट",
"targetHostsHint": "बिना खुले टर्मिनल के इस स्निपेट को सीधे चलाने के लिए होस्ट असाइन करें।",
"noHostsAvailable": "कोई होस्ट कॉन्फ़िगर नहीं है",
"clearTargetHosts": "सभी साफ़ करें",
"hasTargetHosts": "लक्ष्य होस्ट मौजूद हैं",
"runOnTargets": "लक्ष्यों पर चलाएँ",
"directRunSuccess": "\"{{name}}\" को {{count}} होस्ट पर चलाया गया",
"directRunPartialFail": "\"{{name}}\" एक या अधिक होस्ट पर विफल रहा",
"executionResultTitle": "निष्पादन परिणाम: {{name}}",
"executionResultDescription": "प्रत्येक लक्ष्य होस्ट पर स्निपेट चलाने से प्राप्त आउटपुट।",
"executionSuccess": "सफल",
"executionFailed": "असफल",
"expandAll": "सभी फ़ोल्डर खोलें",
"collapseAll": "सभी फ़ोल्डरों को बंद करें",
"settings": "स्निपेट सेटिंग्स",
"variablesHint": "चर: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "रन \"{{name}}\"",
"variablesDialogDescription": "इस कोड स्निपेट को चलाने से पहले इसमें आवश्यक मान भरें।",
"variablesPreviewLabel": "पूर्व दर्शन",
"variablesConfirmButton": "दौड़ना"
},
"keybindings": {
"title": "कुंजीपटल अल्प मार्ग",
"description": "टर्मिनल कॉपी, पेस्ट और कंट्रोल शॉर्टकट को कस्टमाइज़ करें, या टेक्स्ट भेजने या स्निपेट चलाने के लिए कुंजियों को बाइंड करें।",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "क्लिक करें और कुंजियाँ दबाएँ",
"recording": "बटन दबाएँ...",
"comboRequiredError": "पहले एक कुंजी संयोजन दबाएँ",
"textRequiredError": "भेजने के लिए टेक्स्ट दर्ज करें",
"controlCodeRequiredError": "नियंत्रण कोड के लिए एक अक्षर दर्ज करें",
"snippetRequiredError": "एक अंश का चयन करें",
"conflictWarning": "यह संयोजन पहले से ही {{combo}}से जुड़ा हुआ है। सहेजने से दोनों आग पकड़ लेंगे।",
"actionLabel": "कार्रवाई",
"actionCopy": "चयन की प्रतिलिपि बनाएँ",
"actionPaste": "पेस्ट करें",
"actionSendControlCode": "Ctrl+अक्षर सिग्नल भेजें",
"actionSendText": "संदेश भेजें",
"actionRunSnippet": "मौजूदा स्निपेट चलाएँ",
"actionNextTab": "अगले टैब पर जाएं",
"actionPreviousTab": "पिछले टैब पर जाएं",
"actionOpenCommandPalette": "त्वरित खोज खोलें",
"controlCodeLabel": "पत्र",
"textLabel": "भेजने के लिए संदेश",
"appendEnterLabel": "भेजने के बाद एंटर दबाएँ",
"snippetLabel": "टुकड़ा",
"selectSnippetPlaceholder": "एक अंश का चयन करें",
"orphanedSnippetWarning": "अंश नहीं मिला",
"clipboardPermissionNote": "कुछ ब्राउज़रों में पहली बार उपयोग करने पर क्लिपबोर्ड की अनुमति मांगी जा सकती है।"
"actionOpenCommandPalette": "त्वरित खोज खोलें"
},
"userProfile": {
"donateTitle": "Termix को जीवित रखें",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "विषय आवश्यक है",
"ntfyToken": "एक्सेस टोकन (वैकल्पिक)",
"channelSaveFailed": "चैनल सहेजने में विफल"
},
"macros": {
"title": "मैक्रो",
"description": "टर्मिनल में सहेजे गए कमांडों की एक श्रृंखला चलाएं, और सर्वर द्वारा वापस भेजे गए कमांडों पर प्रतिक्रिया दें।",
"target": "लक्ष्य",
"noTerminal": "कोई टर्मिनल खुला नहीं है",
"empty": "अभी तक कोई मैक्रो नहीं है",
"emptyHint": "लॉग इन करना, किसी सेवा को रीस्टार्ट करना या प्रॉम्प्ट का जवाब देना जैसे टर्मिनल कार्यों को स्वचालित करने के लिए एक मैक्रो बनाएं।",
"create": "नया मैक्रो",
"back": "मैक्रोज़ पर वापस जाएँ",
"name": "नाम",
"namePlaceholder": "वेब सर्वर को पुनः आरंभ करें",
"nameRequired": "मैक्रो को सेव करने से पहले उसे एक नाम दें।",
"descriptionLabel": "विवरण",
"descriptionPlaceholder": "यह मैक्रो क्या करता है (वैकल्पिक)",
"unsaved": "असहेजित परिवर्तन",
"save": "बचाना",
"saved": "मैक्रो सहेजा गया",
"saveFailed": "मैक्रो को सहेजा नहीं जा सका",
"delete": "मिटाना",
"deleteTitle": "क्या इस मैक्रो को हटा दें?",
"deleteBody": "\"{{name}}\" को स्थायी रूप से हटा दिया जाएगा। इसे पूर्ववत नहीं किया जा सकता है।",
"deleted": "मैक्रो हटा दिया गया",
"cancel": "रद्द करना",
"run": "दौड़ना",
"stop": "रुकना",
"running": "दौड़ना...",
"completed": "मैक्रो समाप्त हो गया",
"failed": "मैक्रो विफल रहा",
"loadFailed": "मैक्रो लोड नहीं हो सके",
"needTerminal": "सबसे पहले एक टर्मिनल खोलें और उससे कनेक्ट करें।",
"noSteps": "इस मैक्रो में अभी तक कोई चरण नहीं है। कृपया नीचे एक चरण जोड़ें।",
"addStep": "चरण जोड़ें",
"stepNumber": "चरण {{number}}",
"removeStep": "चरण हटाएँ",
"confirmRunTitle": "क्या आप इस मैक्रो को चलाना चाहते हैं?",
"confirmRunBody": "\"{{name}}\" {{host}} को कमांड भेजेगा।",
"confirmRunAction": "मैक्रो चलाएँ",
"stepTypes": {
"send": {
"label": "कमांड भेजें",
"help": "यह टर्मिनल में ठीक उसी तरह टेक्स्ट टाइप करता है जैसे आप उसे खुद टाइप करते हैं।"
},
"wait": {
"label": "आउटपुट की प्रतीक्षा करें",
"help": "यह तब तक रुका रहता है जब तक सर्वर आपके द्वारा बताए गए टेक्स्ट को प्रिंट नहीं कर देता, जैसे पासवर्ड प्रॉम्प्ट।"
},
"delay": {
"label": "विराम",
"help": "अगले चरण से पहले एक निश्चित समय तक प्रतीक्षा करता है।"
},
"if": {
"label": "यदि आउटपुट में शामिल है",
"help": "यह सर्वर द्वारा अब तक प्रिंट की गई हर चीज़ की जाँच करता है, फिर दो शाखाओं में से एक को चलाता है।"
},
"repeat": {
"label": "दोहराना",
"help": "इसके अंदर के चरणों को निर्धारित संख्या में बार-बार दोहराता है।"
}
},
"send": {
"text": "आदेश या पाठ",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "भेजने के बाद एंटर दबाएँ",
"pressEnterHelp": "इसे बंद करने पर आपको टेक्स्ट टाइप करने के बाद उसे सबमिट करने की आवश्यकता नहीं होगी।"
},
"wait": {
"pattern": "आउटपुट में यह जानकारी आने तक प्रतीक्षा करें",
"patternPlaceholder": "पासवर्ड:",
"timeout": "हार मान लो",
"onTimeout": "अगर यह कभी प्रकट नहीं होता",
"stop": "मैक्रो को रोकें",
"continue": "फिर भी आगे बढ़ते रहो।"
},
"delay": {
"duration": "के लिए प्रतीक्षा करें"
},
"ifStep": {
"pattern": "यदि आउटपुट में शामिल है",
"patternPlaceholder": "सक्रिय: सक्रिय (चल रहा)",
"then": "फिर ऐसा करें",
"else": "अन्यथा ऐसा करें",
"emptyBranch": "कोई कदम नहीं"
},
"repeat": {
"count": "कितनी बार",
"times": "टाइम्स"
},
"match": {
"useRegex": "नियमित अभिव्यक्ति के साथ मिलान करें",
"useRegexHelp": "इसे हटा दें ताकि यह लिखे गए पाठ से बिल्कुल मेल खाए।",
"invalidRegex": "यह एक मान्य रेगुलर एक्सप्रेशन नहीं है, इसे सादे टेक्स्ट के रूप में ही माना जाएगा।"
},
"seconds": "सेकंड",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Oldalsáv beállításai...",
"hosts": "Házigazdák",
"snippets": "Kódrészletek",
"macros": "Makrók",
"hostManager": "Házigazda-kezelő",
"credentials": "Hitelesítő adatok",
"connections": "Kapcsolatok",
@@ -869,7 +868,6 @@
"groupTags": "Címkék és kitűzés",
"groupTunnels": "Alagutak",
"groupJumpHosts": "Ugrásszervezők",
"groupQuickActions": "Gyors műveletek",
"groupFeatureFlags": "Jellemzőjelzők",
"groupAdvanced": "Speciális konfiguráció",
"preview": "Előnézet",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH-kulcsok vagy jelszavak tárolása",
"tmuxMonitor": "Tmux monitor",
"tmuxMonitorDesc": "TMUX munkamenetek figyelése a hosztokon keresztül",
"snippets": "Kódrészletek",
"runSnippetDesc": "Futtatás az aktív terminálfülön",
"noSnippetsFound": "Nem található a(z) \"{{search}} \" keresésnek megfelelő kódrészlet",
"recentActivity": "Legutóbbi tevékenységek",
"serversAndHosts": "Szerverek és hosztok",
"noHostsFound": "Nem található a következőnek megfelelő gazdagép: \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Előző lap",
"hotkeysJumpTab": "Ugrás a lapra"
},
"snippets": {
"title": "Kódrészletek",
"createSnippetTitle": "Kódrészlet létrehozása",
"createSnippetDescription": "Hozzon létre egy új parancsrészletet a gyors végrehajtáshoz",
"nameLabel": "Név",
"namePlaceholder": "pl. Indítsd újra az Nginx-et",
"descriptionLabel": "Leírás",
"descriptionPlaceholder": "Opcionális leírás",
"optional": "Választható",
"folderLabel": "Mappa",
"noFolder": "Nincs mappa (Nincs kategorizálva)",
"commandLabel": "Parancs",
"commandPlaceholder": "Pl.: sudo systemctl indítsa újra az nginx-et",
"typeLabel": "Típus",
"typeCommand": "Parancs",
"typeNote": "Jegyzet",
"noteLabel": "Jegyzet",
"notePlaceholder": "pl. egy újrafelhasználható felszólítás vagy emlékeztető szöveg",
"cancel": "Mégsem",
"createSnippetButton": "Kódrészlet létrehozása",
"createFolderTitle": "Mappa létrehozása",
"createFolderDescription": "Rendezd a kódrészleteket mappákba",
"folderNameLabel": "Mappa neve",
"folderNamePlaceholder": "pl. rendszerparancsok, Docker szkriptek",
"folderColorLabel": "Mappa színe",
"folderIconLabel": "Mappa ikon",
"previewLabel": "Előnézet",
"folderNameFallback": "Mappa neve",
"createFolderButton": "Mappa létrehozása",
"targetTerminals": "Célterminálok",
"selectAll": "Minden",
"selectNone": "Egyik sem",
"noTerminalTabsOpen": "Nincsenek megnyitott terminálfülek",
"searchPlaceholder": "Keresési kódrészletek...",
"newSnippet": "Új részlet",
"newFolder": "Új mappa",
"run": "Fut",
"noSnippetsInFolder": "Nincsenek kódrészletek ebben a mappában",
"uncategorized": "Kategorizálatlan",
"editSnippetTitle": "Kódrészlet szerkesztése",
"editSnippetDescription": "Frissítse ezt a parancsrészletet",
"saveSnippetButton": "Változtatások mentése",
"createSuccess": "A kódrészlet sikeresen létrehozva",
"createFailed": "Nem sikerült létrehozni a kódrészletet",
"updateSuccess": "A részlet sikeresen frissítve",
"updateFailed": "Nem sikerült frissíteni a kódrészletet",
"deleteFailed": "Nem sikerült törölni a részletet",
"folderCreateSuccess": "Mappa sikeresen létrehozva",
"folderCreateFailed": "Nem sikerült létrehozni a mappát",
"editFolderTitle": "Mappa szerkesztése",
"editFolderDescription": "Nevezze át vagy módosítsa a mappa megjelenését",
"saveFolderButton": "Változtatások mentése",
"editFolder": "Mappa szerkesztése",
"deleteFolder": "Mappa törlése",
"folderDeleteSuccess": "A(z) „{{name}}” mappa törölve",
"folderDeleteFailed": "Nem sikerült törölni a mappát",
"folderEditSuccess": "Mappa sikeresen frissítve",
"folderEditFailed": "Nem sikerült frissíteni a mappát",
"confirmRunMessage": "Futtassa a \"{{name}} \" programot?",
"confirmRunButton": "Fut",
"runSuccess": "Futtatta a következőt: \"{{name}}\" {{count}} terminál(ok)ban",
"pasteSuccess": "Beillesztve a(z) \"{{name}}\" elemet a(z) {{count}} terminál(ok)ba",
"pasteToTerminal": "Paszta",
"copySuccess": "A(z) „{{name}}” a vágólapra másolva",
"shareTitle": "Részlet megosztása",
"shareUser": "Felhasználó",
"shareRole": "Szerep",
"selectUser": "Válasszon ki egy felhasználót...",
"selectRole": "Válasszon egy szerepet...",
"shareSuccess": "A részlet sikeresen megosztva",
"shareFailed": "Nem sikerült megosztani a részletet",
"revokeSuccess": "Hozzáférés visszavonva",
"revokeFailed": "Nem sikerült visszavonni a hozzáférést",
"currentAccess": "Jelenlegi hozzáférés",
"shareLoadError": "Nem sikerült betölteni a megosztott adatokat",
"loading": "Terhelés...",
"close": "Közeli",
"reorderFailed": "Nem sikerült menteni a kódrészlet sorrendjét",
"importExport": "Importálás / Exportálás",
"exportBtn": "JSON exportálása",
"importBtn": "JSON importálása",
"exportSuccess": "A kódrészletek exportálása sikeresen megtörtént.",
"exportFailed": "Nem sikerült exportálni a kódrészleteket",
"importTitle": "Kódrészletek importálása",
"importDescription": "Kódrészletek és mappák importálása Termix által exportált JSON-fájlból",
"importDropOrClick": "Húzz ide egy JSON fájlt, vagy kattints a böngészéshez",
"importSelectedFile": "Kiválasztva: {{name}}",
"importOverwrite": "Írja felül a meglévő kódrészleteket ugyanazzal a névvel és mappával",
"importStartBtn": "Importálás",
"importSuccess": "Importálás befejezve: {{snippets}} kódrészlet hozzáadva, {{updated}} frissítve, {{skipped}} kihagyva, {{folders}} mappa hozzáadva",
"importFailed": "Nem sikerült importálni a kódrészleteket",
"importInvalidFile": "Érvénytelen fájl: a várt JSON objektum kódrészletekkel vagy mappatömbökkel",
"targetHostsLabel": "Célállomások",
"targetHostsHint": "Rendeljen hozzá hosztokat a kódrészlet közvetlen, nyitott terminál nélküli futtatásához.",
"noHostsAvailable": "Nincsenek konfigurált gazdagépek",
"clearTargetHosts": "Összes törlése",
"hasTargetHosts": "Célzott hosztokkal rendelkezik",
"runOnTargets": "Célokon fut",
"directRunSuccess": "\"{{name}}\" futtatva {{count}} gazdagépen",
"directRunPartialFail": "„{{name}}” hiba történt egy vagy több gépen",
"executionResultTitle": "Végrehajtás eredménye: {{name}}",
"executionResultDescription": "A kódrészlet minden célállomáson történő futtatásának kimenete.",
"executionSuccess": "Siker",
"executionFailed": "Sikertelen",
"expandAll": "Az összes mappa kibontása",
"collapseAll": "Az összes mappa összecsukása",
"settings": "Kódrészlet beállításai",
"variablesHint": "Változók: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Címke}",
"variablesDialogTitle": "Futtassa a \"{{name}}\"",
"variablesDialogDescription": "Töltse ki a kódrészlet futtatása előtt szükséges értékeket.",
"variablesPreviewLabel": "Előnézet",
"variablesConfirmButton": "Fut"
},
"keybindings": {
"title": "Billentyűparancsok",
"description": "Testreszabhatja a terminál másolási, beillesztési és vezérlési billentyűparancsait, vagy billentyűket köthet szöveg küldéséhez vagy kódrészlet futtatásához.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Kattintson és nyomja meg a billentyűket",
"recording": "Nyomja meg a billentyűket...",
"comboRequiredError": "Először nyomjon meg egy billentyűkombinációt",
"textRequiredError": "Írd be a küldendő szöveget",
"controlCodeRequiredError": "Írjon be egyetlen betűt az ellenőrző kódhoz",
"snippetRequiredError": "Válasszon ki egy részletet",
"conflictWarning": "Ez a kombináció már a {{combo}}-hoz van kötve. Mentés esetén mindkettő aktiválódik.",
"actionLabel": "Akció",
"actionCopy": "Kijelölés másolása",
"actionPaste": "Paszta",
"actionSendControlCode": "Ctrl+betűjel küldése",
"actionSendText": "SMS küldése",
"actionRunSnippet": "Meglévő kódrészlet futtatása",
"actionNextTab": "Váltás a következő lapra",
"actionPreviousTab": "Váltás az előző lapra",
"actionOpenCommandPalette": "Gyorskeresés megnyitása",
"controlCodeLabel": "Levél",
"textLabel": "Küldendő szöveg",
"appendEnterLabel": "Küldés után nyomja meg az Enter billentyűt",
"snippetLabel": "Töredék",
"selectSnippetPlaceholder": "Válasszon ki egy részletet",
"orphanedSnippetWarning": "részlet nem található",
"clipboardPermissionNote": "Egyes böngészőkben első használatkor vágólap-engedélyt kérhet."
"actionOpenCommandPalette": "Gyorskeresés megnyitása"
},
"userProfile": {
"donateTitle": "Tartsd életben a Termixet",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "A téma megadása kötelező",
"ntfyToken": "Hozzáférési token (opcionális)",
"channelSaveFailed": "Nem sikerült menteni a csatornát"
},
"macros": {
"title": "Makrók",
"description": "Futtass egy mentett parancssorozatot egy terminálban, és reagálj a szerver által küldöttekre.",
"target": "Cél",
"noTerminal": "Nincs nyitva terminál",
"empty": "Még nincsenek makrók",
"emptyHint": "Hozz létre egy makrót egy terminálfolyamat automatizálásához, például bejelentkezéshez, szolgáltatás újraindításához vagy a kérdések megválaszolásához.",
"create": "Új makró",
"back": "Vissza a makrókhoz",
"name": "Név",
"namePlaceholder": "Indítsa újra a webszervert",
"nameRequired": "Mentés előtt adjon nevet a makrónak",
"descriptionLabel": "Leírás",
"descriptionPlaceholder": "Mit csinál ez a makró (opcionális)",
"unsaved": "Nem mentett változtatások",
"save": "Megtakarítás",
"saved": "Makró mentve",
"saveFailed": "Nem sikerült menteni a makrót",
"delete": "Töröl",
"deleteTitle": "Törli ezt a makrót?",
"deleteBody": "A(z) „{{name}}” véglegesen törlésre kerül. Ez a művelet nem vonható vissza.",
"deleted": "Makró törölve",
"cancel": "Mégsem",
"run": "Fut",
"stop": "Stop",
"running": "Futás...",
"completed": "Makró kész",
"failed": "Makró sikertelen",
"loadFailed": "Nem sikerült betölteni a makrókat",
"needTerminal": "Először nyiss meg és csatlakoztass egy terminált",
"noSteps": "Ennek a makrónak még nincsenek lépései. Adjon hozzá egyet alább.",
"addStep": "Lépés hozzáadása",
"stepNumber": "{{number}} lépés",
"removeStep": "Lépés eltávolítása",
"confirmRunTitle": "Futtassa ezt a makrót?",
"confirmRunBody": "A „{{name}}” parancsokat küld a {{host}} címre.",
"confirmRunAction": "Makró futtatása",
"stepTypes": {
"send": {
"label": "Parancs küldése",
"help": "Beírja a szöveget a terminálba, ugyanúgy, mintha beírta volna."
},
"wait": {
"label": "Várjon a kimenetre",
"help": "Szünetet tart, amíg a szerver ki nem írja a megnevezett szöveget, például egy jelszókérést."
},
"delay": {
"label": "Szünet",
"help": "Egy meghatározott ideig vár a következő lépés előtt."
},
"if": {
"label": "Ha a kimenet tartalmaz",
"help": "Ellenőrzi a szerver által eddig kinyomtatott összes adatot, majd lefuttatja a két ág egyikét."
},
"repeat": {
"label": "Ismétlés",
"help": "Meghatározott számú alkalommal futtatja le a benne található lépéseket."
}
},
"send": {
"text": "Parancs vagy szöveg",
"textPlaceholder": "systemctl újraindítja az nginx-et",
"pressEnter": "Küldés után nyomja meg az Enter billentyűt",
"pressEnterHelp": "Kapcsold ki ezt, ha beküldés nélkül szeretnél szöveget beírni."
},
"wait": {
"pattern": "Várjon, amíg a kimenet tartalmazza a",
"patternPlaceholder": "jelszó:",
"timeout": "Add fel utána",
"onTimeout": "Ha soha nem jelenik meg",
"stop": "A makró leállítása",
"continue": "Folytasd mindenképpen"
},
"delay": {
"duration": "Várj"
},
"ifStep": {
"pattern": "Ha a kimenet tartalmaz",
"patternPlaceholder": "Aktív: aktív (fut)",
"then": "Akkor csináld ezt",
"else": "Egyébként tedd ezt",
"emptyBranch": "Nincsenek lépcsők"
},
"repeat": {
"count": "Alkalmak száma",
"times": "alkalommal"
},
"match": {
"useRegex": "Egyezés reguláris kifejezéssel",
"useRegexHelp": "Hagyd ezt ki, hogy a szöveg pontosan úgy illeszkedjen, ahogyan le van írva.",
"invalidRegex": "Nem érvényes reguláris kifejezés, sima szövegként lesz egyezve."
},
"seconds": "másodperc",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Pengaturan Sidebar...",
"hosts": "Tuan rumah",
"snippets": "Cuplikan",
"macros": "Makro",
"hostManager": "Manajer Host",
"credentials": "Kredensial",
"connections": "Koneksi",
@@ -869,7 +868,6 @@
"groupTags": "Label & pin",
"groupTunnels": "Terowongan",
"groupJumpHosts": "Pembawa acara Jump",
"groupQuickActions": "Tindakan cepat",
"groupFeatureFlags": "Bendera fitur",
"groupAdvanced": "Konfigurasi lanjutan",
"preview": "Pratinjau",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Simpan kunci atau kata sandi SSH",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Pantau sesi tmux di seluruh host Anda.",
"snippets": "Cuplikan",
"runSnippetDesc": "Jalankan di tab terminal aktif",
"noSnippetsFound": "Tidak ditemukan cuplikan yang cocok dengan \"{{search}}\"",
"recentActivity": "Aktivitas Terkini",
"serversAndHosts": "Server & Host",
"noHostsFound": "Tidak ditemukan host yang cocok dengan \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Tab sebelumnya",
"hotkeysJumpTab": "Lompat ke tab"
},
"snippets": {
"title": "Cuplikan",
"createSnippetTitle": "Buat Cuplikan",
"createSnippetDescription": "Buat cuplikan perintah baru untuk eksekusi cepat.",
"nameLabel": "Nama",
"namePlaceholder": "misalnya, Mulai ulang Nginx",
"descriptionLabel": "Keterangan",
"descriptionPlaceholder": "Deskripsi opsional",
"optional": "Opsional",
"folderLabel": "Map",
"noFolder": "Tidak ada folder (Tidak terkategorikan)",
"commandLabel": "Memerintah",
"commandPlaceholder": "misalnya, sudo systemctl restart nginx",
"typeLabel": "Jenis",
"typeCommand": "Memerintah",
"typeNote": "Catatan",
"noteLabel": "Catatan",
"notePlaceholder": "misalnya, teks pengingat atau perintah yang dapat digunakan kembali",
"cancel": "Membatalkan",
"createSnippetButton": "Buat Cuplikan",
"createFolderTitle": "Buat Folder",
"createFolderDescription": "Susun cuplikan Anda ke dalam folder.",
"folderNameLabel": "Nama Folder",
"folderNamePlaceholder": "misalnya, Perintah Sistem, Skrip Docker",
"folderColorLabel": "Warna Folder",
"folderIconLabel": "Ikon Folder",
"previewLabel": "Pratinjau",
"folderNameFallback": "Nama Folder",
"createFolderButton": "Buat Folder",
"targetTerminals": "Terminal Target",
"selectAll": "Semua",
"selectNone": "Tidak ada",
"noTerminalTabsOpen": "Tidak ada tab terminal yang terbuka.",
"searchPlaceholder": "Cuplikan pencarian...",
"newSnippet": "Cuplikan Baru",
"newFolder": "Folder Baru",
"run": "Berlari",
"noSnippetsInFolder": "Tidak ada cuplikan di folder ini",
"uncategorized": "Tidak dikategorikan",
"editSnippetTitle": "Edit Cuplikan",
"editSnippetDescription": "Perbarui cuplikan perintah ini",
"saveSnippetButton": "Simpan Perubahan",
"createSuccess": "Cuplikan berhasil dibuat.",
"createFailed": "Gagal membuat cuplikan",
"updateSuccess": "Cuplikan berhasil diperbarui.",
"updateFailed": "Gagal memperbarui cuplikan",
"deleteFailed": "Gagal menghapus cuplikan",
"folderCreateSuccess": "Folder berhasil dibuat.",
"folderCreateFailed": "Gagal membuat folder",
"editFolderTitle": "Edit Folder",
"editFolderDescription": "Ganti nama atau ubah tampilan folder ini",
"saveFolderButton": "Simpan Perubahan",
"editFolder": "Edit folder",
"deleteFolder": "Hapus folder",
"folderDeleteSuccess": "Folder \"{{name}}\" dihapus",
"folderDeleteFailed": "Gagal menghapus folder",
"folderEditSuccess": "Folder berhasil diperbarui.",
"folderEditFailed": "Gagal memperbarui folder",
"confirmRunMessage": "Jalankan \"{{name}}\"?",
"confirmRunButton": "Berlari",
"runSuccess": "Menjalankan \"{{name}}\" di {{count}} terminal",
"pasteSuccess": "\"{{name}}\" ditempelkan ke terminal {{count}}",
"pasteToTerminal": "Pasta",
"copySuccess": "\"{{name}}\" disalin ke papan klip",
"shareTitle": "Bagikan Cuplikan",
"shareUser": "Pengguna",
"shareRole": "Peran",
"selectUser": "Pilih pengguna...",
"selectRole": "Pilih peran...",
"shareSuccess": "Cuplikan berhasil dibagikan",
"shareFailed": "Cuplikan gagal dibagikan",
"revokeSuccess": "Akses dicabut",
"revokeFailed": "Gagal mencabut akses",
"currentAccess": "Akses Saat Ini",
"shareLoadError": "Gagal memuat data berbagi.",
"loading": "Memuat...",
"close": "Menutup",
"reorderFailed": "Gagal menyimpan urutan cuplikan",
"importExport": "Impor / Ekspor",
"exportBtn": "Ekspor JSON",
"importBtn": "Impor JSON",
"exportSuccess": "Cuplikan berhasil diekspor.",
"exportFailed": "Gagal mengekspor cuplikan",
"importTitle": "Cuplikan Impor",
"importDescription": "Impor cuplikan dan folder dari file JSON yang diekspor oleh Termix.",
"importDropOrClick": "Seret file JSON di sini atau klik untuk menelusuri.",
"importSelectedFile": "Dipilih: {{name}}",
"importOverwrite": "Timpa cuplikan yang sudah ada dengan nama dan folder yang sama.",
"importStartBtn": "Impor",
"importSuccess": "Impor selesai: {{snippets}} cuplikan ditambahkan, {{updated}} diperbarui, {{skipped}} dilewati, {{folders}} folder ditambahkan",
"importFailed": "Gagal mengimpor cuplikan kode.",
"importInvalidFile": "Berkas tidak valid: diharapkan objek JSON dengan cuplikan atau array folder.",
"targetHostsLabel": "Host Target",
"targetHostsHint": "Tetapkan host untuk menjalankan cuplikan kode ini secara langsung tanpa terminal yang terbuka.",
"noHostsAvailable": "Tidak ada host yang dikonfigurasi.",
"clearTargetHosts": "Hapus semua",
"hasTargetHosts": "Memiliki host target",
"runOnTargets": "Lari ke Target",
"directRunSuccess": "Menjalankan \"{{name}}\" pada {{count}} host",
"directRunPartialFail": "\"{{name}}\" gagal pada satu atau lebih host",
"executionResultTitle": "Hasil Eksekusi: {{name}}",
"executionResultDescription": "Hasil dari menjalankan cuplikan kode pada setiap host target.",
"executionSuccess": "Kesuksesan",
"executionFailed": "Gagal",
"expandAll": "Buka semua folder",
"collapseAll": "Tutup semua folder",
"settings": "Pengaturan cuplikan",
"variablesHint": "Variabel: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Jalankan \"{{name}}\"",
"variablesDialogDescription": "Isi nilai-nilai yang dibutuhkan cuplikan kode ini sebelum menjalankannya.",
"variablesPreviewLabel": "Pratinjau",
"variablesConfirmButton": "Berlari"
},
"keybindings": {
"title": "Pintasan Keyboard",
"description": "Sesuaikan pintasan salin, tempel, dan kontrol terminal, atau tetapkan tombol untuk mengirim teks atau menjalankan cuplikan kode.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klik dan tekan tombol",
"recording": "Tekan tombol...",
"comboRequiredError": "Tekan kombinasi tombol terlebih dahulu.",
"textRequiredError": "Masukkan teks yang ingin dikirim",
"controlCodeRequiredError": "Masukkan satu huruf untuk kode kontrol.",
"snippetRequiredError": "Pilih cuplikan",
"conflictWarning": "Kombinasi ini sudah terikat ke {{combo}}. Menyimpan akan membuat keduanya menyala.",
"actionLabel": "Tindakan",
"actionCopy": "Salin pilihan",
"actionPaste": "Pasta",
"actionSendControlCode": "Kirim sinyal Ctrl+huruf",
"actionSendText": "Kirim pesan teks",
"actionRunSnippet": "Jalankan cuplikan kode yang sudah ada.",
"actionNextTab": "Beralih ke tab berikutnya",
"actionPreviousTab": "Beralih ke tab sebelumnya",
"actionOpenCommandPalette": "Buka pencarian cepat",
"controlCodeLabel": "Surat",
"textLabel": "Teks untuk dikirim",
"appendEnterLabel": "Tekan Enter setelah mengirim",
"snippetLabel": "Potongan",
"selectSnippetPlaceholder": "Pilih cuplikan",
"orphanedSnippetWarning": "cuplikan tidak ditemukan",
"clipboardPermissionNote": "Pada beberapa browser, mungkin akan meminta izin akses clipboard saat pertama kali digunakan."
"actionOpenCommandPalette": "Buka pencarian cepat"
},
"userProfile": {
"donateTitle": "Jaga agar Termix tetap hidup.",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Topik wajib diisi",
"ntfyToken": "Token Akses (opsional)",
"channelSaveFailed": "Gagal menyimpan saluran"
},
"macros": {
"title": "Makro",
"description": "Jalankan serangkaian perintah yang tersimpan di terminal, dan tanggapi apa yang dikirimkan kembali oleh server.",
"target": "Target",
"noTerminal": "Tidak ada terminal yang terbuka",
"empty": "Belum ada makro",
"emptyHint": "Buat makro untuk mengotomatiskan alur terminal, seperti masuk, memulai ulang layanan, atau menjawab perintah.",
"create": "Makro baru",
"back": "Kembali ke makro",
"name": "Nama",
"namePlaceholder": "Mulai ulang server web",
"nameRequired": "Beri nama makro sebelum menyimpannya.",
"descriptionLabel": "Keterangan",
"descriptionPlaceholder": "Fungsi makro ini (opsional)",
"unsaved": "Perubahan yang belum disimpan",
"save": "Menyimpan",
"saved": "Makro tersimpan",
"saveFailed": "Makro tidak dapat disimpan.",
"delete": "Menghapus",
"deleteTitle": "Hapus makro ini?",
"deleteBody": "\"{{name}}\" akan dihapus secara permanen. Ini tidak dapat dibatalkan.",
"deleted": "Makro dihapus",
"cancel": "Membatalkan",
"run": "Berlari",
"stop": "Berhenti",
"running": "Berlari...",
"completed": "Makro selesai",
"failed": "Makro gagal",
"loadFailed": "Makro tidak dapat dimuat",
"needTerminal": "Buka dan sambungkan terminal terlebih dahulu.",
"noSteps": "Makro ini belum memiliki langkah. Tambahkan langkah di bawah ini.",
"addStep": "Tambahkan langkah",
"stepNumber": "Langkah {{number}}",
"removeStep": "Hapus langkah",
"confirmRunTitle": "Jalankan makro ini?",
"confirmRunBody": "\"{{name}}\" akan mengirimkan perintah ke {{host}}.",
"confirmRunAction": "Jalankan makro",
"stepTypes": {
"send": {
"label": "Kirim perintah",
"help": "Mengetik teks ke terminal, sama seperti jika Anda mengetiknya."
},
"wait": {
"label": "Tunggu hingga muncul output.",
"help": "Akan berhenti sejenak hingga server menampilkan teks yang Anda sebutkan, seperti saat meminta kata sandi."
},
"delay": {
"label": "Berhenti sebentar",
"help": "Menunggu dalam jangka waktu tertentu sebelum melanjutkan ke langkah berikutnya."
},
"if": {
"label": "Jika output berisi",
"help": "Memeriksa semua yang telah dicetak server sejauh ini, lalu menjalankan salah satu dari dua cabang."
},
"repeat": {
"label": "Mengulang",
"help": "Menjalankan langkah-langkah di dalamnya sebanyak jumlah kali yang telah ditentukan."
}
},
"send": {
"text": "Perintah atau teks",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Tekan Enter setelah mengirim",
"pressEnterHelp": "Matikan opsi ini untuk mengetik teks tanpa mengirimkannya."
},
"wait": {
"pattern": "Tunggu hingga output berisi",
"patternPlaceholder": "kata sandi:",
"timeout": "Menyerah setelah",
"onTimeout": "Jika itu tidak pernah muncul",
"stop": "Hentikan makro",
"continue": "Tetaplah melanjutkan"
},
"delay": {
"duration": "Tunggu"
},
"ifStep": {
"pattern": "Jika outputnya berisi",
"patternPlaceholder": "Aktif: aktif (berjalan)",
"then": "Kemudian lakukan ini",
"else": "Jika tidak, lakukan ini",
"emptyBranch": "Tidak ada tangga"
},
"repeat": {
"count": "Jumlah kali",
"times": "waktu"
},
"match": {
"useRegex": "Cocokkan dengan ekspresi reguler",
"useRegexHelp": "Hilangkan bagian ini agar sesuai persis dengan teks yang tertulis.",
"invalidRegex": "Bukan ekspresi reguler yang valid, akan dicocokkan sebagai teks biasa."
},
"seconds": "detik",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Impostazioni della barra laterale...",
"hosts": "Host",
"snippets": "Frammenti",
"macros": "Macro",
"hostManager": "Gestione host",
"credentials": "Credenziali",
"connections": "Connessioni",
@@ -869,7 +868,6 @@
"groupTags": "Tag e pin",
"groupTunnels": "Gallerie",
"groupJumpHosts": "Ospiti di Jump",
"groupQuickActions": "Azioni rapide",
"groupFeatureFlags": "Flag di funzionalità",
"groupAdvanced": "Configurazione avanzata",
"preview": "Anteprima",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Archivia chiavi SSH o password",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Monitora le sessioni tmux sui tuoi host",
"snippets": "Frammenti",
"runSnippetDesc": "Esegui nella scheda del terminale attiva",
"noSnippetsFound": "Nessun frammento trovato corrispondente a \"{{search}}\"",
"recentActivity": "Attività recenti",
"serversAndHosts": "Server e host",
"noHostsFound": "Nessun host trovato corrispondente a \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Scheda precedente",
"hotkeysJumpTab": "Vai alla scheda"
},
"snippets": {
"title": "Snippet",
"createSnippetTitle": "Crea snippet",
"createSnippetDescription": "Crea un nuovo snippet di comando per un'esecuzione rapida",
"nameLabel": "Nome",
"namePlaceholder": "es. Riavvia Nginx",
"descriptionLabel": "Descrizione",
"descriptionPlaceholder": "Descrizione opzionale",
"optional": "Opzionale",
"folderLabel": "Cartella",
"noFolder": "Nessuna cartella (Senza categoria)",
"commandLabel": "Comando",
"commandPlaceholder": "es. sudo systemctl restart nginx",
"typeLabel": "Tipo",
"typeCommand": "Comando",
"typeNote": "Nota",
"noteLabel": "Nota",
"notePlaceholder": "ad esempio, un messaggio di promemoria o un avviso riutilizzabile",
"cancel": "Annulla",
"createSnippetButton": "Crea snippet",
"createFolderTitle": "Crea cartella",
"createFolderDescription": "Organizza i tuoi snippet in cartelle",
"folderNameLabel": "Nome cartella",
"folderNamePlaceholder": "es. Comandi di sistema, Script Docker",
"folderColorLabel": "Colore cartella",
"folderIconLabel": "Icona cartella",
"previewLabel": "Anteprima",
"folderNameFallback": "Nome cartella",
"createFolderButton": "Crea cartella",
"targetTerminals": "Terminali di destinazione",
"selectAll": "Tutti",
"selectNone": "Nessuno",
"noTerminalTabsOpen": "Nessuna scheda terminale aperta",
"searchPlaceholder": "Cerca snippet...",
"newSnippet": "Nuovo snippet",
"newFolder": "Nuova cartella",
"run": "Esegui",
"noSnippetsInFolder": "Nessuno snippet in questa cartella",
"uncategorized": "Senza categoria",
"editSnippetTitle": "Modifica snippet",
"editSnippetDescription": "Aggiorna questo snippet di comando",
"saveSnippetButton": "Salva modifiche",
"createSuccess": "Snippet creato con successo",
"createFailed": "Impossibile creare lo snippet",
"updateSuccess": "Snippet aggiornato con successo",
"updateFailed": "Impossibile aggiornare lo snippet",
"deleteFailed": "Impossibile eliminare lo snippet",
"folderCreateSuccess": "Cartella creata con successo",
"folderCreateFailed": "Impossibile creare la cartella",
"editFolderTitle": "Modifica cartella",
"editFolderDescription": "Rinomina o modifica l'aspetto di questa cartella",
"saveFolderButton": "Salva modifiche",
"editFolder": "Modifica cartella",
"deleteFolder": "Elimina cartella",
"folderDeleteSuccess": "Cartella \"{{name}}\" eliminata",
"folderDeleteFailed": "Impossibile eliminare la cartella",
"folderEditSuccess": "Cartella aggiornata con successo",
"folderEditFailed": "Impossibile aggiornare la cartella",
"confirmRunMessage": "Eseguire \"{{name}}\"?",
"confirmRunButton": "Esegui",
"runSuccess": "Eseguito \"{{name}}\" su {{count}} terminali",
"pasteSuccess": "Incollato \"{{name}}\" nel/nei terminale/i {{count}}",
"pasteToTerminal": "Impasto",
"copySuccess": "Copiato \"{{name}}\" negli appunti",
"shareTitle": "Condividi snippet",
"shareUser": "Utente",
"shareRole": "Ruolo",
"selectUser": "Seleziona un utente...",
"selectRole": "Seleziona un ruolo...",
"shareSuccess": "Snippet condiviso con successo",
"shareFailed": "Condivisione snippet non riuscita",
"revokeSuccess": "Accesso revocato",
"revokeFailed": "Revoca accesso non riuscita",
"currentAccess": "Accesso corrente",
"shareLoadError": "Caricamento dati di condivisione non riuscito",
"loading": "Caricamento...",
"close": "Chiudi",
"reorderFailed": "Salvataggio ordine snippet non riuscito",
"importExport": "Importa / Esporta",
"exportBtn": "Esporta JSON",
"importBtn": "Importa JSON",
"exportSuccess": "Snippet esportati con successo",
"exportFailed": "Esportazione snippet non riuscita",
"importTitle": "Importa snippet",
"importDescription": "Importa snippet e cartelle da un file JSON esportato da Termix",
"importDropOrClick": "Trascina qui un file JSON o clicca per cercare",
"importSelectedFile": "Selezionato: {{name}}",
"importOverwrite": "Sovrascrivi snippet esistenti con stesso nome e cartella",
"importStartBtn": "Importa",
"importSuccess": "Importazione completata: {{snippets}} snippet aggiunti, {{updated}} aggiornati, {{skipped}} saltati, {{folders}} cartelle aggiunte",
"importFailed": "Importazione snippet non riuscita",
"importInvalidFile": "File non valido: previsto un oggetto JSON con array di snippet o cartelle",
"targetHostsLabel": "Host di destinazione",
"targetHostsHint": "Assegna host per eseguire questo snippet direttamente senza un terminale aperto.",
"noHostsAvailable": "Nessun host configurato",
"clearTargetHosts": "Cancella tutto",
"hasTargetHosts": "Ha host di destinazione",
"runOnTargets": "Esegui sugli host",
"directRunSuccess": "Eseguito \"{{name}}\" su {{count}} host",
"directRunPartialFail": "\"{{name}}\" non riuscito su uno o più host",
"executionResultTitle": "Risultati esecuzione: {{name}}",
"executionResultDescription": "Output dell'esecuzione dello snippet su ciascun host di destinazione.",
"executionSuccess": "Successo",
"executionFailed": "Fallito",
"expandAll": "Espandi tutte le cartelle",
"collapseAll": "Comprimi tutte le cartelle",
"settings": "Impostazioni dello snippet",
"variablesHint": "Variabili: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Etichetta}",
"variablesDialogTitle": "Esegui \"{{name}}\"",
"variablesDialogDescription": "Inserisci i valori richiesti da questo frammento di codice prima di eseguirlo.",
"variablesPreviewLabel": "Anteprima",
"variablesConfirmButton": "Correre"
},
"keybindings": {
"title": "Scorciatoie da tastiera",
"description": "Personalizza le scorciatoie da tastiera per copiare, incollare e controllare il terminale, oppure associa dei tasti per inviare testo o eseguire un frammento di codice.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Fare clic e premere i tasti",
"recording": "Premere i tasti...",
"comboRequiredError": "Premere prima una combinazione di tasti",
"textRequiredError": "Inserisci il testo da inviare",
"controlCodeRequiredError": "Inserisci una sola lettera come codice di controllo",
"snippetRequiredError": "Seleziona un frammento",
"conflictWarning": "Questa combinazione è già associata a {{combo}}. Il salvataggio farà scattare entrambi.",
"actionLabel": "Azione",
"actionCopy": "Copia selezione",
"actionPaste": "Impasto",
"actionSendControlCode": "Invia il segnale Ctrl+lettera",
"actionSendText": "Invia testo",
"actionRunSnippet": "Esegui il frammento di codice esistente",
"actionNextTab": "Passa alla scheda successiva",
"actionPreviousTab": "Passa alla scheda precedente",
"actionOpenCommandPalette": "Apri la ricerca rapida",
"controlCodeLabel": "Lettera",
"textLabel": "Testo da inviare",
"appendEnterLabel": "Premi Invio dopo aver inviato",
"snippetLabel": "Frammento",
"selectSnippetPlaceholder": "Seleziona un frammento",
"orphanedSnippetWarning": "frammento non trovato",
"clipboardPermissionNote": "In alcuni browser potrebbe essere richiesta l'autorizzazione per l'apposizione degli appunti al primo utilizzo."
"actionOpenCommandPalette": "Apri la ricerca rapida"
},
"userProfile": {
"donateTitle": "Mantieni Termix vivo",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Argomento obbligatorio",
"ntfyToken": "Token di accesso (facoltativo)",
"channelSaveFailed": "Salvataggio del canale non riuscito"
},
"macros": {
"title": "Macro",
"description": "Esegui una serie di comandi salvati in un terminale e reagisci a ciò che il server ti invia in risposta.",
"target": "Bersaglio",
"noTerminal": "Nessun terminale aperto",
"empty": "Ancora nessuna macro",
"emptyHint": "Crea una macro per automatizzare un flusso di lavoro da terminale, come l'accesso, il riavvio di un servizio o la risposta a richieste.",
"create": "Nuova macro",
"back": "Torna alle macro",
"name": "Nome",
"namePlaceholder": "Riavvia il server web",
"nameRequired": "Assegna un nome alla macro prima di salvarla.",
"descriptionLabel": "Descrizione",
"descriptionPlaceholder": "Cosa fa questa macro (facoltativo)",
"unsaved": "Modifiche non salvate",
"save": "Salva",
"saved": "Macro salvata",
"saveFailed": "Impossibile salvare la macro",
"delete": "Eliminare",
"deleteTitle": "Eliminare questa macro?",
"deleteBody": "\"{{name}}\" verrà eliminato definitivamente. Questa operazione non può essere annullata.",
"deleted": "Macro eliminato",
"cancel": "Cancellare",
"run": "Correre",
"stop": "Fermare",
"running": "Corsa...",
"completed": "Macro terminato",
"failed": "Il fallimento di Macro",
"loadFailed": "Impossibile caricare le macro",
"needTerminal": "Aprire e collegare prima un terminale",
"noSteps": "Questa macro non ha ancora passaggi. Aggiungine uno qui sotto.",
"addStep": "Aggiungi passaggio",
"stepNumber": "Passo {{number}}",
"removeStep": "Rimuovere il passaggio",
"confirmRunTitle": "Eseguire questa macro?",
"confirmRunBody": "\"{{name}}\" invierà comandi a {{host}}.",
"confirmRunAction": "Esegui macro",
"stepTypes": {
"send": {
"label": "inviare comando",
"help": "Digita il testo nel terminale, proprio come se lo avessi digitato tu."
},
"wait": {
"label": "Attendi l'output",
"help": "Si mette in pausa finché il server non stampa il testo che hai specificato, ad esempio una richiesta di password."
},
"delay": {
"label": "Pausa",
"help": "Attende un intervallo di tempo fisso prima di procedere al passo successivo."
},
"if": {
"label": "Se l'output contiene",
"help": "Controlla tutto ciò che il server ha stampato finora, quindi esegue uno dei due rami."
},
"repeat": {
"label": "Ripetere",
"help": "Esegue i passaggi al suo interno un numero prestabilito di volte."
}
},
"send": {
"text": "Comando o testo",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Premi Invio dopo aver inviato",
"pressEnterHelp": "Disattiva questa opzione per digitare del testo senza inviarlo."
},
"wait": {
"pattern": "Attendi che l'output contenga",
"patternPlaceholder": "password:",
"timeout": "Rinunciare dopo",
"onTimeout": "Se non appare mai",
"stop": "Interrompi la macro",
"continue": "Continua comunque"
},
"delay": {
"duration": "Attendi"
},
"ifStep": {
"pattern": "Se l'output contiene",
"patternPlaceholder": "Attivo: attivo (in corsa)",
"then": "Poi fai questo",
"else": "Altrimenti fai così",
"emptyBranch": "Nessun gradino"
},
"repeat": {
"count": "Numero di volte",
"times": "volte"
},
"match": {
"useRegex": "Corrispondenza con un'espressione regolare",
"useRegexHelp": "Lasciare vuoto questo elemento per far sì che il testo corrisponda esattamente a come è stato scritto.",
"invalidRegex": "Non è un'espressione regolare valida, verrà interpretata come testo semplice."
},
"seconds": "secondi",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "サイドバーの設定...",
"hosts": "ホスト",
"snippets": "スニペット",
"macros": "マクロ",
"hostManager": "ホストマネージャー",
"credentials": "認証情報",
"connections": "接続",
@@ -869,7 +868,6 @@
"groupTags": "タグとピン",
"groupTunnels": "トンネル",
"groupJumpHosts": "ジャンプホスト",
"groupQuickActions": "クイックアクション",
"groupFeatureFlags": "機能フラグ",
"groupAdvanced": "詳細設定",
"preview": "プレビュー",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH鍵やパスワードを保存",
"tmuxMonitor": "Tmux モニター",
"tmuxMonitorDesc": "ホスト間の tmux セッションを監視",
"snippets": "断片",
"runSnippetDesc": "アクティブなターミナルタブで実行します",
"noSnippetsFound": "「{{search}}」に一致するスニペットは見つかりませんでした",
"recentActivity": "最近のアクティビティ",
"serversAndHosts": "サーバー & ホスト",
"noHostsFound": "\"{{search}}\" に一致するホストは見つかりませんでした",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "前のタブ",
"hotkeysJumpTab": "タブにジャンプ"
},
"snippets": {
"title": "スニペット",
"createSnippetTitle": "スニペットを作成",
"createSnippetDescription": "クイック実行用の新しいコマンドスニペットを作成します",
"nameLabel": "名前",
"namePlaceholder": "例: Nginxの再起動",
"descriptionLabel": "説明",
"descriptionPlaceholder": "任意の説明",
"optional": "任意",
"folderLabel": "フォルダ",
"noFolder": "フォルダなし (未分類)",
"commandLabel": "コマンド",
"commandPlaceholder": "例: sudo systemctl restart nginx",
"typeLabel": "タイプ",
"typeCommand": "指示",
"typeNote": "注記",
"noteLabel": "注記",
"notePlaceholder": "例えば、再利用可能なプロンプトまたはリマインダーテキスト",
"cancel": "キャンセル",
"createSnippetButton": "スニペットを作成",
"createFolderTitle": "フォルダを作成",
"createFolderDescription": "スニペットをフォルダに整理します",
"folderNameLabel": "フォルダ名",
"folderNamePlaceholder": "例: システムコマンド, Dockerスクリプト",
"folderColorLabel": "フォルダの色",
"folderIconLabel": "フォルダアイコン",
"previewLabel": "プレビュー",
"folderNameFallback": "フォルダ名",
"createFolderButton": "フォルダを作成",
"targetTerminals": "対象端末",
"selectAll": "すべて選択",
"selectNone": "すべて解除",
"noTerminalTabsOpen": "開いている端末タブはありません",
"searchPlaceholder": "スニペットを検索...",
"newSnippet": "新規スニペット",
"newFolder": "新規フォルダ",
"run": "実行",
"noSnippetsInFolder": "このフォルダにスニペットはありません",
"uncategorized": "未分類",
"editSnippetTitle": "スニペットの編集",
"editSnippetDescription": "このコマンドスニペットを更新",
"saveSnippetButton": "変更を保存",
"createSuccess": "スニペットが正常に作成されました",
"createFailed": "スニペットの作成に失敗しました",
"updateSuccess": "スニペットが正常に更新されました",
"updateFailed": "スニペットの更新に失敗しました",
"deleteFailed": "スニペットの削除に失敗しました",
"folderCreateSuccess": "フォルダが正常に作成されました",
"folderCreateFailed": "フォルダの作成に失敗しました",
"editFolderTitle": "フォルダの編集",
"editFolderDescription": "フォルダの名前や外観を変更",
"saveFolderButton": "変更を保存",
"editFolder": "フォルダを編集",
"deleteFolder": "フォルダを削除",
"folderDeleteSuccess": "フォルダ「{{name}}」を削除しました",
"folderDeleteFailed": "フォルダの削除に失敗しました",
"folderEditSuccess": "フォルダが正常に更新されました",
"folderEditFailed": "フォルダの更新に失敗しました",
"confirmRunMessage": "「{{name}}」を実行しますか?",
"confirmRunButton": "実行",
"runSuccess": "{{count}}個の端末で「{{name}}」を実行しました",
"pasteSuccess": "「{{name}}」を {{count}} 端末に貼り付けました",
"pasteToTerminal": "ペースト",
"copySuccess": "「{{name}}」をクリップボードにコピーしました",
"shareTitle": "スニペットを共有",
"shareUser": "ユーザー",
"shareRole": "ロール",
"selectUser": "ユーザーを選択...",
"selectRole": "ロールを選択...",
"shareSuccess": "スニペットが正常に共有されました",
"shareFailed": "スニペットの共有に失敗しました",
"revokeSuccess": "アクセスが取り消されました",
"revokeFailed": "アクセスの取り消しに失敗しました",
"currentAccess": "現在のアクセス権",
"shareLoadError": "共有データの読み込みに失敗しました",
"loading": "読み込み中...",
"close": "閉じる",
"reorderFailed": "スニペットの並び順の保存に失敗しました",
"importExport": "インポート / エクスポート",
"exportBtn": "JSONをエクスポート",
"importBtn": "JSONをインポート",
"exportSuccess": "スニペットが正常にエクスポートされました",
"exportFailed": "スニペットのエクスポートに失敗しました",
"importTitle": "スニペットのインポート",
"importDescription": "Termix がエクスポートした JSON ファイルからスニペットとフォルダーをインポートします",
"importDropOrClick": "JSON ファイルをここにドロップするか、クリックして参照",
"importSelectedFile": "選択中: {{name}}",
"importOverwrite": "同じ名前とフォルダーの既存スニペットを上書きする",
"importStartBtn": "インポート",
"importSuccess": "インポート完了: {{snippets}} 件のスニペット追加、{{updated}} 件更新、{{skipped}} 件スキップ、{{folders}} 件のフォルダー追加",
"importFailed": "スニペットのインポートに失敗しました",
"importInvalidFile": "無効なファイルです: snippets または folders 配列を含む JSON オブジェクトが必要です",
"targetHostsLabel": "ターゲットホスト",
"targetHostsHint": "このスニペットを端末を開かずに直接実行するホストを割り当てます。",
"noHostsAvailable": "設定されたホストがありません",
"clearTargetHosts": "すべてクリア",
"hasTargetHosts": "ターゲットホストあり",
"runOnTargets": "ターゲットで実行",
"directRunSuccess": "\"{{name}}\" を {{count}} 台のホストで実行しました",
"directRunPartialFail": "\"{{name}}\" が1つ以上のホストで失敗しました",
"executionResultTitle": "実行結果: {{name}}",
"executionResultDescription": "各ターゲットホストでスニペットを実行した結果の出力です。",
"executionSuccess": "成功",
"executionFailed": "失敗",
"expandAll": "すべてのフォルダーを展開する",
"collapseAll": "すべてのフォルダーを折りたたむ",
"settings": "スニペット設定",
"variablesHint": "変数: $HOST、 $USER、 $PORT、 $NAME、 ${INPUT_1:Label}",
"variablesDialogTitle": "「{{name}} を実行」",
"variablesDialogDescription": "このコードスニペットを実行する前に、必要な値を入力してください。",
"variablesPreviewLabel": "プレビュー",
"variablesConfirmButton": "走る"
},
"keybindings": {
"title": "キーボードショートカット",
"description": "ターミナルのコピー、ペースト、コントロールのショートカットをカスタマイズしたり、テキストの送信やスニペットの実行にキーを割り当てたりできます。",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "クリックしてキーを押す",
"recording": "キーを押してください...",
"comboRequiredError": "まずキーの組み合わせを押してください",
"textRequiredError": "送信するテキストを入力してください",
"controlCodeRequiredError": "制御コードとして1文字を入力してください",
"snippetRequiredError": "抜粋を選択してください",
"conflictWarning": "この組み合わせは既に {{combo}}にバインドされています。保存すると両方とも発火します。",
"actionLabel": "アクション",
"actionCopy": "選択範囲をコピー",
"actionPaste": "ペースト",
"actionSendControlCode": "Ctrlキーと文字キーを同時に押して信号を送る",
"actionSendText": "テキストを送信",
"actionRunSnippet": "既存のスニペットを実行する",
"actionNextTab": "次のタブに切り替える",
"actionPreviousTab": "前のタブに切り替える",
"actionOpenCommandPalette": "クイック検索を開く",
"controlCodeLabel": "手紙",
"textLabel": "送信するテキスト",
"appendEnterLabel": "送信後にEnterキーを押してください。",
"snippetLabel": "抜粋",
"selectSnippetPlaceholder": "抜粋を選択してください",
"orphanedSnippetWarning": "スニペットが見つかりません",
"clipboardPermissionNote": "一部のブラウザでは、初回使用時にクリップボードのアクセス許可を求められる場合があります。"
"actionOpenCommandPalette": "クイック検索を開く"
},
"userProfile": {
"donateTitle": "Termixを支援する",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "トピックは必須です",
"ntfyToken": "アクセストークン(任意)",
"channelSaveFailed": "チャンネルの保存に失敗しました"
},
"macros": {
"title": "マクロ",
"description": "ターミナルで保存済みのコマンド系列を実行し、サーバーからの応答に応じて処理を行います。",
"target": "ターゲット",
"noTerminal": "端末が開いていません",
"empty": "マクロはまだありません",
"emptyHint": "ログイン、サービスの再起動、プロンプトへの応答など、ターミナル操作の流れを自動化するマクロを作成します。",
"create": "新しいマクロ",
"back": "マクロに戻る",
"name": "名前",
"namePlaceholder": "ウェブサーバーを再起動します",
"nameRequired": "保存する前にマクロに名前を付けてください。",
"descriptionLabel": "説明",
"descriptionPlaceholder": "このマクロの機能(オプション)",
"unsaved": "保存されていない変更",
"save": "保存",
"saved": "マクロが保存されました",
"saveFailed": "マクロを保存できませんでした",
"delete": "消去",
"deleteTitle": "このマクロを削除しますか?",
"deleteBody": "\"{{name}}\" は完全に削除されます。元に戻すことはできません。",
"deleted": "マクロが削除されました",
"cancel": "キャンセル",
"run": "走る",
"stop": "停止",
"running": "実行中...",
"completed": "マクロ撮影終了",
"failed": "マクロが失敗しました",
"loadFailed": "マクロを読み込めませんでした",
"needTerminal": "まずターミナルを開いて接続します。",
"noSteps": "このマクロにはまだ手順がありません。以下に手順を追加してください。",
"addStep": "ステップを追加する",
"stepNumber": "ステップ {{number}}",
"removeStep": "削除手順",
"confirmRunTitle": "このマクロを実行しますか?",
"confirmRunBody": "\"{{name}}\" は {{host}} にコマンドを送信します。",
"confirmRunAction": "マクロを実行する",
"stepTypes": {
"send": {
"label": "コマンドを送信",
"help": "端末にテキストを入力します。まるで手入力したかのように表示されます。"
},
"wait": {
"label": "出力を待つ",
"help": "サーバーが指定したテキスト(パスワード入力画面など)を表示するまで一時停止します。"
},
"delay": {
"label": "一時停止",
"help": "次のステップに進む前に、一定時間待機します。"
},
"if": {
"label": "出力に以下が含まれる場合",
"help": "サーバーがこれまでに出力したすべての内容を確認し、その後、2つの分岐のうちいずれか1つを実行します。"
},
"repeat": {
"label": "繰り返す",
"help": "指定された回数だけ、その中の手順を実行します。"
}
},
"send": {
"text": "コマンドまたはテキスト",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "送信後にEnterキーを押してください。",
"pressEnterHelp": "テキストを送信せずに入力する場合は、これをオフにしてください。"
},
"wait": {
"pattern": "出力に以下の内容が含まれるまで待機してください",
"patternPlaceholder": "パスワード:",
"timeout": "諦めた後",
"onTimeout": "それが決して現れない場合",
"stop": "マクロを停止する",
"continue": "とにかく続けて"
},
"delay": {
"duration": "待って"
},
"ifStep": {
"pattern": "出力に以下が含まれる場合",
"patternPlaceholder": "アクティブ:アクティブ(実行中)",
"then": "次にこれを実行してください",
"else": "そうでなければ、これを実行してください。",
"emptyBranch": "階段なし"
},
"repeat": {
"count": "回数",
"times": "タイムズ"
},
"match": {
"useRegex": "正規表現で一致させる",
"useRegexHelp": "原文と完全に一致させるには、この部分を削除してください。",
"invalidRegex": "有効な正規表現ではないため、プレーンテキストとしてマッチします。"
},
"seconds": "秒",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "사이드바 설정...",
"hosts": "호스트",
"snippets": "스니펫",
"macros": "매크로",
"hostManager": "호스트 관리자",
"credentials": "자격 증명",
"connections": "연결",
@@ -869,7 +868,6 @@
"groupTags": "태그 및 핀",
"groupTunnels": "터널",
"groupJumpHosts": "점프 호스트",
"groupQuickActions": "빠른 조치",
"groupFeatureFlags": "기능 플래그",
"groupAdvanced": "고급 설정",
"preview": "시사",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH 키 또는 비밀번호 저장",
"tmuxMonitor": "Tmux 모니터",
"tmuxMonitorDesc": "호스트 전반의 Tmux 세션 모니터링",
"snippets": "짧은 발췌",
"runSnippetDesc": "현재 활성화된 터미널 탭에서 실행하세요",
"noSnippetsFound": "\"{{search}} \" 와 일치하는 스니펫을 찾을 수 없습니다.",
"recentActivity": "최근 활동",
"serversAndHosts": "서버 및 호스트",
"noHostsFound": "\"{{search}}\"에 일치하는 호스트를 찾을 수 없습니다.",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "이전 탭",
"hotkeysJumpTab": "탭으로 이동"
},
"snippets": {
"title": "스니펫",
"createSnippetTitle": "스니펫 생성",
"createSnippetDescription": "빠른 실행을 위한 새 명령 스니펫을 생성합니다",
"nameLabel": "이름",
"namePlaceholder": "예: Nginx 재시작",
"descriptionLabel": "설명",
"descriptionPlaceholder": "선택 사항 설명",
"optional": "선택 사항",
"folderLabel": "폴더",
"noFolder": "폴더 없음 (미분류)",
"commandLabel": "명령",
"commandPlaceholder": "예: sudo systemctl restart nginx",
"typeLabel": "유형",
"typeCommand": "명령",
"typeNote": "메모",
"noteLabel": "메모",
"notePlaceholder": "예: 재사용 가능한 안내 메시지 또는 알림 텍스트",
"cancel": "취소",
"createSnippetButton": "스니펫 생성",
"createFolderTitle": "폴더 생성",
"createFolderDescription": "스니펫을 폴더로 정리합니다",
"folderNameLabel": "폴더 이름",
"folderNamePlaceholder": "예: 시스템 명령, Docker 스크립트",
"folderColorLabel": "폴더 색상",
"folderIconLabel": "폴더 아이콘",
"previewLabel": "미리보기",
"folderNameFallback": "폴더 이름",
"createFolderButton": "폴더 만들기",
"targetTerminals": "대상 터미널",
"selectAll": "전체",
"selectNone": "없음",
"noTerminalTabsOpen": "열린 터미널 탭 없음",
"searchPlaceholder": "스니펫 검색...",
"newSnippet": "새 스니펫",
"newFolder": "새 폴더",
"run": "실행",
"noSnippetsInFolder": "이 폴더에 스니펫이 없습니다",
"uncategorized": "미분류",
"editSnippetTitle": "스니펫 편집",
"editSnippetDescription": "이 명령 스니펫을 수정합니다",
"saveSnippetButton": "변경사항 저장",
"createSuccess": "스니펫이 생성되었습니다",
"createFailed": "스니펫 생성에 실패했습니다",
"updateSuccess": "스니펫이 업데이트되었습니다",
"updateFailed": "스니펫 업데이트에 실패했습니다",
"deleteFailed": "스니펫 삭제에 실패했습니다",
"folderCreateSuccess": "폴더가 생성되었습니다",
"folderCreateFailed": "폴더 생성에 실패했습니다",
"editFolderTitle": "폴더 편집",
"editFolderDescription": "이 폴더의 이름을 바꾸거나 모양을 변경합니다",
"saveFolderButton": "변경사항 저장",
"editFolder": "폴더 편집",
"deleteFolder": "폴더 삭제",
"folderDeleteSuccess": "폴더 \"{{name}}\"이(가) 삭제되었습니다",
"folderDeleteFailed": "폴더 삭제에 실패했습니다",
"folderEditSuccess": "폴더가 업데이트되었습니다",
"folderEditFailed": "폴더 업데이트에 실패했습니다",
"confirmRunMessage": "\"{{name}}\"을(를) 실행하시겠습니까?",
"confirmRunButton": "실행",
"runSuccess": "\"{{name}}\"을(를) {{count}}개 터미널에서 실행했습니다",
"pasteSuccess": "\"{{name}}\"을 {{count}} 터미널에 붙여넣었습니다.",
"pasteToTerminal": "반죽",
"copySuccess": "\"{{name}}\"을(를) 클립보드에 복사했습니다",
"shareTitle": "스니펫 공유",
"shareUser": "사용자",
"shareRole": "역할",
"selectUser": "사용자 선택...",
"selectRole": "역할 선택...",
"shareSuccess": "스니펫이 성공적으로 공유되었습니다",
"shareFailed": "스니펫 공유 실패",
"revokeSuccess": "접근 권한이 취소되었습니다",
"revokeFailed": "접근 권한 취소 실패",
"currentAccess": "현재 접근 권한",
"shareLoadError": "공유 데이터 불러오기 실패",
"loading": "로딩 중...",
"close": "닫기",
"reorderFailed": "스니펫 순서 저장 실패",
"importExport": "가져오기 / 내보내기",
"exportBtn": "JSON 내보내기",
"importBtn": "JSON 가져오기",
"exportSuccess": "스니펫을 성공적으로 내보냈습니다",
"exportFailed": "스니펫 내보내기 실패",
"importTitle": "스니펫 가져오기",
"importDescription": "Termix에서 내보낸 JSON 파일로부터 스니펫과 폴더를 가져옵니다",
"importDropOrClick": "JSON 파일을 여기에 놓거나 클릭하여 찾아보기",
"importSelectedFile": "선택됨: {{name}}",
"importOverwrite": "같은 이름과 폴더의 기존 스니펫 덮어쓰기",
"importStartBtn": "가져오기",
"importSuccess": "가져오기 완료: 스니펫 {{snippets}}개 추가됨, {{updated}}개 업데이트됨, {{skipped}}개 건너뜀, 폴더 {{folders}}개 추가됨",
"importFailed": "스니펫 가져오기 실패",
"importInvalidFile": "잘못된 파일: 스니펫 또는 폴더 배열을 포함하는 JSON 객체가 필요합니다",
"targetHostsLabel": "대상 호스트",
"targetHostsHint": "열린 터미널 없이 이 스니펫을 직접 실행할 호스트를 할당하세요.",
"noHostsAvailable": "구성된 호스트 없음",
"clearTargetHosts": "모두 지우기",
"hasTargetHosts": "대상 호스트 있음",
"runOnTargets": "대상에서 실행",
"directRunSuccess": "\"{{name}}\"을(를) {{count}}개 호스트에서 실행했습니다",
"directRunPartialFail": "\"{{name}}\"이(가) 하나 이상의 호스트에서 실패했습니다",
"executionResultTitle": "실행 결과: {{name}}",
"executionResultDescription": "각 대상 호스트에서 스니펫을 실행한 출력입니다.",
"executionSuccess": "성공",
"executionFailed": "실패",
"expandAll": "모든 폴더를 펼치세요",
"collapseAll": "모든 폴더 접기",
"settings": "스니펫 설정",
"variablesHint": "변수: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "\"{{name}} \" 실행",
"variablesDialogDescription": "이 코드 조각을 실행하기 전에 필요한 값을 입력하세요.",
"variablesPreviewLabel": "시사",
"variablesConfirmButton": "달리다"
},
"keybindings": {
"title": "키보드 단축키",
"description": "터미널 복사, 붙여넣기 및 제어 단축키를 사용자 지정하거나, 키를 바인딩하여 텍스트를 전송하거나 코드 조각을 실행할 수 있습니다.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "클릭하고 키를 누르세요",
"recording": "키를 누르세요...",
"comboRequiredError": "먼저 키 조합을 누르세요.",
"textRequiredError": "보낼 텍스트를 입력하세요",
"controlCodeRequiredError": "제어 코드에 한 글자를 입력하세요.",
"snippetRequiredError": "코드 조각을 선택하세요",
"conflictWarning": "이 조합은 이미 {{combo}}에 연결되어 있습니다. 저장하면 둘 다 발사됩니다.",
"actionLabel": "행동",
"actionCopy": "선택 복사",
"actionPaste": "반죽",
"actionSendControlCode": "Ctrl+문자 신호를 보내세요",
"actionSendText": "문자 보내기",
"actionRunSnippet": "기존 코드 조각을 실행하세요",
"actionNextTab": "다음 탭으로 이동",
"actionPreviousTab": "이전 탭으로 이동",
"actionOpenCommandPalette": "빠른 검색 열기",
"controlCodeLabel": "편지",
"textLabel": "보낼 텍스트",
"appendEnterLabel": "전송 후 Enter 키를 누르세요.",
"snippetLabel": "단편",
"selectSnippetPlaceholder": "코드 조각을 선택하세요",
"orphanedSnippetWarning": "스니펫을 찾을 수 없습니다.",
"clipboardPermissionNote": "일부 브라우저에서는 처음 사용 시 클립보드 권한을 요청할 수 있습니다."
"actionOpenCommandPalette": "빠른 검색 열기"
},
"userProfile": {
"donateTitle": "Termix 유지 지원",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "토픽은 필수입니다",
"ntfyToken": "액세스 토큰 (선택 사항)",
"channelSaveFailed": "채널 저장에 실패했습니다"
},
"macros": {
"title": "매크로",
"description": "터미널에서 저장된 일련의 명령어를 실행하고, 서버가 보내는 응답에 반응합니다.",
"target": "목표",
"noTerminal": "터미널이 열려 있지 않습니다",
"empty": "아직 매크로가 없습니다.",
"emptyHint": "터미널 로그인, 서비스 재시작 또는 프롬프트 응답과 같은 터미널 작업 흐름을 자동화하는 매크로를 만드세요.",
"create": "새로운 매크로",
"back": "매크로로 돌아가기",
"name": "이름",
"namePlaceholder": "웹 서버를 재시작하세요",
"nameRequired": "매크로를 저장하기 전에 이름을 지정하세요.",
"descriptionLabel": "설명",
"descriptionPlaceholder": "이 매크로의 기능 (선택 사항)",
"unsaved": "저장되지 않은 변경 사항",
"save": "구하다",
"saved": "매크로 저장됨",
"saveFailed": "매크로를 저장할 수 없습니다.",
"delete": "삭제",
"deleteTitle": "이 매크로를 삭제하시겠습니까?",
"deleteBody": "\"{{name}}\"는 영구적으로 삭제됩니다. 이 작업은 되돌릴 수 없습니다.",
"deleted": "매크로 삭제됨",
"cancel": "취소",
"run": "달리다",
"stop": "멈추다",
"running": "달리기...",
"completed": "매크로 완료",
"failed": "매크로 실패",
"loadFailed": "매크로를 불러올 수 없습니다.",
"needTerminal": "먼저 터미널을 열고 연결하세요.",
"noSteps": "이 매크로는 아직 단계가 없습니다. 아래에 단계를 추가하세요.",
"addStep": "단계 추가",
"stepNumber": "단계 {{number}}",
"removeStep": "제거 단계",
"confirmRunTitle": "이 매크로를 실행하시겠습니까?",
"confirmRunBody": "\"{{name}}\"는 {{host}}로 명령을 보냅니다.",
"confirmRunAction": "매크로 실행",
"stepTypes": {
"send": {
"label": "명령을 보내세요",
"help": "터미널에 텍스트를 입력합니다. 마치 직접 타이핑하는 것과 같습니다."
},
"wait": {
"label": "출력이 나올 때까지 기다리세요",
"help": "서버가 사용자가 입력한 텍스트(예: 비밀번호 입력창)를 출력할 때까지 일시 중지됩니다."
},
"delay": {
"label": "정지시키다",
"help": "다음 단계로 넘어가기 전에 일정 시간 동안 기다립니다."
},
"if": {
"label": "출력에 다음 내용이 포함되어 있는 경우",
"help": "서버에서 지금까지 출력한 모든 내용을 확인한 후 두 가지 분기 중 하나를 실행합니다."
},
"repeat": {
"label": "반복하다",
"help": "그 안에 있는 단계들을 정해진 횟수만큼 실행합니다."
}
},
"send": {
"text": "명령 또는 텍스트",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "전송 후 Enter 키를 누르세요.",
"pressEnterHelp": "텍스트를 입력하되 제출하지 않으려면 이 기능을 끄세요."
},
"wait": {
"pattern": "출력 내용이 포함될 때까지 기다리세요",
"patternPlaceholder": "비밀번호:",
"timeout": "포기한 후에",
"onTimeout": "만약 전혀 나타나지 않는다면",
"stop": "매크로를 중지하세요",
"continue": "어쨌든 계속 가세요"
},
"delay": {
"duration": "기다리세요"
},
"ifStep": {
"pattern": "출력에 다음 내용이 포함되어 있는 경우",
"patternPlaceholder": "활성: 활성(실행 중)",
"then": "그럼 이렇게 하세요",
"else": "그렇지 않으면 이렇게 하세요.",
"emptyBranch": "단계 없음"
},
"repeat": {
"count": "횟수",
"times": "타임스"
},
"match": {
"useRegex": "정규 표현식과 일치",
"useRegexHelp": "원문과 정확히 일치하려면 이 부분을 삭제하세요.",
"invalidRegex": "유효한 정규 표현식이 아니므로 일반 텍스트로 처리됩니다."
},
"seconds": "초",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Instellingen zijbalk...",
"hosts": "Gastheren",
"snippets": "Fragmenten",
"macros": "Macro's",
"hostManager": "Hostmanager",
"credentials": "Referenties",
"connections": "Verbindingen",
@@ -869,7 +868,6 @@
"groupTags": "Labels en speld",
"groupTunnels": "Tunnels",
"groupJumpHosts": "Jump-hosts",
"groupQuickActions": "Snelle acties",
"groupFeatureFlags": "Functievlaggen",
"groupAdvanced": "Geavanceerde configuratie",
"preview": "Voorbeeld",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Bewaar SSH-sleutels of -wachtwoorden",
"tmuxMonitor": "Tmux Monitor",
"tmuxMonitorDesc": "Monitor tmux-sessies op al je hosts.",
"snippets": "Fragmenten",
"runSnippetDesc": "Voer dit uit in het actieve terminaltabblad.",
"noSnippetsFound": "Geen fragmenten gevonden die overeenkomen met \"{{search}}\"",
"recentActivity": "Recente activiteit",
"serversAndHosts": "Servers en hostingproviders",
"noHostsFound": "Geen hosts gevonden die overeenkomen met \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Vorig tabblad",
"hotkeysJumpTab": "Ga naar het tabblad"
},
"snippets": {
"title": "Fragmenten",
"createSnippetTitle": "Maak een codefragment aan",
"createSnippetDescription": "Maak een nieuw opdrachtfragment aan voor snelle uitvoering.",
"nameLabel": "Naam",
"namePlaceholder": "bijvoorbeeld: Nginx opnieuw opstarten",
"descriptionLabel": "Beschrijving",
"descriptionPlaceholder": "Optionele beschrijving",
"optional": "Optioneel",
"folderLabel": "Map",
"noFolder": "Geen map (Niet gecategoriseerd)",
"commandLabel": "Commando",
"commandPlaceholder": "bijvoorbeeld: sudo systemctl restart nginx",
"typeLabel": "Type",
"typeCommand": "Commando",
"typeNote": "Opmerking",
"noteLabel": "Opmerking",
"notePlaceholder": "bijvoorbeeld een herbruikbare prompt of herinneringstekst",
"cancel": "Annuleren",
"createSnippetButton": "Maak een codefragment aan",
"createFolderTitle": "Map maken",
"createFolderDescription": "Organiseer je codefragmenten in mappen.",
"folderNameLabel": "Mapnaam",
"folderNamePlaceholder": "bijvoorbeeld systeemopdrachten, Docker-scripts",
"folderColorLabel": "Mapkleur",
"folderIconLabel": "Mappictogram",
"previewLabel": "Voorbeeld",
"folderNameFallback": "Mapnaam",
"createFolderButton": "Map maken",
"targetTerminals": "Doelterminals",
"selectAll": "Alle",
"selectNone": "Geen",
"noTerminalTabsOpen": "Geen terminaltabbladen geopend",
"searchPlaceholder": "Zoekfragmenten...",
"newSnippet": "Nieuw fragment",
"newFolder": "Nieuwe map",
"run": "Loop",
"noSnippetsInFolder": "Geen codefragmenten in deze map",
"uncategorized": "Niet gecategoriseerd",
"editSnippetTitle": "Bewerk fragment",
"editSnippetDescription": "Werk dit opdrachtfragment bij.",
"saveSnippetButton": "Wijzigingen opslaan",
"createSuccess": "Codefragment succesvol aangemaakt",
"createFailed": "Het is niet gelukt om een codefragment te maken.",
"updateSuccess": "Codefragment succesvol bijgewerkt",
"updateFailed": "Het bijwerken van het codefragment is mislukt.",
"deleteFailed": "Het verwijderen van het fragment is mislukt.",
"folderCreateSuccess": "Map succesvol aangemaakt",
"folderCreateFailed": "Map aanmaken mislukt",
"editFolderTitle": "Map bewerken",
"editFolderDescription": "Hernoem of wijzig het uiterlijk van deze map.",
"saveFolderButton": "Wijzigingen opslaan",
"editFolder": "Map bewerken",
"deleteFolder": "Map verwijderen",
"folderDeleteSuccess": "Map \"{{name}}\" verwijderd",
"folderDeleteFailed": "Map verwijderen mislukt",
"folderEditSuccess": "Map succesvol bijgewerkt",
"folderEditFailed": "Het bijwerken van de map is mislukt.",
"confirmRunMessage": "Run \"{{name}}\"?",
"confirmRunButton": "Loop",
"runSuccess": "Voerde \"{{name}}\" uit in {{count}} terminal(s)",
"pasteSuccess": "\"{{name}}\" is geplakt in de terminal(s) {{count}}",
"pasteToTerminal": "Plakken",
"copySuccess": "\"{{name}}\" is naar het klembord gekopieerd",
"shareTitle": "Fragment delen",
"shareUser": "Gebruiker",
"shareRole": "Rol",
"selectUser": "Selecteer een gebruiker...",
"selectRole": "Kies een rol...",
"shareSuccess": "Fragment succesvol gedeeld",
"shareFailed": "Het delen van het fragment is mislukt.",
"revokeSuccess": "Toegang ingetrokken",
"revokeFailed": "Toegang intrekken is mislukt",
"currentAccess": "Huidige toegang",
"shareLoadError": "Het laden van de gedeelde gegevens is mislukt.",
"loading": "Laden...",
"close": "Dichtbij",
"reorderFailed": "Het opslaan van de volgorde van de codefragmenten is mislukt.",
"importExport": "Import/Export",
"exportBtn": "Exporteer JSON",
"importBtn": "JSON importeren",
"exportSuccess": "Codefragmenten succesvol geëxporteerd",
"exportFailed": "Het exporteren van codefragmenten is mislukt.",
"importTitle": "Importeer codefragmenten",
"importDescription": "Importeer codefragmenten en mappen vanuit een JSON-bestand dat is geëxporteerd door Termix.",
"importDropOrClick": "Sleep hier een JSON-bestand naartoe of klik om te bladeren.",
"importSelectedFile": "Geselecteerd: {{name}}",
"importOverwrite": "Overschrijf bestaande codefragmenten met dezelfde naam en map.",
"importStartBtn": "Importeren",
"importSuccess": "Import voltooid: {{snippets}} fragment(en) toegevoegd, {{updated}} bijgewerkt, {{skipped}} overgeslagen, {{folders}} map(pen) toegevoegd",
"importFailed": "Het importeren van codefragmenten is mislukt.",
"importInvalidFile": "Ongeldig bestand: er werd een JSON-object verwacht met snippets of mappenarrays.",
"targetHostsLabel": "Doelhosts",
"targetHostsHint": "Wijs hosts toe om dit codefragment direct uit te voeren zonder een geopende terminal.",
"noHostsAvailable": "Geen hosts geconfigureerd",
"clearTargetHosts": "Alles wissen",
"hasTargetHosts": "Heeft doelhosts",
"runOnTargets": "Uitvoeren op doelen",
"directRunSuccess": "Voerde \"{{name}}\" uit op {{count}} host(s)",
"directRunPartialFail": "\"{{name}}\" is mislukt op een of meer hosts",
"executionResultTitle": "Uitvoeringsresultaten: {{name}}",
"executionResultDescription": "Uitvoer van het uitvoeren van het codefragment op elke doelhost.",
"executionSuccess": "Succes",
"executionFailed": "Mislukt",
"expandAll": "Vouw alle mappen uit",
"collapseAll": "Alle mappen inklappen",
"settings": "Snippet-instellingen",
"variablesHint": "Variabelen: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Uitvoeren \"{{name}}\"",
"variablesDialogDescription": "Vul de waarden in die dit codefragment nodig heeft voordat je het uitvoert.",
"variablesPreviewLabel": "Voorbeeld",
"variablesConfirmButton": "Loop"
},
"keybindings": {
"title": "Toetsenbord sneltoetsen",
"description": "Pas de sneltoetsen voor kopiëren, plakken en besturing in de terminal aan, of koppel toetsen aan het verzenden van tekst of het uitvoeren van een codefragment.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klik en druk op de toetsen",
"recording": "Druk op de toetsen...",
"comboRequiredError": "Druk eerst op een toetsencombinatie.",
"textRequiredError": "Voer de tekst in die u wilt verzenden",
"controlCodeRequiredError": "Voer één letter in als besturingscode.",
"snippetRequiredError": "Selecteer een fragment",
"conflictWarning": "Deze combinatie is al gekoppeld aan {{combo}}. Door op te slaan zullen beide vuren.",
"actionLabel": "Actie",
"actionCopy": "Selectie kopiëren",
"actionPaste": "Plakken",
"actionSendControlCode": "Verzend het Ctrl+letter-signaal",
"actionSendText": "Sms verzenden",
"actionRunSnippet": "Voer het bestaande codefragment uit.",
"actionNextTab": "Ga naar het volgende tabblad",
"actionPreviousTab": "Ga terug naar het vorige tabblad",
"actionOpenCommandPalette": "Snel zoeken openen",
"controlCodeLabel": "Brief",
"textLabel": "Tekst om te verzenden",
"appendEnterLabel": "Druk op Enter na het verzenden.",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Selecteer een fragment",
"orphanedSnippetWarning": "fragment niet gevonden",
"clipboardPermissionNote": "In sommige browsers kan bij het eerste gebruik om toestemming voor het gebruik van het klembord worden gevraagd."
"actionOpenCommandPalette": "Snel zoeken openen"
},
"userProfile": {
"donateTitle": "Houd Termix in leven",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Onderwerp is verplicht",
"ntfyToken": "Toegangstoken (optioneel)",
"channelSaveFailed": "Het opslaan van het kanaal is mislukt."
},
"macros": {
"title": "Macro's",
"description": "Voer een opgeslagen reeks commando's uit in een terminal en reageer op wat de server terugstuurt.",
"target": "Doel",
"noTerminal": "Geen terminal geopend",
"empty": "Nog geen macro's",
"emptyHint": "Maak een macro om een terminalproces te automatiseren, zoals inloggen, een service herstarten of prompts beantwoorden.",
"create": "Nieuwe macro",
"back": "Terug naar macro's",
"name": "Naam",
"namePlaceholder": "Webserver opnieuw opstarten",
"nameRequired": "Geef de macro een naam voordat je hem opslaat.",
"descriptionLabel": "Beschrijving",
"descriptionPlaceholder": "Wat deze macro doet (optioneel)",
"unsaved": "Niet-opgeslagen wijzigingen",
"save": "Redden",
"saved": "Macro opgeslagen",
"saveFailed": "Het lukte niet om de macro op te slaan.",
"delete": "Verwijderen",
"deleteTitle": "Deze macro verwijderen?",
"deleteBody": "\"{{name}}\" wordt permanent verwijderd. Dit kan niet ongedaan gemaakt worden.",
"deleted": "Macro verwijderd",
"cancel": "Annuleren",
"run": "Loop",
"stop": "Stop",
"running": "Rennen...",
"completed": "Macro voltooid",
"failed": "Macro mislukt",
"loadFailed": "Macro's konden niet worden geladen.",
"needTerminal": "Open en verbind eerst een terminal.",
"noSteps": "Deze macro bevat nog geen stappen. Voeg er hieronder een toe.",
"addStep": "Voeg stap toe",
"stepNumber": "Stap {{number}}",
"removeStep": "Verwijder stap",
"confirmRunTitle": "Voer deze macro uit?",
"confirmRunBody": "\"{{name}}\" zal commando's naar {{host}} sturen.",
"confirmRunAction": "Macro uitvoeren",
"stepTypes": {
"send": {
"label": "Verzend commando",
"help": "Typt tekst in de terminal, net alsof je die zelf hebt ingetypt."
},
"wait": {
"label": "Wacht op uitvoer",
"help": "Het programma pauzeert totdat de server de door u opgegeven tekst afdrukt, zoals bij een wachtwoordprompt."
},
"delay": {
"label": "Pauze",
"help": "Wacht een vaste tijdsduur voordat de volgende stap wordt uitgevoerd."
},
"if": {
"label": "Als de uitvoer het volgende bevat:",
"help": "Controleert alles wat de server tot nu toe heeft afgedrukt en voert vervolgens een van de twee vertakkingen uit."
},
"repeat": {
"label": "Herhalen",
"help": "Voert de stappen binnenin een vast aantal keren uit."
}
},
"send": {
"text": "Opdracht of tekst",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Druk op Enter na het verzenden.",
"pressEnterHelp": "Schakel deze optie uit om tekst te typen zonder deze te verzenden."
},
"wait": {
"pattern": "Wacht tot de uitvoer het volgende bevat:",
"patternPlaceholder": "wachtwoord:",
"timeout": "Geef het op na",
"onTimeout": "Als het nooit verschijnt",
"stop": "Stop de macro",
"continue": "Ga toch door."
},
"delay": {
"duration": "Wachten op"
},
"ifStep": {
"pattern": "Als de uitvoer het volgende bevat:",
"patternPlaceholder": "Actief: actief (lopend)",
"then": "Doe dan dit",
"else": "Doe anders dit",
"emptyBranch": "Geen treden"
},
"repeat": {
"count": "Aantal keren",
"times": "tijden"
},
"match": {
"useRegex": "Overeenkomst met een reguliere expressie",
"useRegexHelp": "Laat dit weg zodat de tekst exact overeenkomt met de geschreven tekst.",
"invalidRegex": "Dit is geen geldige reguliere expressie; het wordt als platte tekst herkend."
},
"seconds": "seconden",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Innstillinger for sidefelt...",
"hosts": "Verter",
"snippets": "Utdrag",
"macros": "Makroer",
"hostManager": "Vertsadministrator",
"credentials": "Legitimasjon",
"connections": "Tilkoblinger",
@@ -869,7 +868,6 @@
"groupTags": "Tagger og pin",
"groupTunnels": "Tunneler",
"groupJumpHosts": "Hoppverter",
"groupQuickActions": "Hurtighandlinger",
"groupFeatureFlags": "Funksjonsflagg",
"groupAdvanced": "Avansert konfigurasjon",
"preview": "Forhåndsvisning",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Lagre SSH-nøkler eller passord",
"tmuxMonitor": "Tmux-skjerm",
"tmuxMonitorDesc": "Overvåk tmux-økter på tvers av vertene dine",
"snippets": "Utdrag",
"runSnippetDesc": "Kjør i den aktive terminalfanen",
"noSnippetsFound": "Ingen utdrag funnet som samsvarer med «{{search}}»",
"recentActivity": "Nylig aktivitet",
"serversAndHosts": "Servere og verter",
"noHostsFound": "Ingen verter funnet som samsvarer med «{{search}}»",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Forrige fane",
"hotkeysJumpTab": "Hopp til fane"
},
"snippets": {
"title": "Utdrag",
"createSnippetTitle": "Opprett utdrag",
"createSnippetDescription": "Opprett et nytt kommandosnutt for rask utførelse",
"nameLabel": "Navn",
"namePlaceholder": "f.eks. Start Nginx på nytt",
"descriptionLabel": "Beskrivelse",
"descriptionPlaceholder": "Valgfri beskrivelse",
"optional": "Valgfri",
"folderLabel": "Mappe",
"noFolder": "Ingen mappe (ukategorisert)",
"commandLabel": "Kommando",
"commandPlaceholder": "f.eks. sudo systemctl start nginx på nytt",
"typeLabel": "Type",
"typeCommand": "Kommando",
"typeNote": "Note",
"noteLabel": "Note",
"notePlaceholder": "f.eks. en gjenbrukbar ledetekst eller påminnelsestekst",
"cancel": "Kansellere",
"createSnippetButton": "Opprett utdrag",
"createFolderTitle": "Opprett mappe",
"createFolderDescription": "Organiser utdragene dine i mapper",
"folderNameLabel": "Mappenavn",
"folderNamePlaceholder": "f.eks. systemkommandoer, Docker-skript",
"folderColorLabel": "Mappefarge",
"folderIconLabel": "Mappeikon",
"previewLabel": "Forhåndsvisning",
"folderNameFallback": "Mappenavn",
"createFolderButton": "Opprett mappe",
"targetTerminals": "Target Terminals",
"selectAll": "Alle",
"selectNone": "Ingen",
"noTerminalTabsOpen": "Ingen terminalfaner åpne",
"searchPlaceholder": "Søk i utdrag ...",
"newSnippet": "Nytt utdrag",
"newFolder": "Ny mappe",
"run": "Løp",
"noSnippetsInFolder": "Ingen utdrag i denne mappen",
"uncategorized": "Ukategorisert",
"editSnippetTitle": "Rediger utdrag",
"editSnippetDescription": "Oppdater dette kommandosnuttet",
"saveSnippetButton": "Lagre endringer",
"createSuccess": "Kodeutdraget er opprettet",
"createFailed": "Kunne ikke opprette kodebiten",
"updateSuccess": "Kodeutdraget er oppdatert",
"updateFailed": "Kunne ikke oppdatere kodestykket",
"deleteFailed": "Kunne ikke slette utdraget",
"folderCreateSuccess": "Mappen er opprettet",
"folderCreateFailed": "Kunne ikke opprette mappen",
"editFolderTitle": "Rediger mappe",
"editFolderDescription": "Gi denne mappen nytt navn eller endre utseendet",
"saveFolderButton": "Lagre endringer",
"editFolder": "Rediger mappe",
"deleteFolder": "Slett mappe",
"folderDeleteSuccess": "Mappen «{{name}}» er slettet",
"folderDeleteFailed": "Kunne ikke slette mappen",
"folderEditSuccess": "Mappen er oppdatert",
"folderEditFailed": "Kunne ikke oppdatere mappen",
"confirmRunMessage": "Kjør \"{{name}}\"?",
"confirmRunButton": "Løp",
"runSuccess": "Kjørte \"{{name}}\" i {{count}} terminal(er)",
"pasteSuccess": "Limte inn «{{name}}» i {{count}} terminal(er)",
"pasteToTerminal": "Lim inn",
"copySuccess": "Kopierte «{{name}}» til utklippstavlen",
"shareTitle": "Del utdrag",
"shareUser": "Bruker",
"shareRole": "Rolle",
"selectUser": "Velg en bruker...",
"selectRole": "Velg en rolle...",
"shareSuccess": "Kodestykket ble delt",
"shareFailed": "Kunne ikke dele utdraget",
"revokeSuccess": "Tilgang tilbakekalt",
"revokeFailed": "Kunne ikke tilbakekalle tilgang",
"currentAccess": "Nåværende tilgang",
"shareLoadError": "Kunne ikke laste inn delingsdata",
"loading": "Laster inn...",
"close": "Lukke",
"reorderFailed": "Kunne ikke lagre rekkefølgen på kodeutdraget",
"importExport": "Import/eksport",
"exportBtn": "Eksporter JSON",
"importBtn": "Importer JSON",
"exportSuccess": "Utdragene er eksportert",
"exportFailed": "Kunne ikke eksportere utdrag",
"importTitle": "Importer utdrag",
"importDescription": "Importer utdrag og mapper fra en JSON-fil eksportert av Termix",
"importDropOrClick": "Slipp en JSON-fil her eller klikk for å bla gjennom",
"importSelectedFile": "Valgt: {{name}}",
"importOverwrite": "Overskriv eksisterende utdrag med samme navn og mappe",
"importStartBtn": "Import",
"importSuccess": "Import fullført: {{snippets}} kodebit(er) lagt til, {{updated}} oppdatert, {{skipped}} hoppet over, {{folders}} mappe(r) lagt til",
"importFailed": "Kunne ikke importere kodebiter",
"importInvalidFile": "Ugyldig fil: forventet et JSON-objekt med snippets eller mappearrayer",
"targetHostsLabel": "Målverter",
"targetHostsHint": "Tildel verter til å kjøre dette kodestykket direkte uten en åpen terminal.",
"noHostsAvailable": "Ingen verter konfigurert",
"clearTargetHosts": "Fjern alle",
"hasTargetHosts": "Har målverter",
"runOnTargets": "Løp på mål",
"directRunSuccess": "Kjørte \"{{name}}\" på {{count}} vert(er)",
"directRunPartialFail": "«{{name}}» mislyktes på én eller flere verter",
"executionResultTitle": "Utførelsesresultater: {{name}}",
"executionResultDescription": "Utdata fra kjøring av kodestykket på hver målvert.",
"executionSuccess": "Suksess",
"executionFailed": "Mislyktes",
"expandAll": "Utvid alle mapper",
"collapseAll": "Skjul alle mapper",
"settings": "Innstillinger for utdrag",
"variablesHint": "Variabler: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Etikett}",
"variablesDialogTitle": "Kjør «{{name}}»",
"variablesDialogDescription": "Fyll inn verdiene dette kodestykket trenger før du kjører det.",
"variablesPreviewLabel": "Forhåndsvisning",
"variablesConfirmButton": "Løp"
},
"keybindings": {
"title": "Tastatursnarveier",
"description": "Tilpass snarveier for terminalkopiering, liming og kontroll, eller bind taster for å sende tekst eller kjøre et tekstutdrag.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klikk og trykk på tastene",
"recording": "Trykk på tastene...",
"comboRequiredError": "Trykk først på en tastekombinasjon",
"textRequiredError": "Skriv inn teksten som skal sendes",
"controlCodeRequiredError": "Skriv inn én bokstav for kontrollkoden",
"snippetRequiredError": "Velg et utdrag",
"conflictWarning": "Denne kombinasjonen er allerede bundet til {{combo}}. Lagring vil få begge til å aktiveres.",
"actionLabel": "Handling",
"actionCopy": "Kopier valg",
"actionPaste": "Lim inn",
"actionSendControlCode": "Send Ctrl+bokstavsignal",
"actionSendText": "Send tekstmelding",
"actionRunSnippet": "Kjør eksisterende kodebit",
"actionNextTab": "Bytt til neste fane",
"actionPreviousTab": "Bytt til forrige fane",
"actionOpenCommandPalette": "Åpne hurtigsøk",
"controlCodeLabel": "Brev",
"textLabel": "Tekst som skal sendes",
"appendEnterLabel": "Trykk Enter etter sending",
"snippetLabel": "Utdrag",
"selectSnippetPlaceholder": "Velg et utdrag",
"orphanedSnippetWarning": "utdraget ikke funnet",
"clipboardPermissionNote": "Kan be om tillatelse til utklippstavlen ved første gangs bruk i noen nettlesere."
"actionOpenCommandPalette": "Åpne hurtigsøk"
},
"userProfile": {
"donateTitle": "Hold Termix i live",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Emne er obligatorisk",
"ntfyToken": "Tilgangstoken (valgfritt)",
"channelSaveFailed": "Kunne ikke lagre kanalen"
},
"macros": {
"title": "Makroer",
"description": "Kjør en lagret serie med kommandoer i en terminal, og reager på det serveren sender tilbake.",
"target": "Mål",
"noTerminal": "Ingen terminal åpen",
"empty": "Ingen makroer ennå",
"emptyHint": "Opprett en makro for å automatisere en terminalflyt, som å logge inn, starte en tjeneste på nytt eller svare på forespørsler.",
"create": "Ny makro",
"back": "Tilbake til makroer",
"name": "Navn",
"namePlaceholder": "Start nettserveren på nytt",
"nameRequired": "Gi makroen et navn før du lagrer",
"descriptionLabel": "Beskrivelse",
"descriptionPlaceholder": "Hva denne makroen gjør (valgfritt)",
"unsaved": "Ulagrede endringer",
"save": "Spare",
"saved": "Makro lagret",
"saveFailed": "Kunne ikke lagre makroen",
"delete": "Slett",
"deleteTitle": "Slette denne makroen?",
"deleteBody": "«{{name}}» vil bli slettet permanent. Dette kan ikke angres.",
"deleted": "Makro slettet",
"cancel": "Kansellere",
"run": "Løp",
"stop": "Stoppe",
"running": "Løping...",
"completed": "Makro ferdig",
"failed": "Makroen mislyktes",
"loadFailed": "Kunne ikke laste inn makroer",
"needTerminal": "Åpne og koble til en terminal først",
"noSteps": "Denne makroen har ingen trinn ennå. Legg til et nedenfor.",
"addStep": "Legg til trinn",
"stepNumber": "Trinn {{number}}",
"removeStep": "Fjern trinn",
"confirmRunTitle": "Kjøre denne makroen?",
"confirmRunBody": "\"{{name}}\" vil sende kommandoer til {{host}}.",
"confirmRunAction": "Kjør makro",
"stepTypes": {
"send": {
"label": "Send kommando",
"help": "Skriver tekst inn i terminalen, på samme måte som om du hadde skrevet den."
},
"wait": {
"label": "Vent på utdata",
"help": "Pauserer til serveren skriver ut teksten du oppgir, som en passordforespørsel."
},
"delay": {
"label": "Pause",
"help": "Venter en fast tid før neste trinn."
},
"if": {
"label": "Hvis utdataene inneholder",
"help": "Sjekker alt serveren har skrevet ut så langt, og kjører deretter en av to grener."
},
"repeat": {
"label": "Gjenta",
"help": "Kjører trinnene inni den et angitt antall ganger."
}
},
"send": {
"text": "Kommando eller tekst",
"textPlaceholder": "systemctl omstart nginx",
"pressEnter": "Trykk Enter etter sending",
"pressEnterHelp": "Slå av dette for å skrive tekst uten å sende den inn."
},
"wait": {
"pattern": "Vent til utdataene inneholder",
"patternPlaceholder": "passord:",
"timeout": "Gi opp etterpå",
"onTimeout": "Hvis den aldri dukker opp",
"stop": "Stopp makroen",
"continue": "Fortsett uansett"
},
"delay": {
"duration": "Vent på"
},
"ifStep": {
"pattern": "Hvis utdataene inneholder",
"patternPlaceholder": "Aktiv: aktiv (løper)",
"then": "Gjør så dette",
"else": "Ellers gjør dette",
"emptyBranch": "Ingen trinn"
},
"repeat": {
"count": "Antall ganger",
"times": "ganger"
},
"match": {
"useRegex": "Samsvar med et regulært uttrykk",
"useRegexHelp": "La dette være av for å få teksten til å se nøyaktig slik den er skrevet.",
"invalidRegex": "Ikke et gyldig regulært uttrykk, det vil bli matchet som ren tekst"
},
"seconds": "sekunder",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Ustawienia paska bocznego...",
"hosts": "Hosty",
"snippets": "Snippety",
"macros": "Makra",
"hostManager": "Menedżer hostów",
"credentials": "Poświadczenia",
"connections": "Połączenia",
@@ -869,7 +868,6 @@
"groupTags": "Tagi i przypinanie",
"groupTunnels": "Tunele",
"groupJumpHosts": "Hosty pośrednie",
"groupQuickActions": "Szybkie akcje",
"groupFeatureFlags": "Flagi funkcji",
"groupAdvanced": "Zaawansowana konfiguracja",
"preview": "Podgląd",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Przechowuj klucze SSH lub hasła",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Monitoruj sesje tmux na swoich hostach",
"snippets": "Snippety",
"runSnippetDesc": "Uruchom w aktywnej karcie terminala",
"noSnippetsFound": "Nie znaleziono snippetów pasujących do „{{search}}”",
"recentActivity": "Ostatnia aktywność",
"serversAndHosts": "Serwery i hosty",
"noHostsFound": "Nie znaleziono hostów pasujących do „{{search}}”",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Poprzednia karta",
"hotkeysJumpTab": "Przejdź do karty"
},
"snippets": {
"title": "Snippety",
"createSnippetTitle": "Utwórz snippet",
"createSnippetDescription": "Utwórz nowy snippet polecenia do szybkiego uruchamiania",
"nameLabel": "Nazwa",
"namePlaceholder": "np. Uruchom ponownie Nginx",
"descriptionLabel": "Opis",
"descriptionPlaceholder": "Opis opcjonalny",
"optional": "Opcjonalne",
"folderLabel": "Folder",
"noFolder": "Brak folderu (bez kategorii)",
"commandLabel": "Polecenie",
"commandPlaceholder": "np. sudo systemctl restart nginx",
"typeLabel": "Typ",
"typeCommand": "Polecenie",
"typeNote": "Notatka",
"noteLabel": "Notatka",
"notePlaceholder": "np. tekst monitu lub przypomnienia do wielokrotnego użytku",
"cancel": "Anuluj",
"createSnippetButton": "Utwórz snippet",
"createFolderTitle": "Utwórz folder",
"createFolderDescription": "Uporządkuj snippety w folderach",
"folderNameLabel": "Nazwa folderu",
"folderNamePlaceholder": "np. Polecenia systemowe, Skrypty Dockera",
"folderColorLabel": "Kolor folderu",
"folderIconLabel": "Ikona folderu",
"previewLabel": "Podgląd",
"folderNameFallback": "Nazwa folderu",
"createFolderButton": "Utwórz folder",
"targetTerminals": "Terminale docelowe",
"selectAll": "Wszystkie",
"selectNone": "Żaden",
"noTerminalTabsOpen": "Brak otwartych kart terminala",
"searchPlaceholder": "Wyszukaj snippety...",
"newSnippet": "Nowy snippet",
"newFolder": "Nowy folder",
"run": "Uruchom",
"noSnippetsInFolder": "Brak snippetów w tym folderze",
"uncategorized": "Bez kategorii",
"editSnippetTitle": "Edytuj snippet",
"editSnippetDescription": "Zaktualizuj ten snippet polecenia",
"saveSnippetButton": "Zapisz zmiany",
"createSuccess": "Utworzono snippet",
"createFailed": "Nie udało się utworzyć snippetu",
"updateSuccess": "Zaktualizowano snippet",
"updateFailed": "Nie udało się zaktualizować snippetu",
"deleteFailed": "Nie udało się usunąć snippetu",
"folderCreateSuccess": "Folder został pomyślnie utworzony",
"folderCreateFailed": "Nie udało się utworzyć folderu",
"editFolderTitle": "Edytuj folder",
"editFolderDescription": "Zmień nazwę lub wygląd tego folderu",
"saveFolderButton": "Zapisz zmiany",
"editFolder": "Edytuj folder",
"deleteFolder": "Usuń folder",
"folderDeleteSuccess": "Folder „{{name}}” został usunięty",
"folderDeleteFailed": "Nie udało się usunąć folderu",
"folderEditSuccess": "Folder został pomyślnie zaktualizowany",
"folderEditFailed": "Nie udało się zaktualizować folderu",
"confirmRunMessage": "Uruchomić „{{name}}”?",
"confirmRunButton": "Uruchom",
"runSuccess": "Uruchomiono „{{name}}” w {{count}} terminalach",
"pasteSuccess": "Wklejono „{{name}}” do {{count}} terminali",
"pasteToTerminal": "Wklej",
"copySuccess": "Skopiowano „{{name}}” do schowka",
"shareTitle": "Udostępnij snippet",
"shareUser": "Użytkownik",
"shareRole": "Rola",
"selectUser": "Wybierz użytkownika...",
"selectRole": "Wybierz rolę...",
"shareSuccess": "Udostępniono snippet",
"shareFailed": "Nie udało się udostępnić snippetu",
"revokeSuccess": "Unieważniono dostęp",
"revokeFailed": "Nie udało się unieważnić dostępu",
"currentAccess": "Aktualny dostęp",
"shareLoadError": "Nie udało się załadować danych udostępniania",
"loading": "Ładowanie...",
"close": "Zamknij",
"reorderFailed": "Nie udało się zapisać kolejności snippetów",
"importExport": "Import/Eksport",
"exportBtn": "Eksportuj JSON",
"importBtn": "Importuj JSON",
"exportSuccess": "Wyeksportowano snippety",
"exportFailed": "Nie udało się wyeksportować snippetów",
"importTitle": "Importuj snippety",
"importDescription": "Importuj snippety i foldery z pliku JSON wyeksportowanego przez Termix",
"importDropOrClick": "Upuść plik JSON tutaj lub kliknij, aby przeglądać",
"importSelectedFile": "Wybrano: {{name}}",
"importOverwrite": "Nadpisz istniejące snippety o tej samej nazwie i folderze",
"importStartBtn": "Import",
"importSuccess": "Import ukończony: dodano {{snippets}} snippetów, zaktualizowano {{updated}}, pominięto {{skipped}}, dodano {{folders}} folderów",
"importFailed": "Nie udało się zaimportować snippetów",
"importInvalidFile": "Nieprawidłowy plik: oczekiwano obiektu JSON z tablicami snippets lub folders",
"targetHostsLabel": "Hosty docelowe",
"targetHostsHint": "Przypisz hosty, aby uruchamiać ten snippet bezpośrednio, bez otwartego terminala.",
"noHostsAvailable": "Brak skonfigurowanych hostów",
"clearTargetHosts": "Wyczyść wszystko",
"hasTargetHosts": "Ma hosty docelowe",
"runOnTargets": "Uruchom na hostach docelowych",
"directRunSuccess": "Uruchomiono „{{name}}” na {{count}} hostach",
"directRunPartialFail": "„{{name}}” nie powiodło się na jednym lub kilku hostach",
"executionResultTitle": "Wyniki wykonania: {{name}}",
"executionResultDescription": "Wyjście z uruchomienia snippetu na każdym hoście docelowym.",
"executionSuccess": "Powodzenie",
"executionFailed": "Niepowodzenie",
"expandAll": "Rozwiń wszystkie foldery",
"collapseAll": "Zwiń wszystkie foldery",
"settings": "Ustawienia snippetów",
"variablesHint": "Zmienne: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Uruchom „{{name}}”",
"variablesDialogDescription": "Przed uruchomieniem snippetu wprowadź wymagane wartości.",
"variablesPreviewLabel": "Podgląd",
"variablesConfirmButton": "Uruchom"
},
"keybindings": {
"title": "Skróty klawiaturowe",
"description": "Dostosuj skróty kopiowania, wklejania i sterowania w terminalu albo przypisz klawiszom wysyłanie tekstu lub uruchomienie snippetu.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Kliknij i naciśnij klawisze",
"recording": "Naciśnij klawisze...",
"comboRequiredError": "Najpierw naciśnij kombinację klawiszy",
"textRequiredError": "Wprowadź tekst do wysłania",
"controlCodeRequiredError": "Wprowadź pojedynczą literę kodu kontrolnego",
"snippetRequiredError": "Wybierz snippet",
"conflictWarning": "Ta kombinacja jest już przypisana do {{combo}}. Po zapisaniu zadziałają obie.",
"actionLabel": "Akcja",
"actionCopy": "Kopiuj zaznaczenie",
"actionPaste": "Wklej",
"actionSendControlCode": "Wyślij sygnał Ctrl+litera",
"actionSendText": "Wyślij tekst",
"actionRunSnippet": "Uruchom istniejący snippet",
"actionNextTab": "Przejdź do następnej karty",
"actionPreviousTab": "Przejdź do poprzedniej karty",
"actionOpenCommandPalette": "Otwórz szybkie wyszukiwanie",
"controlCodeLabel": "Litera",
"textLabel": "Tekst do wysłania",
"appendEnterLabel": "Naciśnij Enter po wysłaniu",
"snippetLabel": "Snippet",
"selectSnippetPlaceholder": "Wybierz snippet",
"orphanedSnippetWarning": "nie znaleziono snippetu",
"clipboardPermissionNote": "Przy pierwszym użyciu w niektórych przeglądarkach może zostać wyświetlony monit o zezwolenie na dostęp do schowka."
"actionOpenCommandPalette": "Otwórz szybkie wyszukiwanie"
},
"userProfile": {
"donateTitle": "Utrzymajmy Termix przy życiu",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Temat jest wymagany",
"ntfyToken": "Token dostępu (opcjonalnie)",
"channelSaveFailed": "Nie udało się zapisać kanału"
},
"macros": {
"title": "Makra",
"description": "Uruchom zapisaną serię poleceń w terminalu i zareaguj na to, co odeśle serwer.",
"target": "Cel",
"noTerminal": "Brak otwartego terminala",
"empty": "Brak makr",
"emptyHint": "Utwórz makro, aby zautomatyzować pracę w terminalu: logowanie, restart usługi czy odpowiadanie na monity.",
"create": "Nowe makro",
"back": "Powrót do makr",
"name": "Nazwa",
"namePlaceholder": "Uruchom ponownie serwer WWW",
"nameRequired": "Przed zapisaniem nadaj makrze nazwę",
"descriptionLabel": "Opis",
"descriptionPlaceholder": "Co robi to makro (opcjonalnie)",
"unsaved": "Niezapisane zmiany",
"save": "Zapisz",
"saved": "Zapisano makro",
"saveFailed": "Nie udało się zapisać makra",
"delete": "Usuń",
"deleteTitle": "Usunąć to makro?",
"deleteBody": "„{{name}}” zostanie trwale usunięte. Nie można tego cofnąć.",
"deleted": "Usunięto makro",
"cancel": "Anuluj",
"run": "Uruchom",
"stop": "Zatrzymaj",
"running": "Działa...",
"completed": "Makro zakończone",
"failed": "Makro nie powiodło się",
"loadFailed": "Nie można załadować makr",
"needTerminal": "Najpierw otwórz i podłącz terminal",
"noSteps": "To makro nie ma jeszcze żadnych kroków. Dodaj krok poniżej.",
"addStep": "Dodaj krok",
"stepNumber": "Krok {{number}}",
"removeStep": "Usuń krok",
"confirmRunTitle": "Uruchomić to makro?",
"confirmRunBody": "„{{name}}” wyśle polecenia do {{host}}.",
"confirmRunAction": "Uruchom makro",
"stepTypes": {
"send": {
"label": "Wyślij polecenie",
"help": "Wpisuje tekst do terminala, tak samo, jakby wpisano go ręcznie."
},
"wait": {
"label": "Czekaj na wyjście",
"help": "Wstrzymuje wykonanie, aż serwer wypisze podany tekst, np. monit o hasło."
},
"delay": {
"label": "Pauza",
"help": "Czeka przez ustalony czas przed kolejnym krokiem."
},
"if": {
"label": "Jeśli wyjście zawiera",
"help": "Sprawdza wszystko, co serwer wypisał do tej pory, a potem uruchamia jedną z dwóch gałęzi."
},
"repeat": {
"label": "Powtórz",
"help": "Uruchamia zawarte w nim kroki ustaloną liczbę razy."
}
},
"send": {
"text": "Polecenie lub tekst",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Naciśnij Enter po wysłaniu",
"pressEnterHelp": "Wyłącz tę opcję, aby wpisać tekst bez przesyłania go."
},
"wait": {
"pattern": "Czekaj, aż wyjście będzie zawierać",
"patternPlaceholder": "password:",
"timeout": "Przerwij po",
"onTimeout": "Jeśli nigdy się nie pojawi",
"stop": "Zatrzymaj makro",
"continue": "Kontynuuj mimo wszystko"
},
"delay": {
"duration": "Czekaj"
},
"ifStep": {
"pattern": "Jeśli wyjście zawiera",
"patternPlaceholder": "Active: active (running)",
"then": "Następnie zrób to",
"else": "W przeciwnym razie zrób to",
"emptyBranch": "Brak kroków"
},
"repeat": {
"count": "Liczba razy",
"times": "razy"
},
"match": {
"useRegex": "Dopasuj do wyrażenia regularnego",
"useRegexHelp": "Zostaw wyłączone, aby dopasować tekst dokładnie tak, jak został wpisany.",
"invalidRegex": "Nie jest to prawidłowe wyrażenie regularne, zostanie dopasowane jako zwykły tekst"
},
"seconds": "sekundy",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Configurações da barra lateral...",
"hosts": "Anfitriões",
"snippets": "Trechos",
"macros": "Macros",
"hostManager": "Gerente de Hospedagem",
"credentials": "Credenciais",
"connections": "Conexões",
@@ -869,7 +868,6 @@
"groupTags": "Etiquetas e alfinete",
"groupTunnels": "Túneis",
"groupJumpHosts": "Apresentadores do Jump",
"groupQuickActions": "Ações rápidas",
"groupFeatureFlags": "Sinalizadores de recursos",
"groupAdvanced": "Configuração avançada",
"preview": "Pré-visualização",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Armazene chaves ou senhas SSH",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Monitore as sessões do tmux em seus hosts.",
"snippets": "Trechos",
"runSnippetDesc": "Execute na aba do terminal ativo",
"noSnippetsFound": "Nenhum trecho encontrado que corresponda a \"{{search}}\"",
"recentActivity": "Atividade recente",
"serversAndHosts": "Servidores e Hosts",
"noHostsFound": "Nenhum host encontrado que corresponda a \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Aba anterior",
"hotkeysJumpTab": "Ir para a aba"
},
"snippets": {
"title": "Trechos",
"createSnippetTitle": "Criar trecho de código",
"createSnippetDescription": "Crie um novo trecho de comando para execução rápida.",
"nameLabel": "Nome",
"namePlaceholder": "Por exemplo, reinicie o Nginx.",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "Descrição opcional",
"optional": "Opcional",
"folderLabel": "Pasta",
"noFolder": "Sem pasta (Não categorizado)",
"commandLabel": "Comando",
"commandPlaceholder": "Por exemplo, sudo systemctl restart nginx",
"typeLabel": "Tipo",
"typeCommand": "Comando",
"typeNote": "Observação",
"noteLabel": "Observação",
"notePlaceholder": "Por exemplo, um texto de lembrete ou aviso reutilizável.",
"cancel": "Cancelar",
"createSnippetButton": "Criar trecho de código",
"createFolderTitle": "Criar pasta",
"createFolderDescription": "Organize seus trechos em pastas.",
"folderNameLabel": "Nome da pasta",
"folderNamePlaceholder": "Por exemplo, comandos do sistema, scripts do Docker.",
"folderColorLabel": "Pasta colorida",
"folderIconLabel": "Ícone de pasta",
"previewLabel": "Pré-visualização",
"folderNameFallback": "Nome da pasta",
"createFolderButton": "Criar pasta",
"targetTerminals": "Terminais de destino",
"selectAll": "Todos",
"selectNone": "Nenhum",
"noTerminalTabsOpen": "Nenhuma aba do terminal aberta",
"searchPlaceholder": "Trechos de pesquisa...",
"newSnippet": "Novo trecho",
"newFolder": "Nova pasta",
"run": "Correr",
"noSnippetsInFolder": "Não há trechos de código nesta pasta.",
"uncategorized": "Sem categoria",
"editSnippetTitle": "Editar trecho",
"editSnippetDescription": "Atualize este trecho de comando",
"saveSnippetButton": "Salvar alterações",
"createSuccess": "Trecho criado com sucesso",
"createFailed": "Falha ao criar o trecho de código",
"updateSuccess": "Trecho de código atualizado com sucesso",
"updateFailed": "Falha ao atualizar o trecho de código",
"deleteFailed": "Falha ao excluir o trecho",
"folderCreateSuccess": "Pasta criada com sucesso",
"folderCreateFailed": "Falha ao criar a pasta",
"editFolderTitle": "Pasta de edição",
"editFolderDescription": "Renomeie ou altere a aparência desta pasta.",
"saveFolderButton": "Salvar alterações",
"editFolder": "Pasta de edição",
"deleteFolder": "Excluir pasta",
"folderDeleteSuccess": "Pasta \"{{name}}\" excluída",
"folderDeleteFailed": "Falha ao excluir a pasta",
"folderEditSuccess": "Pasta atualizada com sucesso",
"folderEditFailed": "Falha ao atualizar a pasta",
"confirmRunMessage": "Executar \"{{name}}\"?",
"confirmRunButton": "Correr",
"runSuccess": "Executado \"{{name}}\" em {{count}} terminal(is)",
"pasteSuccess": "Colado \"{{name}}\" em {{count}} terminal(is)",
"pasteToTerminal": "Colar",
"copySuccess": "Copiado \"{{name}}\" para a área de transferência",
"shareTitle": "Compartilhar trecho",
"shareUser": "Usuário",
"shareRole": "Papel",
"selectUser": "Selecione um usuário...",
"selectRole": "Selecione uma função...",
"shareSuccess": "Trecho compartilhado com sucesso",
"shareFailed": "Falha ao compartilhar o trecho",
"revokeSuccess": "Acesso revogado",
"revokeFailed": "Falha ao revogar o acesso",
"currentAccess": "Acesso atual",
"shareLoadError": "Falha ao carregar os dados compartilhados.",
"loading": "Carregando...",
"close": "Fechar",
"reorderFailed": "Falha ao salvar a ordem do trecho de código",
"importExport": "Importação/Exportação",
"exportBtn": "Exportar JSON",
"importBtn": "Importar JSON",
"exportSuccess": "Trechos exportados com sucesso",
"exportFailed": "Falha ao exportar trechos de código.",
"importTitle": "Importar trechos",
"importDescription": "Importe trechos e pastas de um arquivo JSON exportado pelo Termix.",
"importDropOrClick": "Arraste e solte um arquivo JSON aqui ou clique para navegar.",
"importSelectedFile": "Selecionado: {{name}}",
"importOverwrite": "Sobrescrever trechos de código existentes com o mesmo nome e pasta.",
"importStartBtn": "Importar",
"importSuccess": "Importação concluída: {{snippets}} trecho(s) adicionado(s), {{updated}} atualizado(s), {{skipped}} ignorado(s), {{folders}} pasta(s) adicionada(s)",
"importFailed": "Falha ao importar trechos de código.",
"importInvalidFile": "Arquivo inválido: esperava-se um objeto JSON com trechos ou arrays de pastas.",
"targetHostsLabel": "Hosts de destino",
"targetHostsHint": "Atribua hosts para executar este trecho de código diretamente, sem a necessidade de um terminal aberto.",
"noHostsAvailable": "Nenhum host configurado",
"clearTargetHosts": "Limpar tudo",
"hasTargetHosts": "Possui hosts de destino",
"runOnTargets": "Corrida com base em metas",
"directRunSuccess": "Executado \"{{name}}\" em {{count}} host(s)",
"directRunPartialFail": "\"{{name}}\" falhou em um ou mais hosts",
"executionResultTitle": "Resultados da execução: {{name}}",
"executionResultDescription": "Resultado da execução do trecho de código em cada host de destino.",
"executionSuccess": "Sucesso",
"executionFailed": "Fracassado",
"expandAll": "Expandir todas as pastas",
"collapseAll": "Recolher todas as pastas",
"settings": "Configurações de trechos de código",
"variablesHint": "Variáveis: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Executar \"{{name}}\"",
"variablesDialogDescription": "Preencha os valores necessários para este trecho de código antes de executá-lo.",
"variablesPreviewLabel": "Pré-visualização",
"variablesConfirmButton": "Correr"
},
"keybindings": {
"title": "Atalhos de teclado",
"description": "Personalize os atalhos de copiar, colar e controlar o terminal, ou associe teclas para enviar texto ou executar um trecho de código.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Clique e pressione as teclas.",
"recording": "Pressione as teclas...",
"comboRequiredError": "Pressione primeiro uma combinação de teclas",
"textRequiredError": "Digite o texto que deseja enviar.",
"controlCodeRequiredError": "Digite uma única letra para o código de controle.",
"snippetRequiredError": "Selecione um trecho",
"conflictWarning": "Esta combinação já está vinculada a {{combo}}. Salvar fará com que ambas disparem.",
"actionLabel": "Ação",
"actionCopy": "Copiar seleção",
"actionPaste": "Colar",
"actionSendControlCode": "Enviar sinal Ctrl+letra",
"actionSendText": "Enviar mensagem de texto",
"actionRunSnippet": "Executar trecho de código existente",
"actionNextTab": "Mudar para a próxima aba",
"actionPreviousTab": "Alternar para a aba anterior",
"actionOpenCommandPalette": "Abrir pesquisa rápida",
"controlCodeLabel": "Carta",
"textLabel": "Texto a enviar",
"appendEnterLabel": "Pressione Enter após enviar",
"snippetLabel": "Trecho",
"selectSnippetPlaceholder": "Selecione um trecho",
"orphanedSnippetWarning": "trecho não encontrado",
"clipboardPermissionNote": "Pode solicitar permissão para acessar a área de transferência no primeiro uso em alguns navegadores."
"actionOpenCommandPalette": "Abrir pesquisa rápida"
},
"userProfile": {
"donateTitle": "Mantenha o Termix vivo",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "É necessário definir o tema.",
"ntfyToken": "Token de acesso (opcional)",
"channelSaveFailed": "Falha ao salvar o canal"
},
"macros": {
"title": "Macros",
"description": "Execute uma série de comandos salvos em um terminal e reaja ao que o servidor envia de volta.",
"target": "Alvo",
"noTerminal": "Nenhum terminal aberto",
"empty": "Ainda não há macros.",
"emptyHint": "Crie uma macro para automatizar um fluxo de trabalho no terminal, como fazer login, reiniciar um serviço ou responder a solicitações.",
"create": "Nova macro",
"back": "Voltar às macros",
"name": "Nome",
"namePlaceholder": "Reinicie o servidor web.",
"nameRequired": "Dê um nome à macro antes de salvar.",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "O que esta macro faz (opcional)",
"unsaved": "Alterações não salvas",
"save": "Salvar",
"saved": "Macro salvo",
"saveFailed": "Não foi possível salvar a macro.",
"delete": "Excluir",
"deleteTitle": "Excluir esta macro?",
"deleteBody": "\"{{name}}\" será excluído permanentemente. Esta ação não pode ser desfeita.",
"deleted": "Macro excluída",
"cancel": "Cancelar",
"run": "Correr",
"stop": "Parar",
"running": "Correndo...",
"completed": "Macro finalizado",
"failed": "A macro falhou",
"loadFailed": "Não foi possível carregar as macros",
"needTerminal": "Primeiro, abra e conecte um terminal.",
"noSteps": "Esta macro ainda não possui etapas. Adicione uma abaixo.",
"addStep": "Adicionar etapa",
"stepNumber": "Passo {{number}}",
"removeStep": "Remover etapa",
"confirmRunTitle": "Executar esta macro?",
"confirmRunBody": "\"{{name}}\" enviará comandos para {{host}}.",
"confirmRunAction": "Executar macro",
"stepTypes": {
"send": {
"label": "Enviar comando",
"help": "Digita o texto no terminal, da mesma forma que se você o digitasse."
},
"wait": {
"label": "Aguarde a saída",
"help": "Aguarda até que o servidor imprima o texto que você digitou, como uma solicitação de senha."
},
"delay": {
"label": "Pausa",
"help": "Aguarda um período de tempo fixo antes da próxima etapa."
},
"if": {
"label": "Se a saída contiver",
"help": "Verifica tudo o que o servidor imprimiu até o momento e, em seguida, executa uma das duas ramificações."
},
"repeat": {
"label": "Repita",
"help": "Executa os passos internos um número definido de vezes."
}
},
"send": {
"text": "Comando ou texto",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Pressione Enter após enviar",
"pressEnterHelp": "Desative esta opção para digitar texto sem enviá-lo."
},
"wait": {
"pattern": "Aguarde até que a saída contenha",
"patternPlaceholder": "senha:",
"timeout": "Desistir depois",
"onTimeout": "Se nunca aparecer",
"stop": "Pare a macro",
"continue": "Continue mesmo assim"
},
"delay": {
"duration": "Aguarde"
},
"ifStep": {
"pattern": "Se a saída contiver",
"patternPlaceholder": "Ativo: ativo (em execução)",
"then": "Então faça isso",
"else": "Caso contrário, faça isto",
"emptyBranch": "Sem degraus"
},
"repeat": {
"count": "Número de vezes",
"times": "vezes"
},
"match": {
"useRegex": "Combinar com uma expressão regular",
"useRegexHelp": "Deixe este campo em branco para que o texto corresponda exatamente ao que foi escrito.",
"invalidRegex": "Expressão regular inválida; será interpretada como texto simples."
},
"seconds": "segundos",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Definições da barra lateral...",
"hosts": "Máquinas",
"snippets": "Fragmentos",
"macros": "Macros",
"hostManager": "Gestor de Máquinas",
"credentials": "Credenciais",
"connections": "Ligações",
@@ -869,7 +868,6 @@
"groupTags": "Etiquetas e alfinete",
"groupTunnels": "Túneis",
"groupJumpHosts": "Apresentadores do Jump",
"groupQuickActions": "Ações rápidas",
"groupFeatureFlags": "Sinalizadores de recursos",
"groupAdvanced": "Configuração avançada",
"preview": "Pré-visualização",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Guardar chaves SSH ou palavras-passe",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Monitorizar sessões tmux nos seus hosts",
"snippets": "Trechos",
"runSnippetDesc": "Execute no separador do terminal ativo",
"noSnippetsFound": "Não foram encontrados trechos que correspondam a \"{{search}}\"",
"recentActivity": "Atividade Recente",
"serversAndHosts": "Servidores e Hosts",
"noHostsFound": "Nenhum host encontrado que corresponda a \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Separador anterior",
"hotkeysJumpTab": "Ir para o separador"
},
"snippets": {
"title": "Fragmentos",
"createSnippetTitle": "Criar Fragmento",
"createSnippetDescription": "Criar um novo fragmento de comando para execução rápida",
"nameLabel": "Nome",
"namePlaceholder": "p. ex., Reiniciar Nginx",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "Descrição opcional",
"optional": "Opcional",
"folderLabel": "Pasta",
"noFolder": "Sem pasta (Não categorizado)",
"commandLabel": "Comando",
"commandPlaceholder": "p. ex., sudo systemctl restart nginx",
"typeLabel": "Tipo",
"typeCommand": "Comando",
"typeNote": "Nota",
"noteLabel": "Nota",
"notePlaceholder": "Por exemplo, um texto de lembrete ou aviso reutilizável.",
"cancel": "Cancelar",
"createSnippetButton": "Criar Fragmento",
"createFolderTitle": "Criar Pasta",
"createFolderDescription": "Organize os seus fragmentos em pastas",
"folderNameLabel": "Nome da Pasta",
"folderNamePlaceholder": "p. ex., Comandos de Sistema, Scripts Docker",
"folderColorLabel": "Cor da Pasta",
"folderIconLabel": "Ícone da Pasta",
"previewLabel": "Pré-visualização",
"folderNameFallback": "Nome da Pasta",
"createFolderButton": "Criar Pasta",
"targetTerminals": "Terminais de Destino",
"selectAll": "Todos",
"selectNone": "Nenhum",
"noTerminalTabsOpen": "Nenhum separador de terminal aberto",
"searchPlaceholder": "Pesquisar snippets...",
"newSnippet": "Novo Snippet",
"newFolder": "Nova Pasta",
"run": "Executar",
"noSnippetsInFolder": "Nenhum snippet nesta pasta",
"uncategorized": "Sem categoria",
"editSnippetTitle": "Editar Snippet",
"editSnippetDescription": "Atualizar este snippet de comando",
"saveSnippetButton": "Guardar Alterações",
"createSuccess": "Snippet criado com sucesso",
"createFailed": "Falha ao criar snippet",
"updateSuccess": "Snippet atualizado com sucesso",
"updateFailed": "Falha ao atualizar snippet",
"deleteFailed": "Falha ao eliminar snippet",
"folderCreateSuccess": "Pasta criada com sucesso",
"folderCreateFailed": "Falha ao criar pasta",
"editFolderTitle": "Editar Pasta",
"editFolderDescription": "Renomear ou alterar a aparência desta pasta",
"saveFolderButton": "Guardar Alterações",
"editFolder": "Editar pasta",
"deleteFolder": "Eliminar pasta",
"folderDeleteSuccess": "Pasta \"{{name}}\" eliminada",
"folderDeleteFailed": "Falha ao eliminar pasta",
"folderEditSuccess": "Pasta atualizada com sucesso",
"folderEditFailed": "Falha ao atualizar pasta",
"confirmRunMessage": "Executar \"{{name}}\"?",
"confirmRunButton": "Executar",
"runSuccess": "Executado \"{{name}}\" em {{count}} terminal(is)",
"pasteSuccess": "Colado \"{{name}}\" em {{count}} terminal(is)",
"pasteToTerminal": "Colar",
"copySuccess": "Copiado \"{{name}}\" para a área de transferência",
"shareTitle": "Partilhar Snippet",
"shareUser": "Utilizador",
"shareRole": "Função",
"selectUser": "Selecionar um utilizador...",
"selectRole": "Selecionar uma função...",
"shareSuccess": "Fragmento partilhado com sucesso",
"shareFailed": "Falha ao partilhar fragmento",
"revokeSuccess": "Acesso revogado",
"revokeFailed": "Falha ao revogar acesso",
"currentAccess": "Acesso Atual",
"shareLoadError": "Falha ao carregar dados de partilha",
"loading": "A carregar...",
"close": "Fechar",
"reorderFailed": "Falha ao guardar ordem dos fragmentos",
"importExport": "Importar / Exportar",
"exportBtn": "Exportar JSON",
"importBtn": "Importar JSON",
"exportSuccess": "Fragmentos exportados com sucesso",
"exportFailed": "Falha ao exportar fragmentos",
"importTitle": "Importar Fragmentos",
"importDescription": "Importar fragmentos e pastas de um ficheiro JSON exportado pelo Termix",
"importDropOrClick": "Arraste um ficheiro JSON aqui ou clique para procurar",
"importSelectedFile": "Selecionado: {{name}}",
"importOverwrite": "Substituir fragmentos existentes com o mesmo nome e pasta",
"importStartBtn": "Importar",
"importSuccess": "Importação concluída: {{snippets}} fragmento(s) adicionado(s), {{updated}} atualizado(s), {{skipped}} ignorado(s), {{folders}} pasta(s) adicionada(s)",
"importFailed": "Falha ao importar fragmentos",
"importInvalidFile": "Ficheiro inválido: esperado um objeto JSON com arrays de fragmentos ou pastas",
"targetHostsLabel": "Máquinas de Destino",
"targetHostsHint": "Atribuir máquinas para executar este fragmento diretamente sem um terminal aberto.",
"noHostsAvailable": "Nenhuma máquina configurada",
"clearTargetHosts": "Limpar tudo",
"hasTargetHosts": "Possui máquinas de destino",
"runOnTargets": "Executar nos Destinos",
"directRunSuccess": "Executou \"{{name}}\" em {{count}} máquina(s)",
"directRunPartialFail": "\"{{name}}\" falhou numa ou mais máquinas",
"executionResultTitle": "Resultados da Execução: {{name}}",
"executionResultDescription": "Saída da execução do fragmento em cada host de destino.",
"executionSuccess": "Sucesso",
"executionFailed": "Falhou",
"expandAll": "Expandir todas as pastas",
"collapseAll": "Recolher todas as pastas",
"settings": "Configurações de excertos de código",
"variablesHint": "Variáveis: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Executar \"{{name}}\"",
"variablesDialogDescription": "Preencha os valores necessários para este excerto de código antes de o executar.",
"variablesPreviewLabel": "Pré-visualização",
"variablesConfirmButton": "Correr"
},
"keybindings": {
"title": "Atalhos de teclado",
"description": "Personalize os atalhos de copiar, colar e controlar o terminal, ou associe teclas para enviar texto ou executar um trecho de código.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Clique e pressione as teclas.",
"recording": "Pressione as teclas...",
"comboRequiredError": "Pressione primeiro uma combinação de teclas",
"textRequiredError": "Introduza o texto que pretende enviar.",
"controlCodeRequiredError": "Introduza uma única letra para o código de controlo.",
"snippetRequiredError": "Selecione um excerto",
"conflictWarning": "Esta combinação já está ligada a {{combo}}. Guardar fará com que ambas disparem.",
"actionLabel": "Ação",
"actionCopy": "Copiar seleção",
"actionPaste": "Colar",
"actionSendControlCode": "Enviar sinal Ctrl+letra",
"actionSendText": "Enviar mensagem de texto",
"actionRunSnippet": "Executar excerto de código existente",
"actionNextTab": "Mudar para o separador seguinte",
"actionPreviousTab": "Alternar para o separador anterior",
"actionOpenCommandPalette": "Abrir pesquisa rápida",
"controlCodeLabel": "Carta",
"textLabel": "Texto a enviar",
"appendEnterLabel": "Prima Enter após enviar",
"snippetLabel": "Trecho",
"selectSnippetPlaceholder": "Selecione um excerto",
"orphanedSnippetWarning": "trecho não encontrado",
"clipboardPermissionNote": "Pode solicitar permissão para aceder à área de transferência na primeira utilização em alguns navegadores."
"actionOpenCommandPalette": "Abrir pesquisa rápida"
},
"userProfile": {
"donateTitle": "Mantenha o Termix ativo",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "O tópico é obrigatório",
"ntfyToken": "Token de acesso (opcional)",
"channelSaveFailed": "Falha ao guardar o canal"
},
"macros": {
"title": "Macros",
"description": "Execute uma série de comandos guardados num terminal e reaja ao que o servidor envia de volta.",
"target": "Objetivo",
"noTerminal": "Nenhum terminal aberto",
"empty": "Ainda não existem macros.",
"emptyHint": "Crie uma macro para automatizar um fluxo de trabalho no terminal, como por exemplo, iniciar sessão, reiniciar um serviço ou responder a pedidos.",
"create": "Nova macro",
"back": "Voltar às macros",
"name": "Nome",
"namePlaceholder": "Reinicie o servidor web.",
"nameRequired": "Dê um nome à macro antes de guardar.",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "O que faz esta macro (opcional)",
"unsaved": "Alterações não guardadas",
"save": "Guardar",
"saved": "Macro salvo",
"saveFailed": "Não foi possível guardar a macro.",
"delete": "Eliminar",
"deleteTitle": "Apagar esta macro?",
"deleteBody": "\"{{name}}\" será eliminado permanentemente. Esta ação não pode ser anulada.",
"deleted": "Macro excluída",
"cancel": "Cancelar",
"run": "Correr",
"stop": "Parar",
"running": "Corrida...",
"completed": "Macro finalizado",
"failed": "A macro falhou",
"loadFailed": "Não foi possível carregar as macros",
"needTerminal": "Primeiro, abra e ligue um terminal.",
"noSteps": "Esta macro ainda não tem etapas. Adicione uma abaixo.",
"addStep": "Adicionar etapa",
"stepNumber": "Passo {{number}}",
"removeStep": "Remover etapa",
"confirmRunTitle": "Executar esta macro?",
"confirmRunBody": "\"{{name}}\" enviará comandos para {{host}}.",
"confirmRunAction": "Executar macro",
"stepTypes": {
"send": {
"label": "Enviar comando",
"help": "Digita o texto no terminal, da mesma forma que se o digitasse."
},
"wait": {
"label": "Aguarde a saída",
"help": "Aguarda até que o servidor imprima o texto que introduziu, como por exemplo um pedido de palavra-passe."
},
"delay": {
"label": "Pausa",
"help": "Aguarda um período de tempo fixo antes da etapa seguinte."
},
"if": {
"label": "Se a saída contiver",
"help": "Verifica tudo o que o servidor imprimiu até à data e, em seguida, executa uma das duas ramificações."
},
"repeat": {
"label": "Repetir",
"help": "Executa os passos internos um número definido de vezes."
}
},
"send": {
"text": "Comando ou texto",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Prima Enter após enviar",
"pressEnterHelp": "Desative esta opção para digitar texto sem o enviar."
},
"wait": {
"pattern": "Aguarde até que a saída contenha",
"patternPlaceholder": "palavra-passe:",
"timeout": "Desistir depois",
"onTimeout": "Se nunca aparecer",
"stop": "Pare a macro",
"continue": "Continue mesmo assim"
},
"delay": {
"duration": "Aguarde"
},
"ifStep": {
"pattern": "Se a saída contiver",
"patternPlaceholder": "Ativo: ativo (em execução)",
"then": "Então faça isso",
"else": "Caso contrário, faça-o",
"emptyBranch": "Sem degraus"
},
"repeat": {
"count": "Número de vezes",
"times": "vezes"
},
"match": {
"useRegex": "Combinar com uma expressão regular",
"useRegexHelp": "Deixe este campo em branco para que o texto corresponda exatamente ao que foi escrito.",
"invalidRegex": "Expressão regular inválida; será interpretada como texto simples."
},
"seconds": "segundos",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Setări bară laterală...",
"hosts": "Gazde",
"snippets": "Fragmente",
"macros": "Macrocomenzi",
"hostManager": "Manager de gazdă",
"credentials": "Acreditări",
"connections": "Conexiuni",
@@ -869,7 +868,6 @@
"groupTags": "Etichete și pinuri",
"groupTunnels": "Tuneluri",
"groupJumpHosts": "Gazde de salt",
"groupQuickActions": "Acțiuni rapide",
"groupFeatureFlags": "Steaguri de caracteristici",
"groupAdvanced": "Configurație avansată",
"preview": "Previzualizare",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Stocarea cheilor SSH sau a parolelor",
"tmuxMonitor": "Monitor Tmux",
"tmuxMonitorDesc": "Monitorizați sesiunile tmux pe toate gazdele dvs.",
"snippets": "Fragmente",
"runSnippetDesc": "Execută în fila activă a terminalului",
"noSnippetsFound": "Nu s-au găsit fragmente care să corespundă cu „{{search}}”",
"recentActivity": "Activitate recentă",
"serversAndHosts": "Servere și gazde",
"noHostsFound": "Nu s-au găsit gazde care să corespundă cu „{{search}}”",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Fila anterioară",
"hotkeysJumpTab": "Salt la filă"
},
"snippets": {
"title": "Fragmente",
"createSnippetTitle": "Creați fragment",
"createSnippetDescription": "Creați un nou fragment de comandă pentru execuție rapidă",
"nameLabel": "Nume",
"namePlaceholder": "De exemplu, reporniți Nginx",
"descriptionLabel": "Descriere",
"descriptionPlaceholder": "Descriere opțională",
"optional": "Opțional",
"folderLabel": "Pliant",
"noFolder": "Niciun dosar (Necategorizat)",
"commandLabel": "Comanda",
"commandPlaceholder": "de exemplu, sudo systemctl restart nginx",
"typeLabel": "Tip",
"typeCommand": "Comanda",
"typeNote": "Nota",
"noteLabel": "Nota",
"notePlaceholder": "de exemplu, un mesaj sau un text de reamintire reutilizabil",
"cancel": "Anula",
"createSnippetButton": "Creați fragment",
"createFolderTitle": "Creați un folder",
"createFolderDescription": "Organizați fragmentele în foldere",
"folderNameLabel": "Nume folder",
"folderNamePlaceholder": "de exemplu, comenzi de sistem, scripturi Docker",
"folderColorLabel": "Culoarea folderului",
"folderIconLabel": "Pictogramă folder",
"previewLabel": "Previzualizare",
"folderNameFallback": "Nume folder",
"createFolderButton": "Creați folderul",
"targetTerminals": "Terminale țintă",
"selectAll": "Toate",
"selectNone": "Nici unul",
"noTerminalTabsOpen": "Nicio filă de terminal deschisă",
"searchPlaceholder": "Fragmente de căutare...",
"newSnippet": "Fragment nou",
"newFolder": "Dosar nou",
"run": "Aleargă",
"noSnippetsInFolder": "Niciun fragment în acest dosar",
"uncategorized": "Necategorizat",
"editSnippetTitle": "Editați fragmentul",
"editSnippetDescription": "Actualizați acest fragment de comandă",
"saveSnippetButton": "Salvați modificările",
"createSuccess": "Fragmentul a fost creat cu succes",
"createFailed": "Nu s-a putut crea fragmentul",
"updateSuccess": "Fragment actualizat cu succes",
"updateFailed": "Fragmentul nu a putut fi actualizat",
"deleteFailed": "Nu s-a putut șterge fragmentul",
"folderCreateSuccess": "Dosarul a fost creat cu succes",
"folderCreateFailed": "Nu s-a putut crea folderul",
"editFolderTitle": "Editați folderul",
"editFolderDescription": "Redenumiți sau modificați aspectul acestui folder",
"saveFolderButton": "Salvați modificările",
"editFolder": "Editați folderul",
"deleteFolder": "Ștergeți folderul",
"folderDeleteSuccess": "Dosarul „{{name}}” a fost șters",
"folderDeleteFailed": "Ștergerea folderului a eșuat",
"folderEditSuccess": "Dosarul a fost actualizat cu succes",
"folderEditFailed": "Nu s-a putut actualiza folderul",
"confirmRunMessage": "Executați „{{name}}”?",
"confirmRunButton": "Aleargă",
"runSuccess": "A rulat „{{name}}” în terminalul(ele) {{count}}",
"pasteSuccess": "„{{name}}” a fost lipit în terminalul(ele) {{count}}",
"pasteToTerminal": "Pastă",
"copySuccess": "„{{name}}” a fost copiat în clipboard",
"shareTitle": "Partajați fragmentul",
"shareUser": "Utilizator",
"shareRole": "Rol",
"selectUser": "Selectați un utilizator...",
"selectRole": "Selectați un rol...",
"shareSuccess": "Fragmentul a fost partajat cu succes",
"shareFailed": "Nu s-a putut partaja fragmentul",
"revokeSuccess": "Acces revocat",
"revokeFailed": "Nu s-a putut revoca accesul",
"currentAccess": "Acces curent",
"shareLoadError": "Nu s-au putut încărca datele de partajare",
"loading": "Încărcare...",
"close": "Aproape",
"reorderFailed": "Nu s-a putut salva ordinea fragmentelor.",
"importExport": "Import / Export",
"exportBtn": "Export JSON",
"importBtn": "Import JSON",
"exportSuccess": "Fragmentele au fost exportate cu succes",
"exportFailed": "Exportul fragmentelor a eșuat",
"importTitle": "Importați fragmente",
"importDescription": "Importați fragmente și foldere dintr-un fișier JSON exportat de Termix",
"importDropOrClick": "Plasați un fișier JSON aici sau faceți clic pentru a răsfoi",
"importSelectedFile": "Selectat: {{name}}",
"importOverwrite": "Suprascrie fragmentele existente cu același nume și folder",
"importStartBtn": "Import",
"importSuccess": "Import finalizat: {{snippets}} fragment(e) adăugat(e), {{updated}} actualizat, {{skipped}} omis, {{folders}} folder(e) adăugat(e)",
"importFailed": "Importarea fragmentelor a eșuat",
"importInvalidFile": "Fișier nevalid: se aștepta un obiect JSON cu fragmente de date sau tablouri de foldere",
"targetHostsLabel": "Gazde țintă",
"targetHostsHint": "Atribuiți gazde pentru a rula acest fragment direct, fără un terminal deschis.",
"noHostsAvailable": "Nicio gazdă configurată",
"clearTargetHosts": "Ștergeți tot",
"hasTargetHosts": "Are gazde țintă",
"runOnTargets": "Executare pe ținte",
"directRunSuccess": "A rulat „{{name}}” pe gazdă(e) {{count}}",
"directRunPartialFail": "„{{name}}” a eșuat pe una sau mai multe gazde",
"executionResultTitle": "Rezultatele execuției: {{name}}",
"executionResultDescription": "Rezultatul rulării fragmentului de cod pe fiecare gazdă țintă.",
"executionSuccess": "Succes",
"executionFailed": "Eșuat",
"expandAll": "Extindeți toate folderele",
"collapseAll": "Restrângeți toate folderele",
"settings": "Setări fragment",
"variablesHint": "Variabile: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Etichetă}",
"variablesDialogTitle": "Executați „{{name}}”",
"variablesDialogDescription": "Completați valorile necesare acestui fragment de cod înainte de a-l rula.",
"variablesPreviewLabel": "Previzualizare",
"variablesConfirmButton": "Aleargă"
},
"keybindings": {
"title": "Comenzi rapide de la tastatură",
"description": "Personalizați comenzile rapide de copiere, lipire și control din terminal sau asociați taste pentru a trimite text sau a rula un fragment de cod.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Faceți clic și apăsați tastele",
"recording": "Apăsați tastele...",
"comboRequiredError": "Apăsați mai întâi o combinație de taste",
"textRequiredError": "Introduceți textul de trimis",
"controlCodeRequiredError": "Introduceți o singură literă pentru codul de control",
"snippetRequiredError": "Selectați un fragment",
"conflictWarning": "Această combinație este deja legată de {{combo}}. Salvarea va face ca ambele să se declanșeze.",
"actionLabel": "Acţiune",
"actionCopy": "Copiați selecția",
"actionPaste": "Pastă",
"actionSendControlCode": "Trimite semnal Ctrl+literă",
"actionSendText": "Trimiteți mesaj text",
"actionRunSnippet": "Execută fragmentul existent",
"actionNextTab": "Comutare la fila următoare",
"actionPreviousTab": "Comutare la fila anterioară",
"actionOpenCommandPalette": "Deschide căutarea rapidă",
"controlCodeLabel": "Scrisoare",
"textLabel": "Text de trimis",
"appendEnterLabel": "Apăsați Enter după trimitere",
"snippetLabel": "Fragment",
"selectSnippetPlaceholder": "Selectați un fragment",
"orphanedSnippetWarning": "fragmentul nu a fost găsit",
"clipboardPermissionNote": "În unele browsere, este posibil să solicite permisiunea pentru clipboard la prima utilizare."
"actionOpenCommandPalette": "Deschide căutarea rapidă"
},
"userProfile": {
"donateTitle": "Menține Termix în viață",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Subiectul este obligatoriu",
"ntfyToken": "Token de acces (opțional)",
"channelSaveFailed": "Canalul nu a putut fi salvat."
},
"macros": {
"title": "Macrocomenzi",
"description": "Executați o serie de comenzi salvate într-un terminal și reacționați la ceea ce serverul trimite înapoi.",
"target": "Ţintă",
"noTerminal": "Niciun terminal deschis",
"empty": "Încă nu există macrocomenzi",
"emptyHint": "Creați o macrocomandă pentru a automatiza un flux de lucru în terminal, cum ar fi conectarea, repornirea unui serviciu sau răspunsul la solicitări.",
"create": "Macrocomandă nouă",
"back": "Înapoi la macrocomenzi",
"name": "Nume",
"namePlaceholder": "Reporniți serverul web",
"nameRequired": "Dați un nume macrocomenzii înainte de salvare",
"descriptionLabel": "Descriere",
"descriptionPlaceholder": "Ce face această macrocomandă (opțional)",
"unsaved": "Modificări nesalvate",
"save": "Salva",
"saved": "Macrocomandă salvată",
"saveFailed": "Nu s-a putut salva macrocomanda",
"delete": "Şterge",
"deleteTitle": "Ștergeți această macrocomandă?",
"deleteBody": "„{{name}}” va fi șters definitiv. Această acțiune nu poate fi anulată.",
"deleted": "Macrocomandă ștearsă",
"cancel": "Anula",
"run": "Aleargă",
"stop": "Stop",
"running": "Funcţionare...",
"completed": "Macrocomandă finalizată",
"failed": "Macrocomanda a eșuat",
"loadFailed": "Nu s-au putut încărca macrocomenzile",
"needTerminal": "Deschideți și conectați mai întâi un terminal",
"noSteps": "Această macrocomandă nu are încă pași. Adăugați unul mai jos.",
"addStep": "Adăugați un pas",
"stepNumber": "Pasul {{number}}",
"removeStep": "Eliminați pasul",
"confirmRunTitle": "Rulați această macrocomandă?",
"confirmRunBody": "„{{name}}” va trimite comenzi către {{host}}.",
"confirmRunAction": "Executare macrocomandă",
"stepTypes": {
"send": {
"label": "Trimiteți comanda",
"help": "Introduce text în terminal, la fel ca și cum l-ai fi tastat."
},
"wait": {
"label": "Așteptați ieșirea",
"help": "Se întrerupe până când serverul afișează textul pe care îl denumiți, ca o solicitare de parolă."
},
"delay": {
"label": "Pauză",
"help": "Așteaptă o perioadă fixă de timp înainte de pasul următor."
},
"if": {
"label": "Dacă ieșirea conține",
"help": "Verifică tot ce a imprimat serverul până acum, apoi rulează una dintre cele două ramificații."
},
"repeat": {
"label": "Repeta",
"help": "Execută pașii din interiorul său de un număr stabilit de ori."
}
},
"send": {
"text": "Comandă sau text",
"textPlaceholder": "repornirea systemctl a nginx",
"pressEnter": "Apăsați Enter după trimitere",
"pressEnterHelp": "Dezactivați această opțiune pentru a introduce text fără a-l trimite."
},
"wait": {
"pattern": "Așteptați până când ieșirea conține",
"patternPlaceholder": "parolă:",
"timeout": "Renunță după",
"onTimeout": "Dacă nu apare niciodată",
"stop": "Opriți macrocomanda",
"continue": "Continuă oricum"
},
"delay": {
"duration": "Așteptați"
},
"ifStep": {
"pattern": "Dacă ieșirea conține",
"patternPlaceholder": "Activ: activ (alergând)",
"then": "Apoi fă asta",
"else": "Altfel, fă asta",
"emptyBranch": "Fără trepte"
},
"repeat": {
"count": "Numărul de ori",
"times": "ori"
},
"match": {
"useRegex": "Potrivire cu o expresie regulată",
"useRegexHelp": "Omiteți această opțiune pentru a corespunde exact textului așa cum este scris.",
"invalidRegex": "Nu este o expresie regulată validă, va fi găsită ca text simplu."
},
"seconds": "secunde",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Настройки боковой панели...",
"hosts": "Хосты",
"snippets": "Сниппеты",
"macros": "Макросы",
"hostManager": "Менеджер хостов",
"credentials": "Учетные данные",
"connections": "Подключения",
@@ -869,7 +868,6 @@
"groupTags": "Теги и значок",
"groupTunnels": "Туннели",
"groupJumpHosts": "Переключение хостов",
"groupQuickActions": "Быстрые действия",
"groupFeatureFlags": "Флаги функций",
"groupAdvanced": "Расширенные настройки",
"preview": "Предварительный просмотр",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Хранение SSH-ключей или паролей",
"tmuxMonitor": "Мониторинг Tmux",
"tmuxMonitorDesc": "Мониторинг сессий tmux на ваших хостах",
"snippets": "Фрагменты",
"runSnippetDesc": "Запустите в активной вкладке терминала.",
"noSnippetsFound": "Фрагменты, соответствующие \"{{search}}\", не найдены.",
"recentActivity": "Недавняя активность",
"serversAndHosts": "Серверы и хосты",
"noHostsFound": "Не найдено хостов, соответствующих \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Предыдущая вкладка",
"hotkeysJumpTab": "Перейти на вкладку"
},
"snippets": {
"title": "Сниппеты",
"createSnippetTitle": "Создать сниппет",
"createSnippetDescription": "Создать новый сниппет команды для быстрого выполнения",
"nameLabel": "Название",
"namePlaceholder": "например, Перезапустить Nginx",
"descriptionLabel": "Описание",
"descriptionPlaceholder": "Необязательное описание",
"optional": "Необязательно",
"folderLabel": "Папка",
"noFolder": "Нет папки (Без категории)",
"commandLabel": "Команда",
"commandPlaceholder": "например, sudo systemctl restart nginx",
"typeLabel": "Тип",
"typeCommand": "Командование",
"typeNote": "Примечание",
"noteLabel": "Примечание",
"notePlaceholder": "например, многоразовый текст-подсказка или напоминание.",
"cancel": "Отмена",
"createSnippetButton": "Создать сниппет",
"createFolderTitle": "Создать папку",
"createFolderDescription": "Организуйте сниппеты по папкам",
"folderNameLabel": "Название папки",
"folderNamePlaceholder": "например, Системные команды, Скрипты Docker",
"folderColorLabel": "Цвет папки",
"folderIconLabel": "Значок папки",
"previewLabel": "Предпросмотр",
"folderNameFallback": "Название папки",
"createFolderButton": "Создать папку",
"targetTerminals": "Целевые терминалы",
"selectAll": "Все",
"selectNone": "Ничего",
"noTerminalTabsOpen": "Нет открытых вкладок терминала",
"searchPlaceholder": "Поиск сниппетов...",
"newSnippet": "Новый сниппет",
"newFolder": "Новая папка",
"run": "Запустить",
"noSnippetsInFolder": "В этой папке нет сниппетов",
"uncategorized": "Без категории",
"editSnippetTitle": "Редактировать сниппет",
"editSnippetDescription": "Обновить этот командный сниппет",
"saveSnippetButton": "Сохранить изменения",
"createSuccess": "Сниппет успешно создан",
"createFailed": "Не удалось создать сниппет",
"updateSuccess": "Сниппет успешно обновлён",
"updateFailed": "Не удалось обновить сниппет",
"deleteFailed": "Не удалось удалить сниппет",
"folderCreateSuccess": "Папка успешно создана",
"folderCreateFailed": "Не удалось создать папку",
"editFolderTitle": "Редактировать папку",
"editFolderDescription": "Переименовать или изменить внешний вид этой папки",
"saveFolderButton": "Сохранить изменения",
"editFolder": "Редактировать папку",
"deleteFolder": "Удалить папку",
"folderDeleteSuccess": "Папка \"{{name}}\" удалена",
"folderDeleteFailed": "Не удалось удалить папку",
"folderEditSuccess": "Папка успешно обновлена",
"folderEditFailed": "Не удалось обновить папку",
"confirmRunMessage": "Запустить \"{{name}}\"?",
"confirmRunButton": "Запустить",
"runSuccess": "Запущен \"{{name}}\" в {{count}} терминале(ах)",
"pasteSuccess": "Вставлено \"{{name}}\" в {{count}} терминал(ы)",
"pasteToTerminal": "Вставить",
"copySuccess": "Сниппет \"{{name}}\" скопирован в буфер обмена",
"shareTitle": "Поделиться сниппетом",
"shareUser": "Пользователь",
"shareRole": "Роль",
"selectUser": "Выберите пользователя...",
"selectRole": "Выберите роль...",
"shareSuccess": "Сниппет успешно предоставлен в общий доступ",
"shareFailed": "Не удалось предоставить сниппет в общий доступ",
"revokeSuccess": "Доступ отозван",
"revokeFailed": "Не удалось отозвать доступ",
"currentAccess": "Текущий доступ",
"shareLoadError": "Не удалось загрузить данные общего доступа",
"loading": "Загрузка...",
"close": "Закрыть",
"reorderFailed": "Не удалось сохранить порядок сниппетов",
"importExport": "Импорт / Экспорт",
"exportBtn": "Экспорт JSON",
"importBtn": "Импорт JSON",
"exportSuccess": "Сниппеты успешно экспортированы",
"exportFailed": "Не удалось экспортировать сниппеты",
"importTitle": "Импорт сниппетов",
"importDescription": "Импорт сниппетов и папок из JSON-файла, экспортированного Termix",
"importDropOrClick": "Перетащите JSON-файл сюда или нажмите, чтобы выбрать",
"importSelectedFile": "Выбрано: {{name}}",
"importOverwrite": "Перезаписывать существующие сниппеты с одинаковыми именами и папками",
"importStartBtn": "Импортировать",
"importSuccess": "Импорт завершён: добавлено сниппетов: {{snippets}}, обновлено: {{updated}}, пропущено: {{skipped}}, добавлено папок: {{folders}}",
"importFailed": "Не удалось импортировать сниппеты",
"importInvalidFile": "Неверный файл: ожидается JSON-объект с массивами snippets или folders",
"targetHostsLabel": "Целевые хосты",
"targetHostsHint": "Назначьте хосты для запуска этого сниппета напрямую, без открытого терминала.",
"noHostsAvailable": "Нет настроенных хостов",
"clearTargetHosts": "Очистить всё",
"hasTargetHosts": "Имеет целевые хосты",
"runOnTargets": "Запустить на целевых хостах",
"directRunSuccess": "Запущен \"{{name}}\" на {{count}} хосте(ах)",
"directRunPartialFail": "\"{{name}}\" не удалось запустить на одном или нескольких хостах",
"executionResultTitle": "Результаты выполнения: {{name}}",
"executionResultDescription": "Вывод выполнения сниппета на каждом целевом хосте.",
"executionSuccess": "Успех",
"executionFailed": "Ошибка",
"expandAll": "Развернуть все папки",
"collapseAll": "Свернуть все папки",
"settings": "Настройки фрагмента",
"variablesHint": "Переменные: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Запуск \"{{name}}\"",
"variablesDialogDescription": "Перед запуском заполните необходимые для этого фрагмента значения.",
"variablesPreviewLabel": "Предварительный просмотр",
"variablesConfirmButton": "Бегать"
},
"keybindings": {
"title": "Клавиатурные сочетания",
"description": "Настройте сочетания клавиш для копирования, вставки и управления в терминале, а также назначьте клавиши для отправки текста или запуска фрагмента кода.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Нажмите и удерживайте клавиши.",
"recording": "Нажимайте клавиши...",
"comboRequiredError": "Сначала нажмите комбинацию клавиш.",
"textRequiredError": "Введите текст для отправки",
"controlCodeRequiredError": "Введите одну букву в качестве управляющего кода.",
"snippetRequiredError": "Выберите фрагмент",
"conflictWarning": "Эта комбинация уже привязана к {{combo}}. Сохранение активирует обе комбинации.",
"actionLabel": "Действие",
"actionCopy": "Скопировать выделенный фрагмент",
"actionPaste": "Вставить",
"actionSendControlCode": "Отправить сигнал Ctrl+буква",
"actionSendText": "Отправить текст",
"actionRunSnippet": "Запустить существующий фрагмент",
"actionNextTab": "Перейти к следующей вкладке",
"actionPreviousTab": "Перейти на предыдущую вкладку",
"actionOpenCommandPalette": "Открыть быстрый поиск",
"controlCodeLabel": "Письмо",
"textLabel": "Текст для отправки",
"appendEnterLabel": "Нажмите Enter после отправки",
"snippetLabel": "Фрагмент",
"selectSnippetPlaceholder": "Выберите фрагмент",
"orphanedSnippetWarning": "фрагмент не найден",
"clipboardPermissionNote": "В некоторых браузерах при первом использовании может запрашиваться разрешение на использование буфера обмена."
"actionOpenCommandPalette": "Открыть быстрый поиск"
},
"userProfile": {
"donateTitle": "Поддержите Termix",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Тема обязательна",
"ntfyToken": "Токен доступа (необязательно)",
"channelSaveFailed": "Не удалось сохранить канал"
},
"macros": {
"title": "Макросы",
"description": "Выполните сохраненную последовательность команд в терминале и отреагируйте на то, что сервер отправит вам в ответ.",
"target": "Цель",
"noTerminal": "Терминал не открыт",
"empty": "Макросы пока отсутствуют",
"emptyHint": "Создайте макрос для автоматизации действий в терминале, таких как вход в систему, перезапуск службы или ответ на запросы.",
"create": "Новый макрос",
"back": "Вернуться к макроэлементам",
"name": "Имя",
"namePlaceholder": "Перезапустите веб-сервер",
"nameRequired": "Перед сохранением дайте макросу имя.",
"descriptionLabel": "Описание",
"descriptionPlaceholder": "Что делает этот макрос (необязательно)",
"unsaved": "Несохраненные изменения",
"save": "Сохранять",
"saved": "Макрос сохранен",
"saveFailed": "Не удалось сохранить макрос.",
"delete": "Удалить",
"deleteTitle": "Удалить этот макрос?",
"deleteBody": "\"{{name}}\" будет удалено навсегда. Этого нельзя будет отменить.",
"deleted": "Макрос удален",
"cancel": "Отмена",
"run": "Бегать",
"stop": "Останавливаться",
"running": "Бег...",
"completed": "Макро завершен",
"failed": "Макрос не сработал",
"loadFailed": "Не удалось загрузить макросы.",
"needTerminal": "Сначала откройте и подключите терминал.",
"noSteps": "В этом макросе пока нет шагов. Добавьте один ниже.",
"addStep": "Добавить шаг",
"stepNumber": "Шаг {{number}}",
"removeStep": "Удалить шаг",
"confirmRunTitle": "Запустить этот макрос?",
"confirmRunBody": "\"{{name}}\" будет отправлять команды {{host}}.",
"confirmRunAction": "Запустить макрос",
"stepTypes": {
"send": {
"label": "Отправить команду",
"help": "Вводит текст в терминал так же, как если бы вы его ввели вручную."
},
"wait": {
"label": "Дождитесь вывода",
"help": "Приостанавливается до тех пор, пока сервер не выведет указанный вами текст, как запрос пароля."
},
"delay": {
"label": "Пауза",
"help": "Перед следующим шагом выполняется ожидание в течение заданного промежутка времени."
},
"if": {
"label": "Если вывод содержит",
"help": "Проверяет все данные, выведенные сервером на экран, а затем запускает одну из двух ветвей выполнения."
},
"repeat": {
"label": "Повторить",
"help": "Выполняет все шаги, входящие в его состав, заданное количество раз."
}
},
"send": {
"text": "Команда или текст",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Нажмите Enter после отправки",
"pressEnterHelp": "Отключите эту функцию, чтобы вводить текст без отправки."
},
"wait": {
"pattern": "Дождитесь, пока на выходе не появится",
"patternPlaceholder": "пароль:",
"timeout": "Сдаться после",
"onTimeout": "Если оно никогда не появится",
"stop": "Остановите макрос",
"continue": "Продолжайте в любом случае."
},
"delay": {
"duration": "Подождите"
},
"ifStep": {
"pattern": "Если в выходных данных содержится",
"patternPlaceholder": "Активный: активен (работает)",
"then": "Затем сделайте это.",
"else": "В противном случае сделайте это.",
"emptyBranch": "Без ступенек"
},
"repeat": {
"count": "Количество раз",
"times": "времена"
},
"match": {
"useRegex": "Сопоставление с использованием регулярного выражения",
"useRegexHelp": "Оставьте этот параметр отключенным, чтобы текст точно соответствовал написанному.",
"invalidRegex": "Не является допустимым регулярным выражением, будет сопоставлено как обычный текст."
},
"seconds": "секунд",
"secondsShort": "{{count}}с"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Подешавања бочне траке...",
"hosts": "Домаћини",
"snippets": "Исечци",
"macros": "Макрои",
"hostManager": "Менаџер домаћина",
"credentials": "Акредитиви",
"connections": "Везе",
@@ -869,7 +868,6 @@
"groupTags": "Ознаке и пин",
"groupTunnels": "Тунели",
"groupJumpHosts": "Јумп хостови",
"groupQuickActions": "Брзе акције",
"groupFeatureFlags": "Заставице функција",
"groupAdvanced": "Напредна конфигурација",
"preview": "Преглед",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Чувајте SSH кључеве или лозинке",
"tmuxMonitor": "Tmux монитор",
"tmuxMonitorDesc": "Пратите tmux сесије на свим вашим хостовима",
"snippets": "Исечци",
"runSnippetDesc": "Покрени у активној картици терминала",
"noSnippetsFound": "Није пронађен ниједан фрагмент који одговара „{{search}}“",
"recentActivity": "Недавне активности",
"serversAndHosts": "Сервери и хостови",
"noHostsFound": "Није пронађен ниједан хост који одговара „{{search}}“",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Претходна картица",
"hotkeysJumpTab": "Пређи на картицу"
},
"snippets": {
"title": "Исечци",
"createSnippetTitle": "Направи исечак",
"createSnippetDescription": "Направите нови исечак команде за брзо извршавање",
"nameLabel": "Име",
"namePlaceholder": "нпр. Рестартујте Nginx",
"descriptionLabel": "Опис",
"descriptionPlaceholder": "Опциони опис",
"optional": "Опционо",
"folderLabel": "Фасцикла",
"noFolder": "Нема фасцикле (Некатегоризовано)",
"commandLabel": "Команда",
"commandPlaceholder": "нпр. sudo systemctl рестарт nginx",
"typeLabel": "Тип",
"typeCommand": "Команда",
"typeNote": "Напомена",
"noteLabel": "Напомена",
"notePlaceholder": "нпр. текст подсетника или упута за вишекратну употребу",
"cancel": "Откажи",
"createSnippetButton": "Направи исечак",
"createFolderTitle": "Направи фасциклу",
"createFolderDescription": "Организујте своје фрагменте у фасцикле",
"folderNameLabel": "Назив фасцикле",
"folderNamePlaceholder": "нпр. системске команде, Докер скрипте",
"folderColorLabel": "Боја фасцикле",
"folderIconLabel": "Икона фасцикле",
"previewLabel": "Преглед",
"folderNameFallback": "Назив фасцикле",
"createFolderButton": "Направи фасциклу",
"targetTerminals": "Циљни терминали",
"selectAll": "Сви",
"selectNone": "Ниједан",
"noTerminalTabsOpen": "Нема отворених картица терминала",
"searchPlaceholder": "Претражи фрагменте...",
"newSnippet": "Нови исечак",
"newFolder": "Нова фасцикла",
"run": "Трчи",
"noSnippetsInFolder": "Нема фрагмената у овој фасцикли",
"uncategorized": "Некатегоризовано",
"editSnippetTitle": "Измени исечак",
"editSnippetDescription": "Ажурирајте овај исечак команде",
"saveSnippetButton": "Сачувај измене",
"createSuccess": "Исечак је успешно креиран",
"createFailed": "Није успело креирање фрагмента",
"updateSuccess": "Исечак је успешно ажуриран",
"updateFailed": "Ажурирање исечка није успело",
"deleteFailed": "Брисање исечка није успело",
"folderCreateSuccess": "Фолдер је успешно креиран",
"folderCreateFailed": "Није успело креирање фасцикле",
"editFolderTitle": "Уреди фасциклу",
"editFolderDescription": "Преименујте или промените изглед ове фасцикле",
"saveFolderButton": "Сачувај измене",
"editFolder": "Уреди фасциклу",
"deleteFolder": "Обриши фасциклу",
"folderDeleteSuccess": "Фасцикла „{{name}}“ је обрисана",
"folderDeleteFailed": "Брисање фасцикле није успело",
"folderEditSuccess": "Фолдер је успешно ажуриран",
"folderEditFailed": "Ажурирање фасцикле није успело",
"confirmRunMessage": "Покрени „{{name}}“?",
"confirmRunButton": "Трчи",
"runSuccess": "Покренуо је „{{name}}“ у {{count}} терминалу(има)",
"pasteSuccess": "Налепљено је „{{name}}“ у {{count}} терминал(е)",
"pasteToTerminal": "Залепи",
"copySuccess": "Копирано „{{name}}“ у међуспремник",
"shareTitle": "Дели исечак",
"shareUser": "Корисник",
"shareRole": "Улога",
"selectUser": "Изаберите корисника...",
"selectRole": "Изаберите улогу...",
"shareSuccess": "Исечак је успешно дељен",
"shareFailed": "Дељење фрагмента није успело",
"revokeSuccess": "Приступ је опозван",
"revokeFailed": "Опозивање приступа није успело",
"currentAccess": "Тренутни приступ",
"shareLoadError": "Учитавање података о дељењу није успело",
"loading": "Учитавање...",
"close": "Затвори",
"reorderFailed": "Чување редоследа исечака није успело",
"importExport": "Увоз / Извоз",
"exportBtn": "Извези JSON",
"importBtn": "Увези JSON",
"exportSuccess": "Исечци су успешно извезени",
"exportFailed": "Извоз фрагмената није успео",
"importTitle": "Увези фрагменте",
"importDescription": "Увезите фрагменте и фасцикле из JSON датотеке коју је извезао Termix",
"importDropOrClick": "Отпустите JSON датотеку овде или кликните да бисте прегледали",
"importSelectedFile": "Изабрано: {{name}}",
"importOverwrite": "Пребришите постојеће фрагменте истим именом и фасциклом",
"importStartBtn": "Увоз",
"importSuccess": "Увоз завршен: додато {{snippets}} фрагмената, ажурирано {{updated}} , прескочено {{skipped}} , додато {{folders}} фасцикли",
"importFailed": "Увоз фрагмената није успео",
"importInvalidFile": "Неважећа датотека: очекиван је JSON објекат са исечцима или низовима фасцикли",
"targetHostsLabel": "Циљни хостови",
"targetHostsHint": "Доделите хостове да покрену овај исечак директно без отвореног терминала.",
"noHostsAvailable": "Ниједан хост није конфигурисан",
"clearTargetHosts": "Обриши све",
"hasTargetHosts": "Има циљне хостове",
"runOnTargets": "Трчи ка циљевима",
"directRunSuccess": "Покренуто „{{name}}“ на {{count}} хосту(има)",
"directRunPartialFail": "„{{name}}“ није успело на једном или више хостова",
"executionResultTitle": "Резултати извршења: {{name}}",
"executionResultDescription": "Излаз из покретања фрагмента кода на сваком циљном хосту.",
"executionSuccess": "Успех",
"executionFailed": "Неуспешно",
"expandAll": "Прошири све фасцикле",
"collapseAll": "Скупи све фасцикле",
"settings": "Подешавања исечка",
"variablesHint": "Променљиве: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Ознака}",
"variablesDialogTitle": "Покрени „{{name}}“",
"variablesDialogDescription": "Унесите вредности које су потребне овом фрагменту пре него што га покренете.",
"variablesPreviewLabel": "Преглед",
"variablesConfirmButton": "Трчи"
},
"keybindings": {
"title": "Пречице на тастатури",
"description": "Прилагодите пречице за копирање, лепљење и контролу терминала или повежите тастере за слање текста или покретање исечка кода.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Кликните и притисните тастере",
"recording": "Притисните тастере...",
"comboRequiredError": "Прво притисните комбинацију тастера",
"textRequiredError": "Унесите текст за слање",
"controlCodeRequiredError": "Унесите једно слово за контролни код",
"snippetRequiredError": "Изаберите исечак",
"conflictWarning": "Ова комбинација је већ повезана са {{combo}}. Чување ће активирати обе.",
"actionLabel": "Акција",
"actionCopy": "Копирај избор",
"actionPaste": "Залепи",
"actionSendControlCode": "Пошаљи сигнал Ctrl+слово",
"actionSendText": "Пошаљи текст",
"actionRunSnippet": "Покрени постојећи исечак кода",
"actionNextTab": "Пређи на следећу картицу",
"actionPreviousTab": "Пређи на претходну картицу",
"actionOpenCommandPalette": "Отвори брзу претрагу",
"controlCodeLabel": "Писмо",
"textLabel": "Текст за слање",
"appendEnterLabel": "Притисните Ентер након слања",
"snippetLabel": "Исечак",
"selectSnippetPlaceholder": "Изаберите исечак",
"orphanedSnippetWarning": "фрагмент није пронађен",
"clipboardPermissionNote": "Може затражити дозволу за међуспремник при првој употреби у неким прегледачима."
"actionOpenCommandPalette": "Отвори брзу претрагу"
},
"userProfile": {
"donateTitle": "Нека Термикс остане жив",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Тема је обавезна",
"ntfyToken": "Приступни токен (опционо)",
"channelSaveFailed": "Чување канала није успело"
},
"macros": {
"title": "Макрои",
"description": "Покрените сачувану серију команди у терминалу и реагујте на оно што сервер пошаље назад.",
"target": "Циљ",
"noTerminal": "Ниједан терминал није отворен",
"empty": "Још нема макроа",
"emptyHint": "Направите макро за аутоматизацију тока рада терминала, као што је пријављивање, поновно покретање сервиса или одговарање на упите.",
"create": "Нови макро",
"back": "Назад на макрое",
"name": "Име",
"namePlaceholder": "Поново покрените веб сервер",
"nameRequired": "Дајте макроу име пре чувања",
"descriptionLabel": "Опис",
"descriptionPlaceholder": "Шта овај макро ради (опционо)",
"unsaved": "Несачуване промене",
"save": "Сачувај",
"saved": "Макро је сачуван",
"saveFailed": "Није могуће сачувати макро",
"delete": "Обриши",
"deleteTitle": "Да ли желите да обришете овај макро?",
"deleteBody": "„{{name}}“ ће бити трајно обрисано. Ова радња се не може поништити.",
"deleted": "Макро је обрисан",
"cancel": "Откажи",
"run": "Трчи",
"stop": "Заустави",
"running": "Трчи се...",
"completed": "Макро је завршен",
"failed": "Макро није успео",
"loadFailed": "Није могуће учитати макрое",
"needTerminal": "Прво отворите и повежите терминал",
"noSteps": "Овај макро још нема кораке. Додајте један испод.",
"addStep": "Додај корак",
"stepNumber": "Корак {{number}}",
"removeStep": "Уклони корак",
"confirmRunTitle": "Покрени овај макро?",
"confirmRunBody": "„{{name}}“ ће послати команде на {{host}}.",
"confirmRunAction": "Покрени макро",
"stepTypes": {
"send": {
"label": "Пошаљи команду",
"help": "Уноси текст у терминал, исто као да сте га откуцали."
},
"wait": {
"label": "Сачекајте излаз",
"help": "Паузира док сервер не одштампа текст који наведете, као што је захтев за лозинку."
},
"delay": {
"label": "Пауза",
"help": "Чека одређено време пре следећег корака."
},
"if": {
"label": "Ако излаз садржи",
"help": "Проверава све што је сервер до сада одштампао, а затим покреће једну од две гране."
},
"repeat": {
"label": "Понови",
"help": "Покреће кораке унутар њега одређени број пута."
}
},
"send": {
"text": "Команда или текст",
"textPlaceholder": "системцтл рестартовање нгинкса",
"pressEnter": "Притисните Ентер након слања",
"pressEnterHelp": "Искључите ово да бисте куцали текст без његовог слања."
},
"wait": {
"pattern": "Сачекајте док излаз не садржи",
"patternPlaceholder": "лозинка:",
"timeout": "Одустани после",
"onTimeout": "Ако се никада не појави",
"stop": "Зауставите макро",
"continue": "Настави ипак"
},
"delay": {
"duration": "Сачекајте"
},
"ifStep": {
"pattern": "Ако излаз садржи",
"patternPlaceholder": "Активно: активно (покреће се)",
"then": "Онда уради ово",
"else": "У супротном, урадите ово",
"emptyBranch": "Без степеница"
},
"repeat": {
"count": "Број пута",
"times": "пута"
},
"match": {
"useRegex": "Упари се са регуларним изразом",
"useRegexHelp": "Оставите ово искључено да би текст тачно одговарао написаном тексту.",
"invalidRegex": "Није важећи регуларни израз, биће упарен као обичан текст"
},
"seconds": "секунде",
"secondsShort": "{{count}}с"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Inställningar för sidofältet...",
"hosts": "Värdar",
"snippets": "Snuttar",
"macros": "Makron",
"hostManager": "Värdhanterare",
"credentials": "Referenser",
"connections": "Anslutningar",
@@ -869,7 +868,6 @@
"groupTags": "Taggar och pin",
"groupTunnels": "Tunnlar",
"groupJumpHosts": "Hoppa värdar",
"groupQuickActions": "Snabbåtgärder",
"groupFeatureFlags": "Funktionsflaggor",
"groupAdvanced": "Avancerad konfiguration",
"preview": "Förhandsvisning",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Lagra SSH-nycklar eller lösenord",
"tmuxMonitor": "Tmux-monitor",
"tmuxMonitorDesc": "Övervaka tmux-sessioner över dina värdar",
"snippets": "Snuttar",
"runSnippetDesc": "Kör i den aktiva terminalfliken",
"noSnippetsFound": "Inga utdrag hittades som matchar \"{{search}}\"",
"recentActivity": "Senaste aktivitet",
"serversAndHosts": "Servrar och värdar",
"noHostsFound": "Inga värdar hittades som matchar \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Föregående flik",
"hotkeysJumpTab": "Hoppa till fliken"
},
"snippets": {
"title": "Snuttar",
"createSnippetTitle": "Skapa utdrag",
"createSnippetDescription": "Skapa ett nytt kommandokodavsnitt för snabb körning",
"nameLabel": "Namn",
"namePlaceholder": "t.ex. starta om Nginx",
"descriptionLabel": "Beskrivning",
"descriptionPlaceholder": "Valfri beskrivning",
"optional": "Frivillig",
"folderLabel": "Mapp",
"noFolder": "Ingen mapp (Okategoriserad)",
"commandLabel": "Kommando",
"commandPlaceholder": "t.ex. sudo systemctl starta om nginx",
"typeLabel": "Typ",
"typeCommand": "Kommando",
"typeNote": "Notera",
"noteLabel": "Notera",
"notePlaceholder": "t.ex. en återanvändbar prompt eller påminnelsetext",
"cancel": "Avboka",
"createSnippetButton": "Skapa utdrag",
"createFolderTitle": "Skapa mapp",
"createFolderDescription": "Organisera dina utdrag i mappar",
"folderNameLabel": "Mappnamn",
"folderNamePlaceholder": "t.ex. systemkommandon, Docker-skript",
"folderColorLabel": "Mappfärg",
"folderIconLabel": "Mappikon",
"previewLabel": "Förhandsvisning",
"folderNameFallback": "Mappnamn",
"createFolderButton": "Skapa mapp",
"targetTerminals": "Target-terminaler",
"selectAll": "Alla",
"selectNone": "Ingen",
"noTerminalTabsOpen": "Inga terminalflikar öppna",
"searchPlaceholder": "Sök utdrag...",
"newSnippet": "Nytt utdrag",
"newFolder": "Ny mapp",
"run": "Sikt",
"noSnippetsInFolder": "Inga utdrag i den här mappen",
"uncategorized": "Okategoriserad",
"editSnippetTitle": "Redigera utdrag",
"editSnippetDescription": "Uppdatera detta kommandokodavsnitt",
"saveSnippetButton": "Spara ändringar",
"createSuccess": "Kodavsnittet har skapats",
"createFailed": "Misslyckades med att skapa utdrag",
"updateSuccess": "Kodavsnittet har uppdaterats",
"updateFailed": "Misslyckades med att uppdatera utdraget",
"deleteFailed": "Misslyckades med att ta bort utdraget",
"folderCreateSuccess": "Mappen skapades",
"folderCreateFailed": "Misslyckades med att skapa mappen",
"editFolderTitle": "Redigera mapp",
"editFolderDescription": "Byt namn på eller ändra utseendet på den här mappen",
"saveFolderButton": "Spara ändringar",
"editFolder": "Redigera mapp",
"deleteFolder": "Ta bort mapp",
"folderDeleteSuccess": "Mappen \"{{name}}\" raderad",
"folderDeleteFailed": "Misslyckades med att ta bort mappen",
"folderEditSuccess": "Mappen har uppdaterats",
"folderEditFailed": "Misslyckades med att uppdatera mappen",
"confirmRunMessage": "Kör \"{{name}}\"?",
"confirmRunButton": "Sikt",
"runSuccess": "Körde \"{{name}}\" i {{count}} terminal(er)",
"pasteSuccess": "Klistrade in \"{{name}}\" i {{count}} terminal(er)",
"pasteToTerminal": "Klistra",
"copySuccess": "Kopierade \"{{name}}\" till urklipp",
"shareTitle": "Dela utdrag",
"shareUser": "Användare",
"shareRole": "Roll",
"selectUser": "Välj en användare...",
"selectRole": "Välj en roll...",
"shareSuccess": "Kodavsnittet delades",
"shareFailed": "Misslyckades med att dela utdraget",
"revokeSuccess": "Åtkomst återkallad",
"revokeFailed": "Misslyckades med att återkalla åtkomst",
"currentAccess": "Nuvarande åtkomst",
"shareLoadError": "Misslyckades med att ladda delningsdata",
"loading": "Belastning...",
"close": "Nära",
"reorderFailed": "Det gick inte att spara kodavsnittets ordning",
"importExport": "Importera / Exportera",
"exportBtn": "Exportera JSON",
"importBtn": "Importera JSON",
"exportSuccess": "Snippet har exporterats",
"exportFailed": "Misslyckades med att exportera utdrag",
"importTitle": "Importera utdrag",
"importDescription": "Importera utdrag och mappar från en JSON-fil som exporterats av Termix",
"importDropOrClick": "Släpp en JSON-fil här eller klicka för att bläddra",
"importSelectedFile": "Vald: {{name}}",
"importOverwrite": "Skriv över befintliga utdrag med samma namn och mapp",
"importStartBtn": "Importera",
"importSuccess": "Importen är klar: {{snippets}} kodavsnitt tillagda, {{updated}} uppdaterade, {{skipped}} hoppade över, {{folders}} mapp(ar) tillagda",
"importFailed": "Misslyckades med att importera utdrag",
"importInvalidFile": "Ogiltig fil: förväntade ett JSON-objekt med kodavsnitt eller mappmatriser",
"targetHostsLabel": "Målvärdar",
"targetHostsHint": "Tilldela värdar att köra detta kodavsnitt direkt utan en öppen terminal.",
"noHostsAvailable": "Inga värdar konfigurerade",
"clearTargetHosts": "Rensa alla",
"hasTargetHosts": "Har målvärdar",
"runOnTargets": "Kör på mål",
"directRunSuccess": "Körde \"{{name}}\" på {{count}} värd(ar)",
"directRunPartialFail": "\"{{name}}\" misslyckades på en eller flera värdar",
"executionResultTitle": "Exekveringsresultat: {{name}}",
"executionResultDescription": "Utdata från att köra kodavsnittet på varje målvärd.",
"executionSuccess": "Framgång",
"executionFailed": "Misslyckades",
"expandAll": "Expandera alla mappar",
"collapseAll": "Komprimera alla mappar",
"settings": "Inställningar för utdrag",
"variablesHint": "Variabler: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Etikett}",
"variablesDialogTitle": "Kör \"{{name}}\"",
"variablesDialogDescription": "Fyll i de värden som detta kodavsnitt behöver innan du kör det.",
"variablesPreviewLabel": "Förhandsvisning",
"variablesConfirmButton": "Sikt"
},
"keybindings": {
"title": "Kortkommandon",
"description": "Anpassa kortkommandon för att kopiera, klistra in och kontrollera terminalen, eller bind tangenter för att skicka text eller köra ett kodavsnitt.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Klicka och tryck på tangenterna",
"recording": "Tryck på tangenterna...",
"comboRequiredError": "Tryck först på en tangentkombination",
"textRequiredError": "Ange texten som ska skickas",
"controlCodeRequiredError": "Ange en enda bokstav för kontrollkoden",
"snippetRequiredError": "Välj ett utdrag",
"conflictWarning": "Denna kombination är redan bunden till {{combo}}. Om du sparar den aktiveras båda.",
"actionLabel": "Handling",
"actionCopy": "Kopiera val",
"actionPaste": "Klistra",
"actionSendControlCode": "Skicka Ctrl+bokstavssignal",
"actionSendText": "Skicka sms",
"actionRunSnippet": "Kör befintligt kodavsnitt",
"actionNextTab": "Växla till nästa flik",
"actionPreviousTab": "Växla till föregående flik",
"actionOpenCommandPalette": "Öppna snabbsökning",
"controlCodeLabel": "Brev",
"textLabel": "SMS att skicka",
"appendEnterLabel": "Tryck Enter efter att du skickat",
"snippetLabel": "Utdrag",
"selectSnippetPlaceholder": "Välj ett utdrag",
"orphanedSnippetWarning": "utdraget hittades inte",
"clipboardPermissionNote": "Kan be om behörighet för urklipp vid första användningen i vissa webbläsare."
"actionOpenCommandPalette": "Öppna snabbsökning"
},
"userProfile": {
"donateTitle": "Håll Termix vid liv",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Ämne är obligatoriskt",
"ntfyToken": "Åtkomsttoken (valfritt)",
"channelSaveFailed": "Misslyckades med att spara kanalen"
},
"macros": {
"title": "Makron",
"description": "Kör en sparad serie kommandon i en terminal och reagera på vad servern skickar tillbaka.",
"target": "Mål",
"noTerminal": "Ingen terminal öppen",
"empty": "Inga makron ännu",
"emptyHint": "Skapa ett makro för att automatisera ett terminalflöde, som att logga in, starta om en tjänst eller svara på frågor.",
"create": "Nytt makro",
"back": "Tillbaka till makron",
"name": "Namn",
"namePlaceholder": "Starta om webbservern",
"nameRequired": "Ge makrot ett namn innan du sparar",
"descriptionLabel": "Beskrivning",
"descriptionPlaceholder": "Vad detta makro gör (valfritt)",
"unsaved": "Osparade ändringar",
"save": "Spara",
"saved": "Makrot sparat",
"saveFailed": "Kunde inte spara makrot",
"delete": "Radera",
"deleteTitle": "Ta bort det här makrot?",
"deleteBody": "\"{{name}}\" kommer att raderas permanent. Detta kan inte ångras.",
"deleted": "Makro borttaget",
"cancel": "Avboka",
"run": "Sikt",
"stop": "Stopp",
"running": "Spring...",
"completed": "Makro klart",
"failed": "Makrot misslyckades",
"loadFailed": "Kunde inte läsa in makron",
"needTerminal": "Öppna och anslut en terminal först",
"noSteps": "Det här makrot har inga steg ännu. Lägg till ett nedan.",
"addStep": "Lägg till steg",
"stepNumber": "Steg {{number}}",
"removeStep": "Ta bort steg",
"confirmRunTitle": "Köra det här makrot?",
"confirmRunBody": "\"{{name}}\" skickar kommandon till {{host}}.",
"confirmRunAction": "Kör makro",
"stepTypes": {
"send": {
"label": "Skicka kommando",
"help": "Skriver text i terminalen, på samma sätt som om du hade skrivit den."
},
"wait": {
"label": "Vänta på utdata",
"help": "Pausar tills servern skriver ut texten du anger, som en lösenordsfråga."
},
"delay": {
"label": "Paus",
"help": "Väntar en fastställd tid innan nästa steg."
},
"if": {
"label": "Om utdata innehåller",
"help": "Kontrollerar allt som servern har skrivit ut hittills och kör sedan en av två grenar."
},
"repeat": {
"label": "Upprepa",
"help": "Kör stegen inuti ett visst antal gånger."
}
},
"send": {
"text": "Kommando eller text",
"textPlaceholder": "systemctl starta om nginx",
"pressEnter": "Tryck Enter efter att du skickat",
"pressEnterHelp": "Stäng av detta för att skriva text utan att skicka in den."
},
"wait": {
"pattern": "Vänta tills utdata innehåller",
"patternPlaceholder": "lösenord:",
"timeout": "Ge upp efteråt",
"onTimeout": "Om det aldrig dyker upp",
"stop": "Stoppa makrot",
"continue": "Fortsätt ändå"
},
"delay": {
"duration": "Vänta på"
},
"ifStep": {
"pattern": "Om utdata innehåller",
"patternPlaceholder": "Aktiv: aktiv (löper)",
"then": "Gör sedan detta",
"else": "Annars gör så här",
"emptyBranch": "Inga steg"
},
"repeat": {
"count": "Antal gånger",
"times": "gånger"
},
"match": {
"useRegex": "Matcha med ett reguljärt uttryck",
"useRegexHelp": "Lämna detta av för att texten ska se exakt ut som den är skriven.",
"invalidRegex": "Inte ett giltigt reguljärt uttryck, det kommer att matchas som vanlig text"
},
"seconds": "sekunder",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "การตั้งค่าแถบด้านข้าง...",
"hosts": "โฮสต์",
"snippets": "เศษเสี้ยว",
"macros": "มาโคร",
"hostManager": "ผู้จัดการโฮสต์",
"credentials": "คุณสมบัติ",
"connections": "การเชื่อมต่อ",
@@ -869,7 +868,6 @@
"groupTags": "แท็กและพิน",
"groupTunnels": "อุโมงค์",
"groupJumpHosts": "โฮสต์ Jump",
"groupQuickActions": "การดำเนินการอย่างรวดเร็ว",
"groupFeatureFlags": "ธงคุณสมบัติ",
"groupAdvanced": "การกำหนดค่าขั้นสูง",
"preview": "ตัวอย่าง",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "จัดเก็บคีย์ SSH หรือรหัสผ่าน",
"tmuxMonitor": "มอนิเตอร์ Tmux",
"tmuxMonitorDesc": "ตรวจสอบเซสชัน tmux บนโฮสต์ต่างๆ ของคุณ",
"snippets": "เศษเสี้ยว",
"runSnippetDesc": "เรียกใช้ในแท็บเทอร์มินัลที่ใช้งานอยู่",
"noSnippetsFound": "ไม่พบข้อความตัวอย่างที่ตรงกับ \"{{search}}\"",
"recentActivity": "กิจกรรมล่าสุด",
"serversAndHosts": "เซิร์ฟเวอร์และโฮสต์",
"noHostsFound": "ไม่พบโฮสต์ที่ตรงกับ \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "แท็บก่อนหน้า",
"hotkeysJumpTab": "ข้ามไปยังแท็บ"
},
"snippets": {
"title": "เศษเสี้ยว",
"createSnippetTitle": "สร้างโค้ดสั้น",
"createSnippetDescription": "สร้างส่วนคำสั่งใหม่เพื่อเรียกใช้งานอย่างรวดเร็ว",
"nameLabel": "ชื่อ",
"namePlaceholder": "เช่น รีสตาร์ท Nginx",
"descriptionLabel": "คำอธิบาย",
"descriptionPlaceholder": "คำอธิบายเพิ่มเติม (ไม่บังคับ)",
"optional": "ไม่จำเป็น",
"folderLabel": "โฟลเดอร์",
"noFolder": "ไม่มีโฟลเดอร์ (ไม่ได้จัดหมวดหมู่)",
"commandLabel": "สั่งการ",
"commandPlaceholder": "เช่น sudo systemctl restart nginx",
"typeLabel": "พิมพ์",
"typeCommand": "สั่งการ",
"typeNote": "บันทึก",
"noteLabel": "บันทึก",
"notePlaceholder": "เช่น ข้อความแจ้งเตือนหรือข้อความเตือนความจำที่สามารถนำกลับมาใช้ซ้ำได้",
"cancel": "ยกเลิก",
"createSnippetButton": "สร้างโค้ดสั้น",
"createFolderTitle": "สร้างโฟลเดอร์",
"createFolderDescription": "จัดระเบียบข้อความย่อของคุณลงในโฟลเดอร์",
"folderNameLabel": "ชื่อโฟลเดอร์",
"folderNamePlaceholder": "เช่น คำสั่งระบบ, สคริปต์ Docker",
"folderColorLabel": "สีของโฟลเดอร์",
"folderIconLabel": "ไอคอนโฟลเดอร์",
"previewLabel": "ตัวอย่าง",
"folderNameFallback": "ชื่อโฟลเดอร์",
"createFolderButton": "สร้างโฟลเดอร์",
"targetTerminals": "เทอร์มินัลเป้าหมาย",
"selectAll": "ทั้งหมด",
"selectNone": "ไม่มี",
"noTerminalTabsOpen": "ไม่มีแท็บเทอร์มินัลเปิดอยู่",
"searchPlaceholder": "ตัวอย่างการค้นหา...",
"newSnippet": "โค้ดตัวอย่างใหม่",
"newFolder": "โฟลเดอร์ใหม่",
"run": "วิ่ง",
"noSnippetsInFolder": "ไม่มีไฟล์ตัวอย่างในโฟลเดอร์นี้",
"uncategorized": "ไม่มีหมวดหมู่",
"editSnippetTitle": "แก้ไขส่วนย่อย",
"editSnippetDescription": "อัปเดตส่วนคำสั่งนี้",
"saveSnippetButton": "บันทึกการเปลี่ยนแปลง",
"createSuccess": "สร้างโค้ดตัวอย่างสำเร็จแล้ว",
"createFailed": "ไม่สามารถสร้างโค้ดตัวอย่างได้",
"updateSuccess": "อัปเดตข้อมูลตัวอย่างสำเร็จแล้ว",
"updateFailed": "ไม่สามารถอัปเดตโค้ดตัวอย่างได้",
"deleteFailed": "ไม่สามารถลบส่วนย่อยได้",
"folderCreateSuccess": "สร้างโฟลเดอร์สำเร็จแล้ว",
"folderCreateFailed": "ไม่สามารถสร้างโฟลเดอร์ได้",
"editFolderTitle": "แก้ไขโฟลเดอร์",
"editFolderDescription": "เปลี่ยนชื่อหรือเปลี่ยนลักษณะของโฟลเดอร์นี้",
"saveFolderButton": "บันทึกการเปลี่ยนแปลง",
"editFolder": "แก้ไขโฟลเดอร์",
"deleteFolder": "ลบโฟลเดอร์",
"folderDeleteSuccess": "โฟลเดอร์ \"{{name}}\" ถูกลบแล้ว",
"folderDeleteFailed": "ไม่สามารถลบโฟลเดอร์ได้",
"folderEditSuccess": "อัปเดตโฟลเดอร์สำเร็จแล้ว",
"folderEditFailed": "ไม่สามารถอัปเดตโฟลเดอร์ได้",
"confirmRunMessage": "เรียกใช้ \"{{name}}\"?",
"confirmRunButton": "วิ่ง",
"runSuccess": "Ran \"{{name}}\" ในเทอร์มินัล {{count}} เครื่อง",
"pasteSuccess": "วาง \"{{name}}\" ลงในเทอร์มินัล {{count}} แล้ว",
"pasteToTerminal": "แปะ",
"copySuccess": "คัดลอก \"{{name}}\" ไปยังคลิปบอร์ด",
"shareTitle": "แชร์ตัวอย่าง",
"shareUser": "ผู้ใช้",
"shareRole": "บทบาท",
"selectUser": "เลือกผู้ใช้...",
"selectRole": "เลือกบทบาท...",
"shareSuccess": "แชร์โค้ดตัวอย่างสำเร็จแล้ว",
"shareFailed": "ไม่สามารถแชร์ส่วนย่อยได้",
"revokeSuccess": "สิทธิ์การเข้าถึงถูกเพิกถอน",
"revokeFailed": "ไม่สามารถเพิกถอนสิทธิ์การเข้าถึงได้",
"currentAccess": "การเข้าถึงปัจจุบัน",
"shareLoadError": "ไม่สามารถโหลดข้อมูลการแชร์ได้",
"loading": "กำลังโหลด...",
"close": "ปิด",
"reorderFailed": "ไม่สามารถบันทึกลำดับของส่วนย่อยได้",
"importExport": "นำเข้า/ส่งออก",
"exportBtn": "ส่งออก JSON",
"importBtn": "นำเข้า JSON",
"exportSuccess": "ส่งออกข้อมูลตัวอย่างสำเร็จแล้ว",
"exportFailed": "ไม่สามารถส่งออกส่วนย่อยได้",
"importTitle": "นำเข้าโค้ดตัวอย่าง",
"importDescription": "นำเข้าส่วนย่อยและโฟลเดอร์จากไฟล์ JSON ที่ส่งออกโดย Termix",
"importDropOrClick": "วางไฟล์ JSON ที่นี่ หรือคลิกเพื่อเรียกดู",
"importSelectedFile": "เลือกแล้ว: {{name}}",
"importOverwrite": "เขียนทับโค้ดตัวอย่างที่มีอยู่แล้วซึ่งมีชื่อและโฟลเดอร์เดียวกัน",
"importStartBtn": "นำเข้า",
"importSuccess": "การนำเข้าเสร็จสมบูรณ์: เพิ่มส่วนย่อย {{snippets}} รายการ, อัปเดต {{updated}} รายการ, ข้าม {{skipped}} รายการ, เพิ่มโฟลเดอร์ {{folders}} รายการ",
"importFailed": "ไม่สามารถนำเข้าส่วนย่อยได้",
"importInvalidFile": "ไฟล์ไม่ถูกต้อง: คาดหวังอ็อบเจ็กต์ JSON ที่มีอาร์เรย์ของส่วนย่อยหรือโฟลเดอร์",
"targetHostsLabel": "โฮสต์เป้าหมาย",
"targetHostsHint": "กำหนดให้โฮสต์สามารถรันโค้ดส่วนนี้ได้โดยตรงโดยไม่ต้องเปิดเทอร์มินัล",
"noHostsAvailable": "ไม่มีการกำหนดค่าโฮสต์",
"clearTargetHosts": "ล้างทั้งหมด",
"hasTargetHosts": "มีโฮสต์เป้าหมาย",
"runOnTargets": "วิ่งตามเป้าหมาย",
"directRunSuccess": "Ran \"{{name}}\" บนโฮสต์ {{count}} เครื่อง",
"directRunPartialFail": "\"{{name}}\" ล้มเหลวบนโฮสต์อย่างน้อยหนึ่งเครื่อง",
"executionResultTitle": "ผลลัพธ์การดำเนินการ: {{name}}",
"executionResultDescription": "ผลลัพธ์จากการรันโค้ดบนโฮสต์เป้าหมายแต่ละตัว",
"executionSuccess": "ความสำเร็จ",
"executionFailed": "ล้มเหลว",
"expandAll": "ขยายโฟลเดอร์ทั้งหมด",
"collapseAll": "ยุบโฟลเดอร์ทั้งหมด",
"settings": "การตั้งค่าสนิปเป็ต",
"variablesHint": "ตัวแปร: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "วิ่ง \"{{name}}\"",
"variablesDialogDescription": "กรอกค่าที่จำเป็นสำหรับโค้ดส่วนนี้ก่อนที่จะเรียกใช้งาน",
"variablesPreviewLabel": "ตัวอย่าง",
"variablesConfirmButton": "วิ่ง"
},
"keybindings": {
"title": "แป้นพิมพ์ลัด",
"description": "ปรับแต่งปุ่มลัดสำหรับการคัดลอก วาง และควบคุมในเทอร์มินัล หรือกำหนดปุ่มเพื่อส่งข้อความหรือเรียกใช้โค้ดตัวอย่าง",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "คลิกและกดปุ่ม",
"recording": "กดปุ่ม...",
"comboRequiredError": "กดปุ่มคีย์ผสมก่อน",
"textRequiredError": "ป้อนข้อความที่จะส่ง",
"controlCodeRequiredError": "ป้อนตัวอักษรตัวเดียวสำหรับรหัสควบคุม",
"snippetRequiredError": "เลือกส่วนย่อย",
"conflictWarning": "การรวมกันนี้ถูกผูกไว้กับ {{combo}}แล้ว การบันทึกจะทำให้ทั้งสองอย่างทำงาน",
"actionLabel": "การกระทำ",
"actionCopy": "คัดลอกการเลือก",
"actionPaste": "แปะ",
"actionSendControlCode": "ส่งสัญญาณ Ctrl+ตัวอักษร",
"actionSendText": "ส่งข้อความ",
"actionRunSnippet": "เรียกใช้โค้ดตัวอย่างที่มีอยู่",
"actionNextTab": "สลับไปยังแท็บถัดไป",
"actionPreviousTab": "สลับไปยังแท็บก่อนหน้า",
"actionOpenCommandPalette": "เปิดการค้นหาด่วน",
"controlCodeLabel": "จดหมาย",
"textLabel": "ข้อความที่จะส่ง",
"appendEnterLabel": "กด Enter หลังจากส่งแล้ว",
"snippetLabel": "ส่วนหนึ่ง",
"selectSnippetPlaceholder": "เลือกส่วนย่อย",
"orphanedSnippetWarning": "ไม่พบส่วนย่อย",
"clipboardPermissionNote": "ในบางเบราว์เซอร์ อาจขออนุญาตคัดลอกและวางข้อมูลลงในคลิปบอร์ดเมื่อใช้งานครั้งแรก"
"actionOpenCommandPalette": "เปิดการค้นหาด่วน"
},
"userProfile": {
"donateTitle": "รักษา Termix ให้คงอยู่ต่อไป",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "จำเป็นต้องระบุหัวข้อ",
"ntfyToken": "โทเค็นการเข้าถึง (ไม่บังคับ)",
"channelSaveFailed": "ไม่สามารถบันทึกช่องได้"
},
"macros": {
"title": "มาโคร",
"description": "เรียกใช้ชุดคำสั่งที่บันทึกไว้ในเทอร์มินัล แล้วตอบสนองต่อสิ่งที่เซิร์ฟเวอร์ส่งกลับมา",
"target": "เป้า",
"noTerminal": "ไม่มีเทอร์มินัลเปิดอยู่",
"empty": "ยังไม่มีมาโคร",
"emptyHint": "สร้างมาโครเพื่อทำให้กระบวนการทำงานบนเทอร์มินัลเป็นไปโดยอัตโนมัติ เช่น การล็อกอิน การรีสตาร์ทบริการ หรือการตอบคำถามต่างๆ",
"create": "มาโครใหม่",
"back": "กลับไปที่มาโคร",
"name": "ชื่อ",
"namePlaceholder": "รีสตาร์ทเว็บเซิร์ฟเวอร์",
"nameRequired": "ตั้งชื่อมาโครก่อนบันทึก",
"descriptionLabel": "คำอธิบาย",
"descriptionPlaceholder": "มาโครนี้ทำอะไร (ไม่บังคับ)",
"unsaved": "การเปลี่ยนแปลงที่ยังไม่ได้บันทึก",
"save": "บันทึก",
"saved": "บันทึกมาโครแล้ว",
"saveFailed": "ไม่สามารถบันทึกมาโครได้",
"delete": "ลบ",
"deleteTitle": "ลบมาโครนี้?",
"deleteBody": "\"{{name}}\" จะถูกลบอย่างถาวร ไม่สามารถกู้คืนได้",
"deleted": "มาโครถูกลบแล้ว",
"cancel": "ยกเลิก",
"run": "วิ่ง",
"stop": "หยุด",
"running": "วิ่ง...",
"completed": "มาโครเสร็จสิ้น",
"failed": "มาโครล้มเหลว",
"loadFailed": "ไม่สามารถโหลดมาโครได้",
"needTerminal": "เปิดและเชื่อมต่อเทอร์มินัลก่อน",
"noSteps": "มาโครนี้ยังไม่มีขั้นตอนใดๆ เพิ่มขั้นตอนด้านล่าง",
"addStep": "เพิ่มขั้นตอน",
"stepNumber": "ขั้นตอน {{number}}",
"removeStep": "ลบขั้นตอน",
"confirmRunTitle": "เรียกใช้มาโครนี้หรือไม่?",
"confirmRunBody": "\"{{name}}\" จะส่งคำสั่งไปยัง {{host}}",
"confirmRunAction": "เรียกใช้มาโคร",
"stepTypes": {
"send": {
"label": "ส่งคำสั่ง",
"help": "พิมพ์ข้อความลงในเทอร์มินัล เหมือนกับที่คุณพิมพ์ข้อความนั้นด้วยตนเอง"
},
"wait": {
"label": "รอผลลัพธ์",
"help": "หยุดรอจนกว่าเซิร์ฟเวอร์จะแสดงข้อความที่คุณระบุ เช่นเดียวกับการขอให้ป้อนรหัสผ่าน"
},
"delay": {
"label": "หยุดชั่วคราว",
"help": "รอเป็นระยะเวลาที่กำหนดก่อนดำเนินการขั้นตอนต่อไป"
},
"if": {
"label": "หากผลลัพธ์ประกอบด้วย",
"help": "ตรวจสอบทุกอย่างที่เซิร์ฟเวอร์ได้พิมพ์ออกมาแล้ว จากนั้นจึงเลือกดำเนินการตามหนึ่งในสองทางเลือก"
},
"repeat": {
"label": "ทำซ้ำ",
"help": "ดำเนินการตามขั้นตอนภายในตามจำนวนครั้งที่กำหนด"
}
},
"send": {
"text": "คำสั่งหรือข้อความ",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "กด Enter หลังจากส่งแล้ว",
"pressEnterHelp": "ปิดตัวเลือกนี้เพื่อพิมพ์ข้อความโดยไม่ต้องกดส่ง"
},
"wait": {
"pattern": "รอจนกว่าผลลัพธ์จะประกอบด้วย",
"patternPlaceholder": "รหัสผ่าน:",
"timeout": "ยอมแพ้หลังจาก",
"onTimeout": "หากมันไม่ปรากฏเลย",
"stop": "หยุดมาโคร",
"continue": "อย่าหยุดแค่นี้"
},
"delay": {
"duration": "รอสักครู่"
},
"ifStep": {
"pattern": "หากผลลัพธ์ประกอบด้วย",
"patternPlaceholder": "สถานะ: กำลังทำกิจกรรม (วิ่ง)",
"then": "จากนั้นให้ทำแบบนี้",
"else": "หรือทำแบบนี้ก็ได้",
"emptyBranch": "ไม่มีขั้นตอน"
},
"repeat": {
"count": "จำนวนครั้ง",
"times": "ครั้ง"
},
"match": {
"useRegex": "จับคู่ด้วยนิพจน์ปกติ",
"useRegexHelp": "ไม่ต้องใส่ส่วนนี้เพื่อให้ตรงกับข้อความที่เขียนไว้ทุกประการ",
"invalidRegex": "ไม่ใช่รูปแบบนิพจน์ปกติที่ถูกต้อง ระบบจะถือว่าเป็นข้อความธรรมดา"
},
"seconds": "วินาที",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Kenar Çubuğu Ayarları...",
"hosts": "Ana Bilgisayarlar",
"snippets": "Kod Parçacıkları",
"macros": "Makrolar",
"hostManager": "Ana Bilgisayar Yöneticisi",
"credentials": "Kimlik Bilgileri",
"connections": "Bağlantılar",
@@ -869,7 +868,6 @@
"groupTags": "Etiketler ve pinler",
"groupTunnels": "Tüneller",
"groupJumpHosts": "Sunucuları atla",
"groupQuickActions": "Hızlı işlemler",
"groupFeatureFlags": "Özellik bayrakları",
"groupAdvanced": "Gelişmiş yapılandırma",
"preview": "Önizleme",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "SSH anahtarları veya parolaları sakla",
"tmuxMonitor": "Tmux İzleyici",
"tmuxMonitorDesc": "Ana makinelerinizdeki tmux oturumlarını izle",
"snippets": "Kısa bölümler",
"runSnippetDesc": "Etkin terminal sekmesinde çalıştırın.",
"noSnippetsFound": "\"{{search}} \" ile eşleşen hiçbir kod parçacığı bulunamadı.",
"recentActivity": "Son Etkinlik",
"serversAndHosts": "Sunucular ve Ana Makineler",
"noHostsFound": "\"{{search}}\" ile eşleşen ana makineler bulunamadı",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Önceki sekme",
"hotkeysJumpTab": "Sekmeye atla"
},
"snippets": {
"title": "Parçacıklar",
"createSnippetTitle": "Parçacık Oluştur",
"createSnippetDescription": "Hızlı çalıştırma için yeni bir komut parçacığı oluşturun",
"nameLabel": "Ad",
"namePlaceholder": "ör. Nginx'i Yeniden Başlat",
"descriptionLabel": "Açıklama",
"descriptionPlaceholder": "İsteğe bağlı açıklama",
"optional": "İsteğe bağlı",
"folderLabel": "Klasör",
"noFolder": "Klasör yok (Kategorisiz)",
"commandLabel": "Komut",
"commandPlaceholder": "ör. sudo systemctl restart nginx",
"typeLabel": "Tip",
"typeCommand": "Emretmek",
"typeNote": "Not",
"noteLabel": "Not",
"notePlaceholder": "Örneğin, tekrar kullanılabilir bir uyarı veya hatırlatma metni.",
"cancel": "İptal",
"createSnippetButton": "Parçacık Oluştur",
"createFolderTitle": "Klasör Oluştur",
"createFolderDescription": "Parçacıklarınızı klasörlerde düzenleyin",
"folderNameLabel": "Klasör Adı",
"folderNamePlaceholder": "ör. Sistem Komutları, Docker Betikleri",
"folderColorLabel": "Klasör Rengi",
"folderIconLabel": "Klasör Simgesi",
"previewLabel": "Önizleme",
"folderNameFallback": "Klasör Adı",
"createFolderButton": "Klasör Oluştur",
"targetTerminals": "Hedef Terminaller",
"selectAll": "Tümü",
"selectNone": "Hiçbiri",
"noTerminalTabsOpen": "Açık terminal sekmesi yok",
"searchPlaceholder": "Parçacıkları ara...",
"newSnippet": "Yeni Parçacık",
"newFolder": "Yeni Klasör",
"run": "Çalıştır",
"noSnippetsInFolder": "Bu klasörde parçacık yok",
"uncategorized": "Kategorisiz",
"editSnippetTitle": "Parçacığı Düzenle",
"editSnippetDescription": "Bu komut parçacığını güncelle",
"saveSnippetButton": "Değişiklikleri Kaydet",
"createSuccess": "Parçacık başarıyla oluşturuldu",
"createFailed": "Parçacık oluşturulamadı",
"updateSuccess": "Parçacık başarıyla güncellendi",
"updateFailed": "Parçacık güncellenemedi",
"deleteFailed": "Parçacık silinemedi",
"folderCreateSuccess": "Klasör başarıyla oluşturuldu",
"folderCreateFailed": "Klasör oluşturulamadı",
"editFolderTitle": "Klasörü Düzenle",
"editFolderDescription": "Bu klasörü yeniden adlandırın veya görünümünü değiştirin",
"saveFolderButton": "Değişiklikleri Kaydet",
"editFolder": "Klasörü düzenle",
"deleteFolder": "Klasörü sil",
"folderDeleteSuccess": "\"{{name}}\" klasörü silindi",
"folderDeleteFailed": "Klasör silinemedi",
"folderEditSuccess": "Klasör başarıyla güncellendi",
"folderEditFailed": "Klasör güncellenemedi",
"confirmRunMessage": "\"{{name}}\" çalıştırılsın mı?",
"confirmRunButton": "Çalıştır",
"runSuccess": "\"{{name}}\" {{count}} terminalde çalıştırıldı",
"pasteSuccess": "\"{{name}}\" ifadesi {{count}} terminal(ler)ine yapıştırıldı",
"pasteToTerminal": "Yapıştır",
"copySuccess": "\"{{name}}\" panoya kopyalandı",
"shareTitle": "Parçacığı Paylaş",
"shareUser": "Kullanıcı",
"shareRole": "Rol",
"selectUser": "Kullanıcı seç...",
"selectRole": "Rol seç...",
"shareSuccess": "Kod parçacığı başarıyla paylaşıldı",
"shareFailed": "Kod parçacığı paylaşılamadı",
"revokeSuccess": "Erişim iptal edildi",
"revokeFailed": "Erişim iptal edilemedi",
"currentAccess": "Geçerli Erişim",
"shareLoadError": "Paylaşım verisi yüklenemedi",
"loading": "Yükleniyor...",
"close": "Kapat",
"reorderFailed": "Kod parçacığı sıralaması kaydedilemedi",
"importExport": "İçe / Dışa Aktar",
"exportBtn": "JSON Dışa Aktar",
"importBtn": "JSON İçe Aktar",
"exportSuccess": "Kod parçacıkları başarıyla dışa aktarıldı",
"exportFailed": "Kod parçacıkları dışa aktarılamadı",
"importTitle": "Kod Parçacıklarını İçe Aktar",
"importDescription": "Termix tarafından dışa aktarılan bir JSON dosyasından kod parçacıklarını ve klasörleri içe aktar",
"importDropOrClick": "Bir JSON dosyasını buraya sürükleyin veya göz atmak için tıklayın",
"importSelectedFile": "Seçili: {{name}}",
"importOverwrite": "Aynı ada ve klasöre sahip mevcut kod parçacıklarının üzerine yaz",
"importStartBtn": "İçe Aktar",
"importSuccess": "İçe aktarma tamamlandı: {{snippets}} kod parçacığı eklendi, {{updated}} güncellendi, {{skipped}} atlandı, {{folders}} klasör eklendi",
"importFailed": "Kod parçacıkları içe aktarılamadı",
"importInvalidFile": "Geçersiz dosya: snippets veya folders dizileri içeren bir JSON nesnesi bekleniyor",
"targetHostsLabel": "Hedef Sunucular",
"targetHostsHint": "Bu kod parçacığını açık bir terminal olmadan doğrudan çalıştırmak için sunucular atayın.",
"noHostsAvailable": "Yapılandırılmış sunucu yok",
"clearTargetHosts": "Tümünü temizle",
"hasTargetHosts": "Hedef sunuculara sahip",
"runOnTargets": "Hedeflerde Çalıştır",
"directRunSuccess": "{{count}} sunucuda \"{{name}}\" çalıştırıldı",
"directRunPartialFail": "\"{{name}}\" bir veya daha fazla sunucuda başarısız oldu",
"executionResultTitle": "Çalıştırma Sonuçları: {{name}}",
"executionResultDescription": "Her hedef sunucuda parçacığı çalıştırmanın çıktısı.",
"executionSuccess": "Başarılı",
"executionFailed": "Başarısız",
"expandAll": "Tüm klasörleri genişlet",
"collapseAll": "Tüm klasörleri daralt",
"settings": "Kod parçacığı ayarları",
"variablesHint": "Değişkenler: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Çalıştır \"{{name}}\"",
"variablesDialogDescription": "Bu kod parçasını çalıştırmadan önce gerekli değerleri girin.",
"variablesPreviewLabel": "Önizleme",
"variablesConfirmButton": "Koşmak"
},
"keybindings": {
"title": "Klavye Kısayolları",
"description": "Terminaldeki kopyalama, yapıştırma ve kontrol kısayollarını özelleştirin veya metin göndermek ya da kod parçacığı çalıştırmak için tuş atamaları yapın.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Tıklayın ve tuşlara basın",
"recording": "Tuşlara basın...",
"comboRequiredError": "Önce bir tuş kombinasyonuna basın.",
"textRequiredError": "Göndermek istediğiniz metni girin.",
"controlCodeRequiredError": "Kontrol kodu için tek bir harf girin.",
"snippetRequiredError": "Bir parça seçin",
"conflictWarning": "Bu kombinasyon zaten {{combo}}ile ilişkilendirilmiş durumda. Kaydetme işlemi her ikisinin de çalışmasını sağlayacaktır.",
"actionLabel": "Aksiyon",
"actionCopy": "Seçimi kopyala",
"actionPaste": "Yapıştır",
"actionSendControlCode": "Ctrl+harf sinyali gönder",
"actionSendText": "Mesaj gönder",
"actionRunSnippet": "Mevcut kod parçasını çalıştır",
"actionNextTab": "Sonraki sekmeye geç",
"actionPreviousTab": "Önceki sekmeye geç",
"actionOpenCommandPalette": "Hızlı aramayı aç",
"controlCodeLabel": "Mektup",
"textLabel": "Gönderilecek metin",
"appendEnterLabel": "Gönderdikten sonra Enter tuşuna basın.",
"snippetLabel": "Alıntı",
"selectSnippetPlaceholder": "Bir parça seçin",
"orphanedSnippetWarning": "Kod parçası bulunamadı",
"clipboardPermissionNote": "Bazı tarayıcılarda ilk kullanımda panoya kopyalama izni isteyebilir."
"actionOpenCommandPalette": "Hızlı aramayı aç"
},
"userProfile": {
"donateTitle": "Termix'i canlı tutun",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Konu zorunludur",
"ntfyToken": "Erişim Anahtarı (isteğe bağlı)",
"channelSaveFailed": "Kanal kaydedilemedi"
},
"macros": {
"title": "Makrolar",
"description": "Önceden kaydedilmiş bir komut dizisini terminalde çalıştırın ve sunucunun geri gönderdiği yanıtlara göre hareket edin.",
"target": "Hedef",
"noTerminal": "Açık terminal yok.",
"empty": "Henüz makro yok",
"emptyHint": "Giriş yapma, bir hizmeti yeniden başlatma veya komut istemlerine yanıt verme gibi terminal işlemlerini otomatikleştirmek için bir makro oluşturun.",
"create": "Yeni makro",
"back": "Makrolara geri dön",
"name": "İsim",
"namePlaceholder": "Web sunucusunu yeniden başlatın.",
"nameRequired": "Kaydetmeden önce makroya bir ad verin.",
"descriptionLabel": "Tanım",
"descriptionPlaceholder": "Bu makronun ne yaptığı (isteğe bağlı)",
"unsaved": "Kaydedilmemiş değişiklikler",
"save": "Kaydetmek",
"saved": "Makro kaydedildi.",
"saveFailed": "Makro kaydedilemedi.",
"delete": "Silmek",
"deleteTitle": "Bu makroyu silmek ister misiniz?",
"deleteBody": "\"{{name}}\" kalıcı olarak silinecektir. Bu işlem geri alınamaz.",
"deleted": "Makro silindi",
"cancel": "İptal etmek",
"run": "Koşmak",
"stop": "Durmak",
"running": "Koşma...",
"completed": "Makro tamamlandı",
"failed": "Makro başarısız oldu",
"loadFailed": "Makrolar yüklenemedi.",
"needTerminal": "Öncelikle bir terminal açın ve bağlayın.",
"noSteps": "Bu makronun henüz hiçbir adımı yok. Aşağıya bir adım ekleyin.",
"addStep": "Adım ekle",
"stepNumber": "Adım {{number}}",
"removeStep": "Adımı kaldır",
"confirmRunTitle": "Bu makroyu çalıştırayım mı?",
"confirmRunBody": "\"{{name}}\" {{host}} adresine komutlar gönderecektir.",
"confirmRunAction": "Makroyu çalıştır",
"stepTypes": {
"send": {
"label": "Komut gönder",
"help": "Terminale, sanki siz yazmışsınız gibi metin yazar."
},
"wait": {
"label": "Çıktıyı bekleyin",
"help": "Sunucu, belirttiğiniz metni (örneğin parola istemi gibi) yazdırana kadar bekler."
},
"delay": {
"label": "Duraklat",
"help": "Bir sonraki adıma geçmeden önce belirli bir süre bekler."
},
"if": {
"label": "Çıktı içeriyorsa",
"help": "Sunucunun şu ana kadar yazdırdığı her şeyi kontrol eder, ardından iki daldan birini çalıştırır."
},
"repeat": {
"label": "Tekrarlamak",
"help": "İçindeki adımları belirtilen sayıda tekrarlar."
}
},
"send": {
"text": "Komut veya metin",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Gönderdikten sonra Enter tuşuna basın.",
"pressEnterHelp": "Göndermeden metin yazmak için bunu kapatın."
},
"wait": {
"pattern": "Çıktıyı oluşturana kadar bekleyin.",
"patternPlaceholder": "şifre:",
"timeout": "Vazgeçtikten sonra",
"onTimeout": "Eğer hiç ortaya çıkmazsa",
"stop": "Makroyu durdur",
"continue": "Yine de devam edin."
},
"delay": {
"duration": "Bekleyin"
},
"ifStep": {
"pattern": "Çıktı şunları içeriyorsa:",
"patternPlaceholder": "Aktif: aktif (koşmak)",
"then": "O halde şunu yapın",
"else": "Aksi takdirde şunu yapın",
"emptyBranch": "Hiçbir adım yok"
},
"repeat": {
"count": "Sayısı",
"times": "kez"
},
"match": {
"useRegex": "Düzenli ifadeyle eşleştirin",
"useRegexHelp": "Metnin aynen yazıldığı gibi kalması için bunu atlayın.",
"invalidRegex": "Geçerli bir normal ifade değil, düz metin olarak eşleştirilecektir."
},
"seconds": "saniyeler",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Налаштування бічної панелі...",
"hosts": "Хости",
"snippets": "Фрагменти",
"macros": "Макроси",
"hostManager": "Менеджер хостингу",
"credentials": "Облікові дані",
"connections": "З'єднання",
@@ -869,7 +868,6 @@
"groupTags": "Теги та закріплення",
"groupTunnels": "Тунелі",
"groupJumpHosts": "Хости Jump",
"groupQuickActions": "Швидкі дії",
"groupFeatureFlags": "Прапорці функцій",
"groupAdvanced": "Розширена конфігурація",
"preview": "Попередній перегляд",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Зберігайте SSH-ключі або паролі",
"tmuxMonitor": "Монітор Tmux",
"tmuxMonitorDesc": "Моніторинг сесій tmux на ваших хостах",
"snippets": "Фрагменти",
"runSnippetDesc": "Запустити в активній вкладці терміналу",
"noSnippetsFound": "Не знайдено фрагментів, що відповідають \"{{search}}\"",
"recentActivity": "Нещодавня активність",
"serversAndHosts": "Сервери та хости",
"noHostsFound": "Не знайдено хостів, що відповідають \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Попередня вкладка",
"hotkeysJumpTab": "Перейти до вкладки"
},
"snippets": {
"title": "Фрагменти",
"createSnippetTitle": "Створити фрагмент",
"createSnippetDescription": "Створіть новий фрагмент команди для швидкого виконання",
"nameLabel": "Ім'я",
"namePlaceholder": "наприклад, перезавантажте Nginx",
"descriptionLabel": "Опис",
"descriptionPlaceholder": "Додатковий опис",
"optional": "Необов'язково",
"folderLabel": "Папка",
"noFolder": "Немає папки (Без категорії)",
"commandLabel": "Команда",
"commandPlaceholder": "наприклад, sudo systemctl перезавантажити nginx",
"typeLabel": "Тип",
"typeCommand": "Команда",
"typeNote": "Примітка",
"noteLabel": "Примітка",
"notePlaceholder": "наприклад, текст нагадування або підказки для повторного використання",
"cancel": "Скасувати",
"createSnippetButton": "Створити фрагмент",
"createFolderTitle": "Створити папку",
"createFolderDescription": "Упорядкуйте свої фрагменти коду в папки",
"folderNameLabel": "Назва папки",
"folderNamePlaceholder": "наприклад, системні команди, скрипти Docker",
"folderColorLabel": "Колір папки",
"folderIconLabel": "Піктограма папки",
"previewLabel": "Попередній перегляд",
"folderNameFallback": "Назва папки",
"createFolderButton": "Створити папку",
"targetTerminals": "Цільові термінали",
"selectAll": "Усі",
"selectNone": "Жоден",
"noTerminalTabsOpen": "Немає відкритих вкладок терміналу",
"searchPlaceholder": "Пошук фрагментів...",
"newSnippet": "Новий фрагмент",
"newFolder": "Нова папка",
"run": "Бігти",
"noSnippetsInFolder": "У цій папці немає фрагментів",
"uncategorized": "Без категорії",
"editSnippetTitle": "Редагувати фрагмент",
"editSnippetDescription": "Оновіть цей фрагмент команди",
"saveSnippetButton": "Зберегти зміни",
"createSuccess": "Фрагмент успішно створено",
"createFailed": "Не вдалося створити фрагмент",
"updateSuccess": "Фрагмент успішно оновлено",
"updateFailed": "Не вдалося оновити фрагмент",
"deleteFailed": "Не вдалося видалити фрагмент",
"folderCreateSuccess": "Папку успішно створено",
"folderCreateFailed": "Не вдалося створити папку",
"editFolderTitle": "Редагувати папку",
"editFolderDescription": "Перейменуйте або змініть зовнішній вигляд цієї папки",
"saveFolderButton": "Зберегти зміни",
"editFolder": "Редагувати папку",
"deleteFolder": "Видалити папку",
"folderDeleteSuccess": "Папку «{{name}}» видалено",
"folderDeleteFailed": "Не вдалося видалити папку",
"folderEditSuccess": "Папку успішно оновлено",
"folderEditFailed": "Не вдалося оновити папку",
"confirmRunMessage": "Запустити \"{{name}}\"?",
"confirmRunButton": "Бігти",
"runSuccess": "Виконано \"{{name}}\" у {{count}} терміналі(ах)",
"pasteSuccess": "Вставлено \"{{name}}\" у термінал(и) {{count}}",
"pasteToTerminal": "Вставити",
"copySuccess": "Скопійовано \"{{name}}\" у буфер обміну",
"shareTitle": "Поділитися фрагментом",
"shareUser": "Користувач",
"shareRole": "Роль",
"selectUser": "Виберіть користувача...",
"selectRole": "Виберіть роль...",
"shareSuccess": "Фрагмент успішно поділився",
"shareFailed": "Не вдалося поділитися фрагментом",
"revokeSuccess": "Доступ скасовано",
"revokeFailed": "Не вдалося скасувати доступ",
"currentAccess": "Поточний доступ",
"shareLoadError": "Не вдалося завантажити дані спільного доступу",
"loading": "Завантаження...",
"close": "Закрити",
"reorderFailed": "Не вдалося зберегти порядок фрагментів",
"importExport": "Імпорт / Експорт",
"exportBtn": "Експорт JSON",
"importBtn": "Імпорт JSON",
"exportSuccess": "Фрагменти успішно експортовано",
"exportFailed": "Не вдалося експортувати фрагменти",
"importTitle": "Імпорт фрагментів",
"importDescription": "Імпорт фрагментів та папок з JSON-файлу, експортованого Termix",
"importDropOrClick": "Перетягніть сюди JSON-файл або натисніть, щоб переглянути",
"importSelectedFile": "Вибрано: {{name}}",
"importOverwrite": "Перезаписати існуючі фрагменти з тією ж назвою та папкою",
"importStartBtn": "Імпорт",
"importSuccess": "Імпорт завершено: додано {{snippets}} фрагментів, оновлено {{updated}} , пропущено {{skipped}} , додано папок {{folders}}",
"importFailed": "Не вдалося імпортувати фрагменти",
"importInvalidFile": "Недійсний файл: очікується об'єкт JSON з фрагментами або масивами папок",
"targetHostsLabel": "Цільові хости",
"targetHostsHint": "Призначте хости для запуску цього фрагмента коду безпосередньо без відкритого терміналу.",
"noHostsAvailable": "Немає налаштованих хостів",
"clearTargetHosts": "Очистити все",
"hasTargetHosts": "Має цільові хости",
"runOnTargets": "Біг за цілями",
"directRunSuccess": "Виконано \"{{name}}\" на хості(ах) {{count}}",
"directRunPartialFail": "«{{name}}» не вдалося виконати на одному або кількох хостах",
"executionResultTitle": "Результати виконання: {{name}}",
"executionResultDescription": "Вивідні дані запуску фрагмента коду на кожному цільовому хості.",
"executionSuccess": "Успіх",
"executionFailed": "Не вдалося",
"expandAll": "Розгорнути всі папки",
"collapseAll": "Згорнути всі папки",
"settings": "Налаштування фрагмента",
"variablesHint": "Змінні: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Мітка}",
"variablesDialogTitle": "Виконати \"{{name}}\"",
"variablesDialogDescription": "Заповніть значення, необхідні цьому фрагменту, перш ніж запускати його.",
"variablesPreviewLabel": "Попередній перегляд",
"variablesConfirmButton": "Бігти"
},
"keybindings": {
"title": "Комбінації клавіш",
"description": "Налаштуйте комбінації клавіш для копіювання, вставки та керування терміналом або прив’яжіть клавіші для надсилання тексту чи запуску фрагмента коду.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Клацайте та натискайте клавіші",
"recording": "Натискайте клавіші...",
"comboRequiredError": "Спочатку натисніть комбінацію клавіш",
"textRequiredError": "Введіть текст для надсилання",
"controlCodeRequiredError": "Введіть одну літеру для контрольного коду",
"snippetRequiredError": "Виберіть фрагмент",
"conflictWarning": "Ця комбінація вже пов'язана з {{combo}}. Збереження призведе до спрацювання обох.",
"actionLabel": "Дія",
"actionCopy": "Копіювати виділення",
"actionPaste": "Вставити",
"actionSendControlCode": "Надіслати сигнал Ctrl+літера",
"actionSendText": "Надіслати текст",
"actionRunSnippet": "Запустити існуючий фрагмент коду",
"actionNextTab": "Перейти до наступної вкладки",
"actionPreviousTab": "Перейти до попередньої вкладки",
"actionOpenCommandPalette": "Відкрити швидкий пошук",
"controlCodeLabel": "Лист",
"textLabel": "Текст для надсилання",
"appendEnterLabel": "Натисніть Enter після відправлення",
"snippetLabel": "Фрагмент",
"selectSnippetPlaceholder": "Виберіть фрагмент",
"orphanedSnippetWarning": "фрагмент не знайдено",
"clipboardPermissionNote": "У деяких браузерах може запитуватися дозвіл на використання буфера обміну під час першого використання."
"actionOpenCommandPalette": "Відкрити швидкий пошук"
},
"userProfile": {
"donateTitle": "Збережи Термікс живим",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Тема обов'язкова",
"ntfyToken": "Маркер доступу (необов'язково)",
"channelSaveFailed": "Не вдалося зберегти канал"
},
"macros": {
"title": "Макроси",
"description": "Виконайте збережену серію команд у терміналі та відреагуйте на те, що надсилає сервер.",
"target": "Ціль",
"noTerminal": "Термінал не відкритий",
"empty": "Поки що немає макросів",
"emptyHint": "Створіть макрос для автоматизації процесу роботи термінала, такого як вхід у систему, перезапуск служби або відповідь на запити.",
"create": "Новий макрос",
"back": "Повернутися до макросів",
"name": "Ім'я",
"namePlaceholder": "Перезапустіть веб-сервер",
"nameRequired": "Введіть ім'я макросу перед збереженням",
"descriptionLabel": "Опис",
"descriptionPlaceholder": "Що робить цей макрос (необов'язково)",
"unsaved": "Незбережені зміни",
"save": "Зберегти",
"saved": "Макрос збережено",
"saveFailed": "Не вдалося зберегти макрос",
"delete": "Видалити",
"deleteTitle": "Видалити цей макрос?",
"deleteBody": "«{{name}}» буде видалено назавжди. Цю дію неможливо скасувати.",
"deleted": "Макрос видалено",
"cancel": "Скасувати",
"run": "Бігти",
"stop": "СТІЙ",
"running": "Біг...",
"completed": "Макрос завершено",
"failed": "Помилка макросу",
"loadFailed": "Не вдалося завантажити макроси",
"needTerminal": "Спочатку відкрийте та підключіть термінал",
"noSteps": "Цей макрос ще не має кроків. Додайте один нижче.",
"addStep": "Додати крок",
"stepNumber": "Крок {{number}}",
"removeStep": "Видалити крок",
"confirmRunTitle": "Запустити цей макрос?",
"confirmRunBody": "\"{{name}}\" надсилатиме команди до {{host}}.",
"confirmRunAction": "Запустити макрос",
"stepTypes": {
"send": {
"label": "Надіслати команду",
"help": "Вводить текст у термінал так само, як і надруковано."
},
"wait": {
"label": "Зачекайте на вивід",
"help": "Призупиняється, доки сервер не надрукує вказаний вами текст, наприклад, запит на пароль."
},
"delay": {
"label": "Пауза",
"help": "Очікує фіксований проміжок часу перед наступним кроком."
},
"if": {
"label": "Якщо вихід містить",
"help": "Перевіряє все, що сервер надрукував до цього часу, а потім запускає одну з двох гілок."
},
"repeat": {
"label": "Повторити",
"help": "Виконує кроки всередині нього задану кількість разів."
}
},
"send": {
"text": "Команда або текст",
"textPlaceholder": "перезапуск systemctl nginx",
"pressEnter": "Натисніть Enter після відправлення",
"pressEnterHelp": "Вимкніть цю функцію, щоб вводити текст, не надсилаючи його."
},
"wait": {
"pattern": "Зачекайте, поки вихідні дані міститимуть",
"patternPlaceholder": "пароль:",
"timeout": "Здайся після",
"onTimeout": "Якщо воно ніколи не з'явиться",
"stop": "Зупинити макрос",
"continue": "Продовжуй у будь-якому разі"
},
"delay": {
"duration": "Зачекайте"
},
"ifStep": {
"pattern": "Якщо вихід містить",
"patternPlaceholder": "Активний: активний (працює)",
"then": "Тоді зробіть це",
"else": "Інакше зробіть це",
"emptyBranch": "Без сходинок"
},
"repeat": {
"count": "Кількість разів",
"times": "рази"
},
"match": {
"useRegex": "Зіставлення з регулярним виразом",
"useRegexHelp": "Не виключайте це, щоб текст точно відповідав написаному.",
"invalidRegex": "Недійсний регулярний вираз, буде знайдено звичайний текст"
},
"seconds": "секунди",
"secondsShort": "{{count}}с"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "Cài đặt thanh bên...",
"hosts": "Máy chủ",
"snippets": "Đoạn mã",
"macros": "Macro",
"hostManager": "Quản lý máy chủ",
"credentials": "Thông tin đăng nhập",
"connections": "Kết nối",
@@ -869,7 +868,6 @@
"groupTags": "Thẻ & ghim",
"groupTunnels": "Đường hầm",
"groupJumpHosts": "Người dẫn chương trình Jump",
"groupQuickActions": "Hành động nhanh chóng",
"groupFeatureFlags": "Cờ tính năng",
"groupAdvanced": "Cấu hình nâng cao",
"preview": "Xem trước",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "Lưu trữ khóa SSH hoặc mật khẩu",
"tmuxMonitor": "Giám sát Tmux",
"tmuxMonitorDesc": "Giám sát phiên tmux trên các máy chủ",
"snippets": "Những đoạn trích",
"runSnippetDesc": "Chạy trong tab cửa sổ terminal đang hoạt động",
"noSnippetsFound": "Không tìm thấy đoạn mã nào khớp với \"{{search}}\"",
"recentActivity": "Hoạt động gần đây",
"serversAndHosts": "Máy chủ & Host",
"noHostsFound": "Không tìm thấy máy chủ nào khớp với \"{{search}}\"",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "Tab trước",
"hotkeysJumpTab": "Chuyển đến tab"
},
"snippets": {
"title": "Đoạn mã",
"createSnippetTitle": "Tạo đoạn mã",
"createSnippetDescription": "Tạo đoạn mã lệnh mới để thực thi nhanh",
"nameLabel": "Tên",
"namePlaceholder": "Ví dụ: Khởi động lại Nginx",
"descriptionLabel": "Mô tả",
"descriptionPlaceholder": "Mô tả tùy chọn",
"optional": "Tùy chọn",
"folderLabel": "Thư mục",
"noFolder": "Không thư mục (Chưa phân loại)",
"commandLabel": "Lệnh",
"commandPlaceholder": "Ví dụ: sudo systemctl restart nginx",
"typeLabel": "Kiểu",
"typeCommand": "Yêu cầu",
"typeNote": "Ghi chú",
"noteLabel": "Ghi chú",
"notePlaceholder": "Ví dụ: một lời nhắc hoặc văn bản nhắc nhở có thể tái sử dụng.",
"cancel": "Hủy",
"createSnippetButton": "Tạo đoạn mã",
"createFolderTitle": "Tạo thư mục",
"createFolderDescription": "Sắp xếp đoạn mã vào các thư mục",
"folderNameLabel": "Tên thư mục",
"folderNamePlaceholder": "Ví dụ: Lệnh hệ thống, Docker Scripts",
"folderColorLabel": "Màu thư mục",
"folderIconLabel": "Biểu tượng thư mục",
"previewLabel": "Xem trước",
"folderNameFallback": "Tên thư mục",
"createFolderButton": "Tạo thư mục",
"targetTerminals": "Terminal đích",
"selectAll": "Tất cả",
"selectNone": "Không chọn",
"noTerminalTabsOpen": "Không có tab terminal nào mở",
"searchPlaceholder": "Tìm kiếm snippet...",
"newSnippet": "Snippet mới",
"newFolder": "Thư mục mới",
"run": "Chạy",
"noSnippetsInFolder": "Không có snippet nào trong thư mục này",
"uncategorized": "Chưa phân loại",
"editSnippetTitle": "Chỉnh sửa snippet",
"editSnippetDescription": "Cập nhật snippet lệnh này",
"saveSnippetButton": "Lưu thay đổi",
"createSuccess": "Đã tạo snippet thành công",
"createFailed": "Tạo snippet thất bại",
"updateSuccess": "Đã cập nhật snippet thành công",
"updateFailed": "Cập nhật snippet thất bại",
"deleteFailed": "Xóa snippet thất bại",
"folderCreateSuccess": "Đã tạo thư mục thành công",
"folderCreateFailed": "Tạo thư mục thất bại",
"editFolderTitle": "Chỉnh sửa thư mục",
"editFolderDescription": "Đổi tên hoặc thay đổi hình thức của thư mục này",
"saveFolderButton": "Lưu thay đổi",
"editFolder": "Chỉnh sửa thư mục",
"deleteFolder": "Xóa thư mục",
"folderDeleteSuccess": "Đã xóa thư mục \"{{name}}\"",
"folderDeleteFailed": "Xóa thư mục thất bại",
"folderEditSuccess": "Đã cập nhật thư mục thành công",
"folderEditFailed": "Cập nhật thư mục thất bại",
"confirmRunMessage": "Chạy \"{{name}}\"?",
"confirmRunButton": "Chạy",
"runSuccess": "Đã chạy \"{{name}}\" trên {{count}} terminal",
"pasteSuccess": "Đã dán \"{{name}}\" vào {{count}} thiết bị đầu cuối",
"pasteToTerminal": "Dán",
"copySuccess": "Đã sao chép \"{{name}}\" vào clipboard",
"shareTitle": "Chia sẻ snippet",
"shareUser": "Người dùng",
"shareRole": "Vai trò",
"selectUser": "Chọn người dùng...",
"selectRole": "Chọn vai trò...",
"shareSuccess": "Đã chia sẻ đoạn mã thành công",
"shareFailed": "Chia sẻ đoạn mã thất bại",
"revokeSuccess": "Đã thu hồi quyền truy cập",
"revokeFailed": "Thu hồi quyền truy cập thất bại",
"currentAccess": "Quyền truy cập hiện tại",
"shareLoadError": "Tải dữ liệu chia sẻ thất bại",
"loading": "Đang tải...",
"close": "Đóng",
"reorderFailed": "Lưu thứ tự đoạn mã thất bại",
"importExport": "Nhập / Xuất",
"exportBtn": "Xuất JSON",
"importBtn": "Nhập JSON",
"exportSuccess": "Đã xuất đoạn mã thành công",
"exportFailed": "Xuất đoạn mã thất bại",
"importTitle": "Nhập đoạn mã",
"importDescription": "Nhập đoạn mã và thư mục từ tệp JSON được Termix xuất ra",
"importDropOrClick": "Kéo thả tệp JSON vào đây hoặc nhấn để duyệt",
"importSelectedFile": "Đã chọn: {{name}}",
"importOverwrite": "Ghi đè đoạn mã hiện có cùng tên và thư mục",
"importStartBtn": "Nhập",
"importSuccess": "Nhập xong: đã thêm {{snippets}} đoạn mã, cập nhật {{updated}}, bỏ qua {{skipped}}, thêm {{folders}} thư mục",
"importFailed": "Nhập đoạn mã thất bại",
"importInvalidFile": "Tệp không hợp lệ: cần một đối tượng JSON với mảng đoạn mã hoặc thư mục",
"targetHostsLabel": "Máy chủ đích",
"targetHostsHint": "Gán máy chủ để chạy đoạn mã này trực tiếp mà không cần mở terminal.",
"noHostsAvailable": "Chưa cấu hình máy chủ nào",
"clearTargetHosts": "Xóa tất cả",
"hasTargetHosts": "Có máy chủ đích",
"runOnTargets": "Chạy trên máy đích",
"directRunSuccess": "Đã chạy \"{{name}}\" trên {{count}} máy chủ",
"directRunPartialFail": "\"{{name}}\" chạy thất bại trên một hoặc nhiều máy chủ",
"executionResultTitle": "Kết quả thực thi: {{name}}",
"executionResultDescription": "Kết quả từ việc chạy đoạn mã trên từng máy đích.",
"executionSuccess": "Thành công",
"executionFailed": "Thất bại",
"expandAll": "Mở rộng tất cả các thư mục",
"collapseAll": "Thu gọn tất cả các thư mục",
"settings": "Cài đặt đoạn mã",
"variablesHint": "Biến: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}",
"variablesDialogTitle": "Chạy \"{{name}}\"",
"variablesDialogDescription": "Hãy điền đầy đủ các giá trị cần thiết cho đoạn mã này trước khi chạy.",
"variablesPreviewLabel": "Xem trước",
"variablesConfirmButton": "Chạy"
},
"keybindings": {
"title": "Phím tắt",
"description": "Tùy chỉnh các phím tắt sao chép, dán và điều khiển trong terminal, hoặc gán phím để gửi văn bản hoặc chạy một đoạn mã.",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "Nhấp chuột và nhấn phím",
"recording": "Nhấn các phím...",
"comboRequiredError": "Trước tiên, hãy nhấn tổ hợp phím.",
"textRequiredError": "Nhập văn bản cần gửi",
"controlCodeRequiredError": "Nhập một chữ cái duy nhất cho mã điều khiển.",
"snippetRequiredError": "Chọn một đoạn trích",
"conflictWarning": "Sự kết hợp này đã được liên kết với {{combo}}. Lưu lại sẽ khiến cả hai đều bốc cháy.",
"actionLabel": "Hoạt động",
"actionCopy": "Sao chép lựa chọn",
"actionPaste": "Dán",
"actionSendControlCode": "Gửi tín hiệu Ctrl+chữ cái",
"actionSendText": "Gửi tin nhắn",
"actionRunSnippet": "Chạy đoạn mã hiện có",
"actionNextTab": "Chuyển sang tab tiếp theo",
"actionPreviousTab": "Chuyển về tab trước",
"actionOpenCommandPalette": "Mở tìm kiếm nhanh",
"controlCodeLabel": "Thư",
"textLabel": "Văn bản để gửi",
"appendEnterLabel": "Nhấn Enter sau khi gửi",
"snippetLabel": "Đoạn trích",
"selectSnippetPlaceholder": "Chọn một đoạn trích",
"orphanedSnippetWarning": "Không tìm thấy đoạn mã.",
"clipboardPermissionNote": "Một số trình duyệt có thể yêu cầu quyền truy cập clipboard khi sử dụng lần đầu."
"actionOpenCommandPalette": "Mở tìm kiếm nhanh"
},
"userProfile": {
"donateTitle": "Duy trì Termix",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "Chủ đề là bắt buộc",
"ntfyToken": "Mã truy cập (tùy chọn)",
"channelSaveFailed": "Lỗi khi lưu kênh"
},
"macros": {
"title": "Macro",
"description": "Chạy một chuỗi lệnh đã lưu trong cửa sổ dòng lệnh và phản hồi lại những gì máy chủ gửi về.",
"target": "Mục tiêu",
"noTerminal": "Không có thiết bị đầu cuối nào đang mở",
"empty": "Chưa có macro nào.",
"emptyHint": "Tạo macro để tự động hóa quy trình trên thiết bị đầu cuối, chẳng hạn như đăng nhập, khởi động lại dịch vụ hoặc trả lời các lời nhắc.",
"create": "Macro mới",
"back": "Quay lại phần macro",
"name": "Tên",
"namePlaceholder": "Khởi động lại máy chủ web",
"nameRequired": "Đặt tên cho macro trước khi lưu.",
"descriptionLabel": "Sự miêu tả",
"descriptionPlaceholder": "Chức năng của macro này (tùy chọn)",
"unsaved": "Thay đổi chưa được lưu",
"save": "Cứu",
"saved": "Macro đã được lưu",
"saveFailed": "Không thể lưu macro",
"delete": "Xóa bỏ",
"deleteTitle": "Xóa macro này?",
"deleteBody": "\"{{name}}\" sẽ bị xóa vĩnh viễn. Không thể hoàn tác.",
"deleted": "Macro đã bị xóa",
"cancel": "Hủy bỏ",
"run": "Chạy",
"stop": "Dừng lại",
"running": "Đang chạy...",
"completed": "Macro đã hoàn thành",
"failed": "Macro bị lỗi",
"loadFailed": "Không thể tải macro",
"needTerminal": "Trước tiên hãy mở và kết nối thiết bị đầu cuối.",
"noSteps": "Macro này chưa có bước nào. Hãy thêm một bước bên dưới.",
"addStep": "Thêm bước",
"stepNumber": "Bước {{number}}",
"removeStep": "Xóa bước",
"confirmRunTitle": "Chạy macro này?",
"confirmRunBody": "\"{{name}}\" sẽ gửi lệnh đến {{host}}.",
"confirmRunAction": "Chạy macro",
"stepTypes": {
"send": {
"label": "Gửi lệnh",
"help": "Nhập văn bản vào cửa sổ dòng lệnh, giống như khi bạn tự nhập văn bản."
},
"wait": {
"label": "Chờ kết quả đầu ra",
"help": "Tạm dừng cho đến khi máy chủ in ra đoạn văn bản bạn đã đặt tên, ví dụ như lời nhắc nhập mật khẩu."
},
"delay": {
"label": "Tạm dừng",
"help": "Chờ một khoảng thời gian cố định trước khi thực hiện bước tiếp theo."
},
"if": {
"label": "Nếu đầu ra chứa",
"help": "Kiểm tra tất cả những gì máy chủ đã in ra cho đến nay, sau đó chạy một trong hai nhánh."
},
"repeat": {
"label": "Lặp lại",
"help": "Thực thi các bước bên trong nó một số lần nhất định."
}
},
"send": {
"text": "Lệnh hoặc văn bản",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "Nhấn Enter sau khi gửi",
"pressEnterHelp": "Tắt tùy chọn này để nhập văn bản mà không cần gửi đi."
},
"wait": {
"pattern": "Chờ cho đến khi đầu ra chứa",
"patternPlaceholder": "mật khẩu:",
"timeout": "Bỏ cuộc sau",
"onTimeout": "Nếu nó không bao giờ xuất hiện",
"stop": "Dừng macro",
"continue": "Cứ tiếp tục đi"
},
"delay": {
"duration": "Chờ đợi"
},
"ifStep": {
"pattern": "Nếu đầu ra chứa",
"patternPlaceholder": "Đang hoạt động: đang hoạt động (đang chạy)",
"then": "Sau đó hãy làm như thế này",
"else": "Nếu không thì hãy làm như thế này",
"emptyBranch": "Không có bước nào"
},
"repeat": {
"count": "Số lần",
"times": "lần"
},
"match": {
"useRegex": "So khớp với biểu thức chính quy",
"useRegexHelp": "Bỏ phần này đi để văn bản khớp chính xác với nội dung đã viết.",
"invalidRegex": "Đây không phải là biểu thức chính quy hợp lệ, nó sẽ được so khớp như văn bản thuần túy."
},
"seconds": "giây",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "侧边栏设置...",
"hosts": "主机",
"snippets": "代码片段",
"macros": "宏",
"hostManager": "主机管理器",
"credentials": "凭据",
"connections": "连接",
@@ -869,7 +868,6 @@
"groupTags": "标签和置顶",
"groupTunnels": "隧道",
"groupJumpHosts": "跳板机",
"groupQuickActions": "快速操作",
"groupFeatureFlags": "功能标志",
"groupAdvanced": "高级配置",
"preview": "预览",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "存储 SSH 密钥或密码",
"tmuxMonitor": "Tmux 监控器",
"tmuxMonitorDesc": "监控主机上的 tmux 会话",
"snippets": "代码片段",
"runSnippetDesc": "在活动终端标签页中运行",
"noSnippetsFound": "未找到与“{{search}}”匹配的代码片段",
"recentActivity": "最近活动",
"serversAndHosts": "服务器与主机",
"noHostsFound": "未找到匹配 \"{{search}}\" 的主机",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "上一个标签页",
"hotkeysJumpTab": "跳转到标签页"
},
"snippets": {
"title": "代码片段",
"createSnippetTitle": "创建代码片段",
"createSnippetDescription": "创建新的命令代码片段以便快速执行",
"nameLabel": "名称",
"namePlaceholder": "例如:重启 Nginx",
"descriptionLabel": "描述",
"descriptionPlaceholder": "可选描述",
"optional": "可选",
"folderLabel": "文件夹",
"noFolder": "无文件夹(未分类)",
"commandLabel": "命令",
"commandPlaceholder": "例如:sudo systemctl restart nginx",
"typeLabel": "类型",
"typeCommand": "命令",
"typeNote": "笔记",
"noteLabel": "笔记",
"notePlaceholder": "例如,可重复使用的提示或提醒文本",
"cancel": "取消",
"createSnippetButton": "创建代码片段",
"createFolderTitle": "创建文件夹",
"createFolderDescription": "将代码片段整理到文件夹中",
"folderNameLabel": "文件夹名称",
"folderNamePlaceholder": "例如:系统命令、Docker 脚本",
"folderColorLabel": "文件夹颜色",
"folderIconLabel": "文件夹图标",
"previewLabel": "预览",
"folderNameFallback": "文件夹名称",
"createFolderButton": "创建文件夹",
"targetTerminals": "目标终端",
"selectAll": "全选",
"selectNone": "取消全选",
"noTerminalTabsOpen": "没有打开的终端标签页",
"searchPlaceholder": "搜索代码片段...",
"newSnippet": "新建代码片段",
"newFolder": "新建文件夹",
"run": "运行",
"noSnippetsInFolder": "此文件夹中没有代码片段",
"uncategorized": "未分类",
"editSnippetTitle": "编辑代码片段",
"editSnippetDescription": "更新此命令代码片段",
"saveSnippetButton": "保存更改",
"createSuccess": "代码片段创建成功",
"createFailed": "创建代码片段失败",
"updateSuccess": "代码片段更新成功",
"updateFailed": "更新代码片段失败",
"deleteFailed": "删除代码片段失败",
"folderCreateSuccess": "文件夹创建成功",
"folderCreateFailed": "创建文件夹失败",
"editFolderTitle": "编辑文件夹",
"editFolderDescription": "重命名或更改此文件夹的外观",
"saveFolderButton": "保存更改",
"editFolder": "编辑文件夹",
"deleteFolder": "删除文件夹",
"folderDeleteSuccess": "文件夹“{{name}}”已删除",
"folderDeleteFailed": "删除文件夹失败",
"folderEditSuccess": "文件夹更新成功",
"folderEditFailed": "更新文件夹失败",
"confirmRunMessage": "运行“{{name}}”?",
"confirmRunButton": "运行",
"runSuccess": "已在 {{count}} 个终端中运行“{{name}}”",
"pasteSuccess": "已将“{{name}}”粘贴到 {{count}} 终端中",
"pasteToTerminal": "粘贴",
"copySuccess": "已将“{{name}}”复制到剪贴板",
"shareTitle": "共享代码片段",
"shareUser": "用户",
"shareRole": "角色",
"selectUser": "选择一个用户...",
"selectRole": "选择一个角色...",
"shareSuccess": "代码片段共享成功",
"shareFailed": "共享代码片段失败",
"revokeSuccess": "访问权限已撤销",
"revokeFailed": "撤销访问权限失败",
"currentAccess": "当前访问权限",
"shareLoadError": "加载共享数据失败",
"loading": "加载中...",
"close": "关闭",
"reorderFailed": "保存代码片段顺序失败",
"importExport": "导入 / 导出",
"exportBtn": "导出 JSON",
"importBtn": "导入 JSON",
"exportSuccess": "代码片段导出成功",
"exportFailed": "导出代码片段失败",
"importTitle": "导入代码片段",
"importDescription": "从 Termix 导出的 JSON 文件导入代码片段和文件夹",
"importDropOrClick": "将 JSON 文件拖放到此处或点击浏览",
"importSelectedFile": "已选择:{{name}}",
"importOverwrite": "覆盖具有相同名称和文件夹的现有代码片段",
"importStartBtn": "导入",
"importSuccess": "导入完成:添加了 {{snippets}} 个代码片段,更新了 {{updated}} 个,跳过了 {{skipped}} 个,添加了 {{folders}} 个文件夹",
"importFailed": "导入代码片段失败",
"importInvalidFile": "无效文件:应为包含 snippets 或 folders 数组的 JSON 对象",
"targetHostsLabel": "目标主机",
"targetHostsHint": "分配主机,以便无需打开终端即可直接运行此代码片段。",
"noHostsAvailable": "未配置任何主机",
"clearTargetHosts": "清除全部",
"hasTargetHosts": "已分配目标主机",
"runOnTargets": "在目标上运行",
"directRunSuccess": "已在 {{count}} 台主机上运行 \"{{name}}\"",
"directRunPartialFail": "\"{{name}}\" 在一台或多台主机上运行失败",
"executionResultTitle": "执行结果:{{name}}",
"executionResultDescription": "在每台目标主机上运行代码片段的输出。",
"executionSuccess": "成功",
"executionFailed": "失败",
"expandAll": "展开所有文件夹",
"collapseAll": "收起所有文件夹",
"settings": "代码片段设置",
"variablesHint": "变量:$HOST、$USER、$PORT、$NAME、${INPUT_1:Label}",
"variablesDialogTitle": "运行“{{name}}”",
"variablesDialogDescription": "运行此代码片段之前,请填写所需的值。",
"variablesPreviewLabel": "预览",
"variablesConfirmButton": "运行"
},
"keybindings": {
"title": "键盘快捷键",
"description": "自定义终端复制、粘贴和控制快捷键,或绑定按键以发送文本或运行代码片段。",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "点击并按下按键",
"recording": "按下按键……",
"comboRequiredError": "先按下组合键",
"textRequiredError": "请输入要发送的文本",
"controlCodeRequiredError": "请输入单个字母作为控制码",
"snippetRequiredError": "选择代码片段",
"conflictWarning": "此组合已绑定到 {{combo}}。保存后两者都会生效。",
"actionLabel": "操作",
"actionCopy": "复制选择",
"actionPaste": "粘贴",
"actionSendControlCode": "发送 Ctrl+字母信号",
"actionSendText": "发送文本",
"actionRunSnippet": "运行现有代码片段",
"actionNextTab": "切换到下一个标签页",
"actionPreviousTab": "切换到上一个标签页",
"actionOpenCommandPalette": "打开快速搜索",
"controlCodeLabel": "字母",
"textLabel": "发送的文本",
"appendEnterLabel": "发送后按回车键",
"snippetLabel": "片段",
"selectSnippetPlaceholder": "选择代码片段",
"orphanedSnippetWarning": "未找到代码片段",
"clipboardPermissionNote": "某些浏览器在首次使用时可能会提示授予剪贴板权限。"
"actionOpenCommandPalette": "打开快速搜索"
},
"userProfile": {
"donateTitle": "让 Termix 持续运转",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "主题为必填项",
"ntfyToken": "访问令牌(选填)",
"channelSaveFailed": "保存渠道失败"
},
"macros": {
"title": "宏",
"description": "在终端中运行已保存的一系列命令,并对服务器返回的内容做出反应。",
"target": "目标",
"noTerminal": "没有终端打开",
"empty": "暂无宏",
"emptyHint": "创建一个宏来自动化终端流程,例如登录、重启服务或回答提示。",
"create": "新宏",
"back": "返回宏",
"name": "名称",
"namePlaceholder": "重启 Web 服务器",
"nameRequired": "保存之前,请给宏命名。",
"descriptionLabel": "描述",
"descriptionPlaceholder": "此宏的功能(可选)",
"unsaved": "未保存的更改",
"save": "保存",
"saved": "宏已保存",
"saveFailed": "无法保存宏",
"delete": "删除",
"deleteTitle": "删除这个宏?",
"deleteBody": "“{{name}}”将被永久删除。此操作无法撤销。",
"deleted": "宏已删除",
"cancel": "取消",
"run": "运行",
"stop": "停止",
"running": "运行中...",
"completed": "宏已完成",
"failed": "宏失败",
"loadFailed": "无法加载宏。",
"needTerminal": "首先打开并连接终端",
"noSteps": "此宏目前尚无步骤。请在下方添加步骤。",
"addStep": "添加步骤",
"stepNumber": "步骤 {{number}}",
"removeStep": "移除步骤",
"confirmRunTitle": "运行这个宏?",
"confirmRunBody": "\"{{name}}\" 将向 {{host}} 发送命令。",
"confirmRunAction": "运行宏",
"stepTypes": {
"send": {
"label": "发送命令",
"help": "在终端中输入文本,就像你手动输入一样。"
},
"wait": {
"label": "等待输出",
"help": "暂停,直到服务器打印出您指定的文本,例如密码提示。"
},
"delay": {
"label": "暂停",
"help": "等待一段固定时间后再执行下一步操作。"
},
"if": {
"label": "如果输出包含",
"help": "检查服务器目前为止打印的所有内容,然后运行两个分支中的一个。"
},
"repeat": {
"label": "重复",
"help": "将其中的步骤执行设定的次数。"
}
},
"send": {
"text": "命令或文本",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "发送后按回车键",
"pressEnterHelp": "关闭此功能即可输入文本而不提交。"
},
"wait": {
"pattern": "等待输出包含",
"patternPlaceholder": "密码:",
"timeout": "放弃",
"onTimeout": "如果它从未出现",
"stop": "停止宏",
"continue": "无论如何都继续"
},
"delay": {
"duration": "等待"
},
"ifStep": {
"pattern": "如果输出包含",
"patternPlaceholder": "活跃:正在运行",
"then": "然后这样做",
"else": "否则就这样做",
"emptyBranch": "没有步骤"
},
"repeat": {
"count": "次数",
"times": "次"
},
"match": {
"useRegex": "使用正则表达式进行匹配",
"useRegexHelp": "关闭此选项以完全按原文匹配文本。",
"invalidRegex": "这不是有效的正则表达式,它将被匹配为纯文本。"
},
"seconds": "秒",
"secondsShort": "{{count}}s"
}
}
+1 -229
View File
@@ -409,7 +409,6 @@
"sidebarSettings": "側邊欄設定...",
"hosts": "主機",
"snippets": "程式碼片段",
"macros": "宏",
"hostManager": "主機管理員",
"credentials": "憑證",
"connections": "連線",
@@ -869,7 +868,6 @@
"groupTags": "標籤和別針",
"groupTunnels": "隧道",
"groupJumpHosts": "跳轉主機",
"groupQuickActions": "快速行動",
"groupFeatureFlags": "功能標誌",
"groupAdvanced": "進階配置",
"preview": "預覽",
@@ -1068,9 +1066,6 @@
"addCredentialDesc": "儲存 SSH 金鑰或密碼",
"tmuxMonitor": "Tmux 監視器",
"tmuxMonitorDesc": "監控您所有主機的 tmux 會話",
"snippets": "片段",
"runSnippetDesc": "在活動終端選項卡中運行",
"noSnippetsFound": "找不到與「{{search}}」相符的程式碼片段",
"recentActivity": "近期活動",
"serversAndHosts": "伺服器與主機",
"noHostsFound": "找不到符合「{{search}}」的主機",
@@ -1864,119 +1859,6 @@
"hotkeysPrevTab": "上一個索引標籤",
"hotkeysJumpTab": "跳到標籤頁"
},
"snippets": {
"title": "指令片段",
"createSnippetTitle": "建立片段",
"createSnippetDescription": "建立新的指令片段以便快速執行",
"nameLabel": "名稱",
"namePlaceholder": "例如:重新啟動 Nginx",
"descriptionLabel": "說明",
"descriptionPlaceholder": "選用說明",
"optional": "選用",
"folderLabel": "資料夾",
"noFolder": "無資料夾(未分類)",
"commandLabel": "指令",
"commandPlaceholder": "例如:sudo systemctl restart nginx",
"typeLabel": "類型",
"typeCommand": "命令",
"typeNote": "筆記",
"noteLabel": "筆記",
"notePlaceholder": "例如,可重複使用的提示或提醒文字",
"cancel": "取消",
"createSnippetButton": "建立片段",
"createFolderTitle": "建立資料夾",
"createFolderDescription": "將您的片段整理至資料夾",
"folderNameLabel": "資料夾名稱",
"folderNamePlaceholder": "例如:系統指令、Docker 指令碼",
"folderColorLabel": "資料夾顏色",
"folderIconLabel": "資料夾圖示",
"previewLabel": "預覽",
"folderNameFallback": "資料夾名稱",
"createFolderButton": "建立資料夾",
"targetTerminals": "目標終端機",
"selectAll": "全選",
"selectNone": "取消全選",
"noTerminalTabsOpen": "無終端機分頁開啟",
"searchPlaceholder": "搜尋指令片段...",
"newSnippet": "新增指令片段",
"newFolder": "新增資料夾",
"run": "執行",
"noSnippetsInFolder": "此資料夾中無指令片段",
"uncategorized": "未分類",
"editSnippetTitle": "編輯指令片段",
"editSnippetDescription": "更新此指令片段",
"saveSnippetButton": "儲存變更",
"createSuccess": "指令片段建立成功",
"createFailed": "建立指令片段失敗",
"updateSuccess": "指令片段更新成功",
"updateFailed": "更新指令片段失敗",
"deleteFailed": "刪除指令片段失敗",
"folderCreateSuccess": "資料夾建立成功",
"folderCreateFailed": "建立資料夾失敗",
"editFolderTitle": "編輯資料夾",
"editFolderDescription": "重新命名或變更此資料夾的外觀",
"saveFolderButton": "儲存變更",
"editFolder": "編輯資料夾",
"deleteFolder": "刪除資料夾",
"folderDeleteSuccess": "資料夾 \"{{name}}\" 已刪除",
"folderDeleteFailed": "刪除資料夾失敗",
"folderEditSuccess": "資料夾更新成功",
"folderEditFailed": "更新資料夾失敗",
"confirmRunMessage": "執行 \"{{name}}\"?",
"confirmRunButton": "執行",
"runSuccess": "已在 {{count}} 個終端機執行 \"{{name}}\"",
"pasteSuccess": "已將「{{name}}」貼到 {{count}} 終端機中",
"pasteToTerminal": "貼上",
"copySuccess": "已將 \"{{name}}\" 複製到剪貼簿",
"shareTitle": "分享指令片段",
"shareUser": "使用者",
"shareRole": "角色",
"selectUser": "選擇使用者...",
"selectRole": "選擇角色...",
"shareSuccess": "片段分享成功",
"shareFailed": "分享片段失敗",
"revokeSuccess": "已撤銷存取權限",
"revokeFailed": "撤銷存取失敗",
"currentAccess": "目前的存取權限",
"shareLoadError": "載入分享資料失敗",
"loading": "載入中...",
"close": "關閉",
"reorderFailed": "儲存片段排序失敗",
"importExport": "匯入 / 匯出",
"exportBtn": "匯出 JSON",
"importBtn": "匯入 JSON",
"exportSuccess": "片段匯出成功",
"exportFailed": "匯出片段失敗",
"importTitle": "匯入片段",
"importDescription": "從 Termix 匯出的 JSON 檔案匯入片段和資料夾",
"importDropOrClick": "將 JSON 檔案拖放至此,或點擊瀏覽",
"importSelectedFile": "已選取:{{name}}",
"importOverwrite": "覆寫具有相同名稱和資料夾的現有片段",
"importStartBtn": "匯入",
"importSuccess": "匯入完成:已新增 {{snippets}} 個片段、{{updated}} 個已更新、{{skipped}} 個略過、{{folders}} 個資料夾",
"importFailed": "匯入片段失敗",
"importInvalidFile": "無效的檔案:需要包含片段或資料夾陣列的 JSON 物件",
"targetHostsLabel": "目標主機",
"targetHostsHint": "指定主機以直接執行此片段,無需開啟終端機。",
"noHostsAvailable": "未設定主機",
"clearTargetHosts": "清除全部",
"hasTargetHosts": "有目標主機",
"runOnTargets": "在目標上執行",
"directRunSuccess": "已在 {{count}} 台主機上執行「{{name}}」",
"directRunPartialFail": "「{{name}}」在一或多台主機上執行失敗",
"executionResultTitle": "執行結果:{{name}}",
"executionResultDescription": "在每個目標主機上執行片段所產生的輸出。",
"executionSuccess": "成功",
"executionFailed": "失敗",
"expandAll": "展開所有資料夾",
"collapseAll": "折疊所有資料夾",
"settings": "程式碼片段設定",
"variablesHint": "變數: $HOST、 $USER、 $PORT、 $NAME、${INPUT_1:Label}",
"variablesDialogTitle": "運行“{{name}}",
"variablesDialogDescription": "在運行此程式碼片段之前,請填寫所需的值。",
"variablesPreviewLabel": "預覽",
"variablesConfirmButton": "跑步"
},
"keybindings": {
"title": "鍵盤快速鍵",
"description": "自訂終端複製、貼上和控制快捷鍵,或綁定按鍵以傳送文字或執行程式碼片段。",
@@ -2000,26 +1882,11 @@
"pressKeysPlaceholder": "點擊並按下按鍵",
"recording": "按下按鍵…",
"comboRequiredError": "先按下組合鍵",
"textRequiredError": "請輸入要發送的文字",
"controlCodeRequiredError": "請輸入一個字母作為控制碼",
"snippetRequiredError": "選擇一段文字",
"conflictWarning": "此組合已綁定至 {{combo}}。保存後兩者都會生效。",
"actionLabel": "行動",
"actionCopy": "複製選擇",
"actionPaste": "貼上",
"actionSendControlCode": "發送 Ctrl+字母訊號",
"actionSendText": "傳簡訊",
"actionRunSnippet": "運行現有程式碼片段",
"actionNextTab": "切換到下一個標籤頁",
"actionPreviousTab": "切換到上一個標籤頁",
"actionOpenCommandPalette": "打開快速搜尋",
"controlCodeLabel": "信",
"textLabel": "發送的文字",
"appendEnterLabel": "發送後按回車鍵",
"snippetLabel": "片段",
"selectSnippetPlaceholder": "選擇一段文字",
"orphanedSnippetWarning": "未找到程式碼片段",
"clipboardPermissionNote": "某些瀏覽器在首次使用時可能會提示授予剪貼簿權限。"
"actionOpenCommandPalette": "打開快速搜尋"
},
"userProfile": {
"donateTitle": "讓 Termix 持續營運",
@@ -2240,100 +2107,5 @@
"ntfyTopicRequired": "主題為必填",
"ntfyToken": "存取權杖(選填)",
"channelSaveFailed": "無法儲存頻道"
},
"macros": {
"title": "宏",
"description": "在終端機中執行已儲存的一系列命令,並對伺服器傳回的內容做出反應。",
"target": "目標",
"noTerminal": "沒有終端機打開",
"empty": "暫無宏",
"emptyHint": "建立一個巨集來自動化終端流程,例如登入、重新啟動服務或回答提示。",
"create": "新宏",
"back": "回傳宏",
"name": "姓名",
"namePlaceholder": "重啟Web伺服器",
"nameRequired": "儲存之前,請先為巨集命名。",
"descriptionLabel": "描述",
"descriptionPlaceholder": "此巨集的功能(選用)",
"unsaved": "未儲存的更改",
"save": "節省",
"saved": "巨集已儲存",
"saveFailed": "無法保存巨集",
"delete": "刪除",
"deleteTitle": "刪除這個巨集?",
"deleteBody": "“{{name}}”將永久刪除。此操作無法撤銷。",
"deleted": "巨集已刪除",
"cancel": "取消",
"run": "跑步",
"stop": "停止",
"running": "跑步...",
"completed": "巨集已完成",
"failed": "巨集失敗",
"loadFailed": "無法載入巨集。",
"needTerminal": "首先打開並連接終端。",
"noSteps": "此巨集目前尚無步驟。請在下方新增步驟。",
"addStep": "新增步驟",
"stepNumber": "步驟 {{number}}",
"removeStep": "移除步驟",
"confirmRunTitle": "運行這個巨集?",
"confirmRunBody": "\"{{name}}\" 將向 {{host}} 傳送指令。",
"confirmRunAction": "運行巨集",
"stepTypes": {
"send": {
"label": "發送命令",
"help": "在終端機中輸入文本,就像你手動輸入一樣。"
},
"wait": {
"label": "等待輸出",
"help": "暫停,直到伺服器列印您指定的文本,例如密碼提示。"
},
"delay": {
"label": "暫停",
"help": "等待一段固定時間後再執行下一步操作。"
},
"if": {
"label": "如果輸出包含",
"help": "檢查伺服器目前為止列印的所有內容,然後執行兩個分支中的一個。"
},
"repeat": {
"label": "重複",
"help": "將其中的步驟執行設定的次數。"
}
},
"send": {
"text": "命令或文字",
"textPlaceholder": "systemctl restart nginx",
"pressEnter": "發送後按回車鍵",
"pressEnterHelp": "關閉此功能即可輸入文字而不提交。"
},
"wait": {
"pattern": "等待輸出包含",
"patternPlaceholder": "密碼:",
"timeout": "放棄",
"onTimeout": "如果它從未出現",
"stop": "停止宏",
"continue": "無論如何,繼續前進。"
},
"delay": {
"duration": "等待"
},
"ifStep": {
"pattern": "如果輸出包含",
"patternPlaceholder": "活躍:正在運行",
"then": "然後這樣做",
"else": "否則就這樣做",
"emptyBranch": "沒有步驟"
},
"repeat": {
"count": "次數",
"times": "時代"
},
"match": {
"useRegex": "使用正規表示式進行匹配",
"useRegexHelp": "請刪除此行以完全按照原文填寫。",
"invalidRegex": "這不是有效的正規表示式,它將被匹配為純文字。"
},
"seconds": "秒",
"secondsShort": "{{count}}s"
}
}
+67 -1
View File
@@ -20,7 +20,16 @@ import {
registerHostBadge,
registerHostContextMenuItem,
} from "@/sidebar/host-contributions";
import { registerPaletteEntry } from "@/shell/palette-registry";
import {
registerPaletteEntry,
registerPaletteGroup,
type PaletteItemDef,
} from "@/shell/palette-registry";
import {
registerKeybindingAction,
registerKeybindingDefault,
type KeybindingEditorProps,
} from "@/shell/keybinding-registry";
import { registerHostProtocol } from "@/sidebar/host-protocols";
import {
registerDashboardCard,
@@ -278,6 +287,63 @@ export function createPluginApp(
);
},
registerPaletteGroup(group) {
return track(
registerPaletteGroup({
id: group.id,
pluginId,
titleKey: key(group.titleKey),
order: group.order,
showWhenEmpty: group.showWhenEmpty,
load: group.load as unknown as () => Promise<PaletteItemDef[]>,
}),
);
},
registerKeybindingAction(action) {
const declared = (manifest.contributes?.keybindingActions ?? []).some(
(entry) => entry.id === action.id,
);
if (!declared) {
throw new Error(
`${pluginId}: keybinding action "${action.id}" is not declared in manifest contributes.keybindingActions`,
);
}
const validate = action.validate;
return track(
registerKeybindingAction({
id: action.id,
pluginId,
labelKey: key(action.titleKey),
scope: action.scope ?? "session",
editor: action.editor
? (scoped(action.editor) as ComponentType<KeybindingEditorProps>)
: undefined,
summary: action.summary
? (scoped(action.summary) as ComponentType<KeybindingEditorProps>)
: undefined,
validate: validate
? (value) => {
const problem = validate(value);
return problem ? key(problem) : null;
}
: undefined,
run: action.run,
}),
);
},
registerKeybindingDefault(binding) {
return track(
registerKeybindingDefault({
id: binding.id,
pluginId,
combo: binding.combo,
descriptionKey: key(binding.descriptionKey),
}),
);
},
registerDashboardCard(card) {
requireDeclared("card", card.id, "dashboard card");
return track(
+3
View File
@@ -37,6 +37,7 @@ import {
parseCustomKeybindings,
patchOpenTab,
} from "@/api/open-tabs-api";
import { runKeybindingAction } from "@/shell/keybinding-registry";
import { usePluginScope } from "./scope";
import { knownPluginIds, usePluginStore } from "./plugin-store";
import { useUiPreferencesContext } from "@/contexts/UiPreferencesContext";
@@ -361,6 +362,8 @@ export const pluginHostBridge: PluginHostBridge = {
patchOpenTab: (instanceId, updates) => patchOpenTab(instanceId, updates),
getCustomKeybindings: async () =>
parseCustomKeybindings((await getUserPreferences()).customKeybindings),
runKeybindingAction: (action, context) =>
runKeybindingAction(action, context),
getClientPreference: (name) => getCookie(name),
setClientPreference: (name, value) => {
void setCookie(name, value, 365);
+1 -1
View File
@@ -46,7 +46,6 @@ export {
export type * from "@/components/card-grid/types";
export { ConnectionScreen } from "@/components/connection/ConnectionScreen";
export * from "@/components/connection/connection-status";
export { SnippetVariablesDialog } from "@/components/SnippetVariablesDialog";
export {
FullScreenAppWrapper,
type FullScreenAppPhase,
@@ -101,6 +100,7 @@ export { useAdaptivePolling } from "@/hooks/use-adaptive-polling";
export { WidgetTitle } from "@/lib/widget-title";
export { runVisibleInterval } from "@/lib/visible-interval";
export { useIsMobile } from "@/hooks/use-mobile";
export { EmptyState } from "@/components/empty-state";
export { PassphraseDialog } from "@/ssh/dialogs/PassphraseDialog";
export { HostKeyVerificationDialog } from "@/ssh/dialogs/HostKeyVerificationDialog";
+17
View File
@@ -45,6 +45,11 @@ import {
listSettingsComponents,
} from "@/settings/settings-components";
import { getSlotContributions, listActions } from "@/shell/action-registry";
import { listPaletteGroups, loadPaletteGroup } from "@/shell/palette-registry";
import {
listKeybindingActions,
listKeybindingDefaults,
} from "@/shell/keybinding-registry";
import {
getLoginMethodUI,
getSecondFactorUI,
@@ -240,6 +245,18 @@ export async function renderPlugin(
loginMethods: () => mine(listLoginMethodUIs()).map((method) => method.id),
secondFactors: () =>
mine(listSecondFactorUIs()).map((factor) => factor.id),
paletteGroups: () => mine(listPaletteGroups()).map((group) => group.id),
keybindingActions: () =>
mine(listKeybindingActions()).map((action) => action.id),
keybindingDefaults: () =>
mine(listKeybindingDefaults()).map((binding) => binding.id),
},
async loadPaletteGroup(id) {
const group =
listPaletteGroups().find((entry) => entry.id === id) ??
missing("a palette group", id);
return (await loadPaletteGroup(group)) as never;
},
renderTab(type, props = {}) {
+87 -74
View File
@@ -23,22 +23,25 @@ import {
Clock,
Folder,
Pencil,
Play,
Clipboard,
} from "lucide-react";
import { getRecentActivity, type RecentActivityItem } from "@/main-axios";
import { listSnippets } from "@/lib/snippet-provider";
import type { Host, TabType, Tab, Snippet } from "@/types/ui-types";
import type { Host, TabType, Tab } from "@/types/ui-types";
import { canEditHost } from "@/sidebar/host-permissions";
import { RAIL_UTILITY_ITEMS, useRailItems } from "@/sidebar/rail-items";
import { useSnippetRunner } from "@/hooks/use-snippet-runner.tsx";
import {
defaultConnectAction,
hostActionsFor,
runHostAction,
useHostActions,
} from "@/sidebar/host-contributions";
import { paletteEntriesFor, usePaletteEntries } from "./palette-registry";
import {
filterPaletteItems,
loadPaletteGroup,
paletteEntriesFor,
usePaletteEntries,
usePaletteGroups,
type PaletteItemDef,
} from "./palette-registry";
import { activityTarget } from "@/lib/activity-types";
import { shell } from "@/plugin-host/shell-bridge";
@@ -71,9 +74,11 @@ export function CommandPalette({
const [recentActivity, setRecentActivity] = useState<RecentActivityItem[]>(
[],
);
const [snippets, setSnippets] = useState<Snippet[]>([]);
const paletteGroups = usePaletteGroups();
const [groupItems, setGroupItems] = useState<
Record<string, PaletteItemDef[]>
>({});
const [selectedValue, setSelectedValue] = useState("");
const { runSnippet, dialog: runSnippetDialog } = useSnippetRunner();
useEffect(() => {
if (isOpen) {
@@ -82,12 +87,24 @@ export function CommandPalette({
getRecentActivity(5)
.then(setRecentActivity)
.catch(() => {});
listSnippets()
.then((data) => setSnippets(data as unknown as Snippet[]))
.catch(() => {});
}
}, [isOpen]);
useEffect(() => {
if (!isOpen) return;
let cancelled = false;
for (const group of paletteGroups) {
void loadPaletteGroup(group).then((items) => {
if (!cancelled) {
setGroupItems((prev) => ({ ...prev, [group.id]: items }));
}
});
}
return () => {
cancelled = true;
};
}, [isOpen, paletteGroups]);
useEffect(() => {
if (!isOpen) return;
@@ -130,16 +147,6 @@ export function CommandPalette({
groupedHosts.push({ folder, hosts: fhosts });
}
const filteredSnippets = search.trim()
? snippets.filter((s) => {
const query = search.toLowerCase();
return (
s.name.toLowerCase().includes(query) ||
s.content.toLowerCase().includes(query)
);
})
: [];
useEffect(() => {
if (!isOpen) return;
const firstHost = filteredHosts[0];
@@ -159,10 +166,7 @@ export function CommandPalette({
};
const showHostResultsFirst = search.trim().length > 0;
// Closing the palette unmounts this component (AppShell only renders it
// while commandPaletteOpen is true), so keep rendering just the variables
// dialog after close if a snippet run is still pending its inputs.
if (!isOpen) return runSnippetDialog;
if (!isOpen) return null;
return (
<div
@@ -327,53 +331,64 @@ export function CommandPalette({
</>
)}
{filteredSnippets.length > 0 && (
<>
<CommandSeparator className="my-2" />
<CommandGroup
heading={t("commandPalette.snippets")}
className="px-2"
>
{filteredSnippets.map((snippet) => (
<CommandItem
key={snippet.id}
value={`snippet-${snippet.id}`}
onSelect={() => {
if (!activeTargetTab) return;
handleAction(() =>
runSnippet(snippet, [activeTargetTab]),
);
}}
className={cn(
"group flex items-center gap-3 px-3 py-2.5 rounded-none hover:bg-accent-brand/10 cursor-pointer",
!activeTargetTab && "pointer-events-none opacity-50",
)}
>
<div className="size-8 rounded-none bg-muted flex items-center justify-center group-hover:bg-accent-brand/20 transition-colors shrink-0">
{snippet.isNote ? (
<Clipboard className="size-4 text-accent-brand" />
) : (
<Play className="size-4 text-accent-brand" />
)}
</div>
<div className="flex flex-col flex-1 min-w-0">
<span className="text-sm font-semibold truncate">
{snippet.name}
</span>
<span className="text-xs text-muted-foreground truncate font-mono">
{snippet.content}
</span>
</div>
<span className="text-[10px] text-muted-foreground/60 shrink-0">
{activeTargetTab
? t("commandPalette.runSnippetDesc")
: t("newUi.sidebar.snippets.noTerminalTabsOpen")}
</span>
</CommandItem>
))}
</CommandGroup>
</>
)}
{paletteGroups.map((group) => {
const items = filterPaletteItems(
groupItems[group.id] ?? [],
search,
group.showWhenEmpty,
);
if (items.length === 0) return null;
return (
<React.Fragment key={`group-${group.id}`}>
<CommandSeparator className="my-2" />
<CommandGroup heading={t(group.titleKey)} className="px-2">
{items.map((item) => {
const Icon = item.icon;
const blocked = !!item.needsTarget && !activeTargetTab;
return (
<CommandItem
key={`${group.id}-${item.id}`}
value={`group-${group.id}-${item.id}`}
onSelect={() => {
if (blocked) return;
handleAction(() =>
item.run({ targetTab: activeTargetTab, shell }),
);
}}
className={cn(
"group flex items-center gap-3 px-3 py-2.5 rounded-none hover:bg-accent-brand/10 cursor-pointer",
blocked && "pointer-events-none opacity-50",
)}
>
<div className="size-8 rounded-none bg-muted flex items-center justify-center group-hover:bg-accent-brand/20 transition-colors shrink-0">
{Icon && (
<Icon className="size-4 text-accent-brand" />
)}
</div>
<div className="flex flex-col flex-1 min-w-0">
<span className="text-sm font-semibold truncate">
{item.title}
</span>
{item.description && (
<span className="text-xs text-muted-foreground truncate font-mono">
{item.description}
</span>
)}
</div>
{(blocked || item.hint) && (
<span className="text-[10px] text-muted-foreground/60 shrink-0">
{blocked
? t("commandPalette.noTargetTab")
: item.hint}
</span>
)}
</CommandItem>
);
})}
</CommandGroup>
</React.Fragment>
);
})}
{paletteEntriesFor(paletteEntries, "global").length > 0 && (
<>
@@ -661,8 +676,6 @@ export function CommandPalette({
</div>
</Command>
</div>
{runSnippetDialog}
</div>
);
}
+150
View File
@@ -0,0 +1,150 @@
import type { ComponentType } from "react";
import type { KeybindingAction, KeyCombo } from "@/types/keybindings";
import { byOrderThenId, createRegistry } from "@/lib/registry";
export interface KeybindingEditorProps {
action: KeybindingAction;
onChange: (action: KeybindingAction) => void;
}
export interface KeybindingRunContext {
sessionId?: string;
host?: {
ip?: string;
username?: string;
port?: number | string;
name?: string;
} | null;
send?: (data: string) => void;
}
/**
* An action a key can be bound to. The shell registers its own; plugins add
* theirs with app.registerKeybindingAction. `id` is what a saved binding
* stores as action.type.
*/
export interface KeybindingActionDef {
id: string;
pluginId?: string;
/** A full translation key. */
labelKey: string;
scope: "session" | "global";
editor?: ComponentType<KeybindingEditorProps>;
summary?: ComponentType<KeybindingEditorProps>;
/** A full translation key when the action cannot be saved yet. */
validate?: (action: KeybindingAction) => string | null;
run?: (action: KeybindingAction, context: KeybindingRunContext) => void;
order?: number;
}
/** A built-in key the user can rebind. */
export interface KeybindingDefaultDef {
id: string;
pluginId?: string;
combo: KeyCombo;
/** A full translation key. */
descriptionKey: string;
order?: number;
}
/** Fired for the shell's own actions; AppShell runs them. */
export const GLOBAL_KEYBINDING_EVENT = "termix:global-keybinding";
function shellAction(id: string, labelKey: string, order: number) {
return {
id,
labelKey,
scope: "global" as const,
order,
run: (action: KeybindingAction) => {
window.dispatchEvent(
new CustomEvent(GLOBAL_KEYBINDING_EVENT, {
detail: { type: action.type },
}),
);
},
};
}
/** The actions the shell runs itself. */
const SHELL_ACTIONS: KeybindingActionDef[] = [
shellAction("nextTab", "newUi.sidebar.keybindings.actionNextTab", 1000),
shellAction(
"previousTab",
"newUi.sidebar.keybindings.actionPreviousTab",
1001,
),
shellAction(
"openCommandPalette",
"newUi.sidebar.keybindings.actionOpenCommandPalette",
1002,
),
shellAction(
"reconnectSession",
"newUi.sidebar.keybindings.actionReconnectSession",
1003,
),
];
export const SHELL_KEYBINDING_ACTION_IDS = SHELL_ACTIONS.map(
(action) => action.id,
);
const actions = createRegistry<KeybindingActionDef>(byOrderThenId);
const defaults = createRegistry<KeybindingDefaultDef>(byOrderThenId);
function install(): void {
for (const action of SHELL_ACTIONS) {
if (!actions.get(action.id)) actions.register(action);
}
}
install();
/** Adds a plugin's action. A shell action's id cannot be taken over. */
export function registerKeybindingAction(
action: KeybindingActionDef,
): () => void {
if (SHELL_KEYBINDING_ACTION_IDS.includes(action.id)) {
throw new Error(`Keybinding action "${action.id}" belongs to the shell`);
}
const existing = actions.get(action.id);
if (existing && existing.pluginId !== action.pluginId) {
throw new Error(
`Keybinding action "${action.id}" is already registered by ${existing.pluginId}`,
);
}
return actions.register(action);
}
export const registerKeybindingDefault = defaults.register;
export const listKeybindingActions = actions.list;
export const useKeybindingActions = actions.useList;
export const getKeybindingAction = actions.get;
export const listKeybindingDefaults = defaults.list;
export const useKeybindingDefaults = defaults.useList;
/** Back to the shell's own actions and no defaults, for tests. */
export function resetKeybindingRegistry(): void {
actions.reset();
defaults.reset();
install();
}
/**
* Runs a bound action through whoever registered it. False when nothing
* running handles its type (its plugin is off), so the caller can let the
* key through.
*/
export function runKeybindingAction(
action: KeybindingAction,
context: KeybindingRunContext = {},
): boolean {
const def = actions.get(action.type);
if (!def?.run) return false;
try {
def.run(action, context);
} catch (error) {
console.error(`[keybindings] ${action.type} failed`, error);
}
return true;
}
+57 -1
View File
@@ -1,5 +1,5 @@
import type { ComponentType } from "react";
import type { Host } from "@/types/ui-types";
import type { Host, Tab } from "@/types/ui-types";
import { byOrderThenId, createRegistry } from "@/lib/registry";
import type { TabShellCallbacks } from "./tab-registry";
@@ -38,3 +38,59 @@ export function paletteEntriesFor(
}
});
}
/** One row of a palette group. */
export interface PaletteItemDef {
id: string;
title: string;
description?: string;
icon?: ComponentType<{ className?: string }>;
keywords?: string[];
needsTarget?: boolean;
hint?: string;
run: (context: { targetTab?: Tab; shell: TabShellCallbacks }) => void;
}
/** A searchable group of items a plugin fills, e.g. its saved commands. */
export interface PaletteGroupDef {
id: string;
pluginId?: string;
titleKey: string;
order?: number;
load: () => PaletteItemDef[] | Promise<PaletteItemDef[]>;
showWhenEmpty?: boolean;
}
const groups = createRegistry<PaletteGroupDef>(byOrderThenId);
export const registerPaletteGroup = groups.register;
export const usePaletteGroups = groups.useList;
export const listPaletteGroups = groups.list;
export const resetPaletteGroups = groups.reset;
/** A group's items, or none when its loader throws. */
export async function loadPaletteGroup(
group: PaletteGroupDef,
): Promise<PaletteItemDef[]> {
try {
const items = await group.load();
return Array.isArray(items) ? items : [];
} catch {
return [];
}
}
/** The items the query matches, by title, description or keyword. */
export function filterPaletteItems(
items: PaletteItemDef[],
query: string,
showWhenEmpty = false,
): PaletteItemDef[] {
const needle = query.trim().toLowerCase();
if (!needle) return showWhenEmpty ? items : [];
return items.filter((item) =>
[item.title, item.description ?? "", ...(item.keywords ?? [])].some(
(text) => text.toLowerCase().includes(needle),
),
);
}
+1 -15
View File
@@ -18,16 +18,6 @@ import { listPanes, mapTabIds } from "./split/split-tree";
* snapshots, through app.tabs.getLayout and applyLayout.
*/
/**
* Core tab types worth saving in a layout. "dashboard" is left out on
* purpose: the shell always keeps one alive as the fallback tab, so it is
* not a meaningful part of an arrangement.
*/
const CORE_CAPTURABLE = new Set(["macros"]);
/** Core types reopened as singletons, with an optional preselected host. */
const CORE_SINGLETON = new Set(["macros"]);
/** Core tab types that are never part of a saved arrangement. */
const CORE_UNSAVED = new Set([
"dashboard",
@@ -43,20 +33,16 @@ const CORE_UNSAVED = new Set([
* it, and a layout keeps it so nothing is lost when the plugin comes back.
*/
function isUnregisteredPluginTabType(type: string): boolean {
return (
!CORE_UNSAVED.has(type) && !CORE_CAPTURABLE.has(type) && !getTabType(type)
);
return !CORE_UNSAVED.has(type) && !getTabType(type);
}
export function isCapturableTabType(type: string): boolean {
if (CORE_CAPTURABLE.has(type)) return true;
if (isUnregisteredPluginTabType(type)) return true;
const def = getTabType(type);
return !!def && def.inLayouts !== false;
}
function opensAsSingleton(type: string): boolean {
if (CORE_SINGLETON.has(type)) return true;
const def = getTabType(type);
return !!def && (!!def.singleton || !!def.hostless);
}
+1 -1
View File
@@ -73,7 +73,7 @@ export interface TabTypeDef {
panelFrame?: boolean;
/** False keeps this tab out of saved layouts and workspaces. */
inLayouts?: boolean;
/** History, macros and SSH tools act on the last one of these focused. */
/** Panels that type into a terminal act on the last one of these focused. */
commandTarget?: boolean;
/** Paints its own background; its frame stays transparent. */
ownBackground?: boolean;
-32
View File
@@ -1,6 +1,5 @@
/* eslint-disable react-refresh/only-export-components */
import {
Braces,
LayoutDashboard,
LayoutPanelLeft,
Server,
@@ -28,10 +27,6 @@ const DashboardTab = lazy(() =>
default: m.DashboardTab,
})),
);
// Rail panels promoted to full tabs.
const MacrosPanel = lazy(() =>
import("@/sidebar/MacrosPanel").then((m) => ({ default: m.MacrosPanel })),
);
/** Download a likely next tab without starting a connection or mounting UI. */
export function preloadTabSurface(type: TabType): void {
@@ -100,9 +95,6 @@ export function tabIcon(type: TabType) {
return <User className="size-3.5" />;
case "admin-settings":
return <Settings className="size-3.5" />;
case "macros":
return <Braces className="size-3.5" />;
case "split-screen":
return <LayoutPanelLeft className="size-3.5" />;
default: {
@@ -163,23 +155,11 @@ function RegisteredTab({
);
}
/**
* Everything the promoted rail panels need from AppShell. Passed as one bag
* rather than more positional params, which renderTabContent already has too
* many of.
*/
export type PromotedPanelProps = {
terminalTabs?: Tab[];
targetTerminalTabId?: string;
storageMode?: "local" | "cloud";
};
export interface TabRenderContext {
shell: TabShellCallbacks;
isVisible?: boolean;
isFocusedPane?: boolean;
inSplit?: boolean;
panelProps?: PromotedPanelProps;
}
export function renderTabContent(tab: Tab, context: TabRenderContext) {
@@ -188,7 +168,6 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) {
isVisible = true,
isFocusedPane = true,
inSplit = false,
panelProps,
} = context;
switch (tab.type) {
@@ -204,17 +183,6 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) {
case "split-screen":
return null;
case "macros":
return withTabSuspense(
<PanelTabFrame>
<MacrosPanel
terminalTabs={panelProps?.terminalTabs ?? []}
activeTabId={panelProps?.targetTerminalTabId ?? ""}
storageMode={panelProps?.storageMode ?? "local"}
/>
</PanelTabFrame>,
);
case "host-manager":
case "user-profile":
case "admin-settings":
+1 -1
View File
@@ -26,7 +26,7 @@ const RESOURCE_TYPES = [
"user",
"host",
"credential",
"snippet",
"plugin",
"session",
"api_key",
"setting",
+1 -1
View File
@@ -245,7 +245,7 @@ export function AdminEditUserDialog({
</span>
{editUserRolesLoading ? (
<span className="text-xs text-muted-foreground">
{t("newUi.sidebar.snippets.loading")}
{t("common.loading")}
</span>
) : (
<>
+1 -12
View File
@@ -6,7 +6,6 @@ import { Input } from "@/components/input";
import { PasswordInput } from "@/components/password-input";
import { Globe, Pencil, Plus, Shield, Upload, X } from "lucide-react";
import { toast } from "sonner";
import { listSnippets } from "@/lib/snippet-provider";
import { SectionCard, SettingRow, FakeSwitch } from "@/components/section-card";
import {
createSSHHost,
@@ -28,7 +27,6 @@ import {
applyHostDraft,
buildHostEditorPayload,
createHostEditorForm,
mapSnippetResponse,
omitOwnerSshAuthFromSharedEdit,
type HostAuthType,
type HostProtocols,
@@ -116,7 +114,6 @@ export function HostEditor({
};
const [saving, setSaving] = useState(false);
const [snippets, setSnippets] = useState<{ id: number; name: string }[]>([]);
const [isOidcUser, setIsOidcUser] = useState(false);
const [showSecretSources, setShowSecretSources] = useState(false);
const [quickCredentialName, setQuickCredentialName] = useState("");
@@ -131,14 +128,6 @@ export function HostEditor({
.catch(() => {});
}, []);
useEffect(() => {
listSnippets(
adminTargetUserId ? { targetUserId: adminTargetUserId } : undefined,
)
.then((res) => setSnippets(mapSnippetResponse(res)))
.catch(() => {});
}, [adminTargetUserId]);
useEffect(() => {
if (host) {
setForm(createHostEditorForm(host));
@@ -1122,7 +1111,7 @@ export function HostEditor({
updateForm={updateForm}
host={host}
credentials={availableCredentials}
snippets={snippets}
adminTargetUserId={adminTargetUserId}
protocols={protocols as unknown as Record<string, boolean>}
/>
);
-27
View File
@@ -14,13 +14,6 @@ export type { HostProtocols };
export type HostAuthType = Host["authType"];
type SnippetListItem = {
id: number;
name?: string;
title?: string;
};
type SnippetResponse = SnippetListItem[] | { snippets?: SnippetListItem[] };
/**
* Whether the Connection Origin control is meaningful for a host.
*
@@ -33,18 +26,6 @@ export function connectionOriginAppliesTo(protocols: HostProtocols): boolean {
return Object.values(protocols).some(Boolean);
}
export function mapSnippetResponse(
res: unknown,
): { id: number; name: string }[] {
const snippetRes = res as SnippetResponse;
return (
Array.isArray(snippetRes) ? snippetRes : (snippetRes.snippets ?? [])
).map((s) => ({
id: s.id,
name: s.name ?? s.title ?? `Snippet ${s.id}`,
}));
}
/** Overlays a plugin's draft on a new host's form. */
export function applyHostDraft(
form: HostEditorForm,
@@ -110,7 +91,6 @@ export function createHostEditorForm(
"local" | "remote" | null,
localOnly: host?.localOnly ?? false,
forceKeyboardInteractive: host?.forceKeyboardInteractive ?? false,
startupSnippetId: host?.terminalConfig?.startupSnippetId ?? null,
sudoPassword: host?.hasSudoPassword
? "existing_sudo_password"
: (host?.sudoPassword ?? ""),
@@ -127,8 +107,6 @@ export function createHostEditorForm(
portKnockSequence:
host?.portKnockSequence ??
([] as { port: number; protocol: "tcp" | "udp"; delay: number }[]),
quickActions:
host?.quickActions ?? ([] as { name: string; snippetId: string }[]),
rdpCredentialId: host?.rdpCredentialId ?? "",
rdpUser: host?.rdpUser ?? "",
rdpPassword: host?.hasRdpPassword
@@ -320,10 +298,6 @@ export function buildHostEditorPayload(
// a number, and a string id does not compare equal on Postgres/MySQL.
jumpHosts: form.jumpHosts.map((j) => ({ hostId: Number(j.hostId) })),
portKnockSequence: form.portKnockSequence,
quickActions: form.quickActions.map((a) => ({
name: a.name,
snippetId: Number(a.snippetId),
})),
statusCheckEnabled: form.statusCheckEnabled,
statusCheckInterval: form.statusCheckInterval,
// Left out with SSH off, so the stored values stay as they are.
@@ -342,7 +316,6 @@ export function buildHostEditorPayload(
agentIdentity: usesAgent ? form.agentIdentity || null : null,
environmentVariables: form.environmentVariables,
},
terminalConfig: { startupSnippetId: form.startupSnippetId ?? null },
}
: {}),
};
-2
View File
@@ -29,7 +29,6 @@ const GROUPS: { key: FieldGroup; label: string }[] = [
{ key: "tags", label: "groupTags" },
{ key: "proxy", label: "groupProxy" },
{ key: "jumpHosts", label: "groupJumpHosts" },
{ key: "quickActions", label: "groupQuickActions" },
{ key: "featureFlags", label: "groupFeatureFlags" },
{ key: "advanced", label: "groupAdvanced" },
];
@@ -40,7 +39,6 @@ const DEFAULT_GROUPS: FieldGroup[] = [
"tags",
"proxy",
"jumpHosts",
"quickActions",
"featureFlags",
"advanced",
];
-5
View File
@@ -10,7 +10,6 @@ type RawSSHHost = SSHHostWithStatus & {
hasVncPassword?: boolean;
hasTelnetPassword?: boolean;
};
type HostQuickAction = Host["quickActions"][number];
type HostJumpHost = NonNullable<Host["jumpHosts"]>[number];
type RawCredential = {
isShared?: boolean;
@@ -107,10 +106,6 @@ export function sshHostToHost(h: SSHHostWithStatus): Host {
telnetUser: h.telnetUser,
telnetPassword: h.telnetPassword ?? "",
hasTelnetPassword: !!host.hasTelnetPassword || !!h.telnetPassword,
quickActions: (h.quickActions ?? []).map((a) => ({
name: a.name,
snippetId: String(a.snippetId),
})),
jumpHosts: (parseJson<HostJumpHost[]>(h.jumpHosts) ?? []).map((j) => ({
hostId: String(j.hostId ?? j.hostid ?? j),
})),
+1 -1
View File
@@ -30,7 +30,7 @@ export interface HostEditorSectionRenderProps {
) => void;
host?: unknown;
credentials?: unknown[];
snippets?: unknown[];
adminTargetUserId?: string;
protocols: Record<string, boolean>;
}
+144 -252
View File
@@ -6,12 +6,15 @@ import {
parseCustomKeybindings,
} from "@/api/open-tabs-api";
import { saveUserPreferences } from "@/main-axios";
import { listSnippets } from "@/lib/snippet-provider";
import { BUILT_IN_DEFAULTS } from "@/lib/default-keybindings";
import {
useKeybindingActions,
useKeybindingDefaults,
type KeybindingActionDef,
} from "@/shell/keybinding-registry";
import type {
CustomKeybinding,
KeybindingAction,
KeyCombo,
KeybindingActionType,
} from "@/types/keybindings";
import {
Dialog,
@@ -24,11 +27,6 @@ import { Button } from "@/components/button";
import { Input } from "@/components/input";
import { Pencil, Plus, RotateCcw, Trash2 } from "lucide-react";
interface SnippetOption {
id: number;
name: string;
}
function formatCombo(combo: KeyCombo): string {
const parts: string[] = [];
if (combo.ctrl) parts.push("Ctrl");
@@ -39,24 +37,29 @@ function formatCombo(combo: KeyCombo): string {
return parts.join(" + ");
}
const ACTION_LABEL_KEYS: Record<KeybindingActionType, string> = {
copy: "newUi.sidebar.keybindings.actionCopy",
paste: "newUi.sidebar.keybindings.actionPaste",
sendControlCode: "newUi.sidebar.keybindings.actionSendControlCode",
sendText: "newUi.sidebar.keybindings.actionSendText",
runSnippet: "newUi.sidebar.keybindings.actionRunSnippet",
nextTab: "newUi.sidebar.keybindings.actionNextTab",
previousTab: "newUi.sidebar.keybindings.actionPreviousTab",
openCommandPalette: "newUi.sidebar.keybindings.actionOpenCommandPalette",
reconnectSession: "newUi.sidebar.keybindings.actionReconnectSession",
};
function generateId(): string {
return typeof crypto !== "undefined" && "randomUUID" in crypto
? crypto.randomUUID()
: `kb-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
/** An action without the keys an editor cleared, so it saves clean. */
function withoutEmpty(action: KeybindingAction): KeybindingAction {
const out: KeybindingAction = { type: action.type };
for (const [key, value] of Object.entries(action)) {
if (key !== "type" && value !== undefined && value !== null) {
out[key] = value;
}
}
return out;
}
/**
* Appearance > Keybindings. The actions come from the keybinding registry:
* the shell's own and whatever the running plugins register, each with its
* own parameter editor. A saved binding whose plugin is off stays in the
* list and keeps working once the plugin is back.
*/
export function KeybindingsDialog({
open,
onOpenChange,
@@ -65,9 +68,10 @@ export function KeybindingsDialog({
onOpenChange: (v: boolean) => void;
}) {
const { t } = useTranslation();
const actions = useKeybindingActions();
const defaults = useKeybindingDefaults();
const [bindings, setBindings] = useState<CustomKeybinding[]>([]);
const [loading, setLoading] = useState(false);
const [snippets, setSnippets] = useState<SnippetOption[]>([]);
const [formOpen, setFormOpen] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [overridesDefaultId, setOverridesDefaultId] = useState<
@@ -76,32 +80,22 @@ export function KeybindingsDialog({
const [recordedCombo, setRecordedCombo] = useState<KeyCombo | null>(null);
const [recording, setRecording] = useState(false);
const [actionType, setActionType] =
useState<KeybindingActionType>("sendText");
const [text, setText] = useState("");
const [appendEnter, setAppendEnter] = useState(false);
const [controlCode, setControlCode] = useState("");
const [snippetId, setSnippetId] = useState("");
const [draft, setDraft] = useState<KeybindingAction>({ type: "" });
const actionById = useMemo(
() => new Map(actions.map((action) => [action.id, action])),
[actions],
);
const draftDef: KeybindingActionDef | undefined = actionById.get(draft.type);
useEffect(() => {
if (!open) return;
setLoading(true);
Promise.all([
getUserPreferences().catch(() => null),
listSnippets().catch(() => []),
])
.then(([prefs, snippetList]) => {
setBindings(
prefs ? parseCustomKeybindings(prefs.customKeybindings) : [],
);
const list = Array.isArray(snippetList) ? snippetList : [];
setSnippets(
list.map((s: { id: number; name: string }) => ({
id: s.id,
name: s.name,
})),
);
})
getUserPreferences()
.then((prefs) =>
setBindings(parseCustomKeybindings(prefs.customKeybindings)),
)
.catch(() => setBindings([]))
.finally(() => setLoading(false));
}, [open]);
@@ -125,16 +119,19 @@ export function KeybindingsDialog({
return null;
}, [recordedCombo, bindings, editingId, t]);
function actionLabel(type: string): string {
const def = actionById.get(type);
return def
? t(def.labelKey)
: t("newUi.sidebar.keybindings.unavailableAction", { type });
}
function resetForm() {
setEditingId(null);
setOverridesDefaultId(undefined);
setRecordedCombo(null);
setRecording(false);
setActionType("sendText");
setText("");
setAppendEnter(false);
setControlCode("");
setSnippetId("");
setDraft({ type: actions[0]?.id ?? "" });
}
function openAddForm() {
@@ -147,11 +144,7 @@ export function KeybindingsDialog({
setOverridesDefaultId(kb.overridesDefaultId);
setRecordedCombo(kb.combo);
setRecording(false);
setActionType(kb.action.type);
setText(kb.action.text ?? "");
setAppendEnter(kb.action.appendEnter ?? false);
setControlCode(kb.action.controlCode ?? "");
setSnippetId(kb.action.snippetId ?? "");
setDraft({ ...kb.action });
setFormOpen(true);
}
@@ -212,16 +205,14 @@ export function KeybindingsDialog({
toast.error(t("newUi.sidebar.keybindings.comboRequiredError"));
return;
}
if (actionType === "sendText" && !text.trim()) {
toast.error(t("newUi.sidebar.keybindings.textRequiredError"));
if (!draftDef) {
toast.error(t("newUi.sidebar.keybindings.actionRequiredError"));
return;
}
if (actionType === "sendControlCode" && !/^[a-zA-Z]$/.test(controlCode)) {
toast.error(t("newUi.sidebar.keybindings.controlCodeRequiredError"));
return;
}
if (actionType === "runSnippet" && !snippetId) {
toast.error(t("newUi.sidebar.keybindings.snippetRequiredError"));
const action = withoutEmpty(draft);
const problem = draftDef.validate?.(action);
if (problem) {
toast.error(t(problem));
return;
}
@@ -233,21 +224,7 @@ export function KeybindingsDialog({
const newBinding: CustomKeybinding = {
id: editingId ?? generateId(),
combo: recordedCombo,
action: {
type: actionType,
text: actionType === "sendText" ? text : undefined,
controlCode:
actionType === "sendControlCode"
? controlCode.toLowerCase()
: undefined,
snippetId: actionType === "runSnippet" ? snippetId : undefined,
appendEnter:
actionType === "runSnippet"
? true
: actionType === "sendText"
? appendEnter
: undefined,
},
action,
enabled: true,
overridesDefaultId,
createdAt: existing?.createdAt ?? now,
@@ -264,6 +241,7 @@ export function KeybindingsDialog({
}
const customOnlyBindings = bindings.filter((kb) => !kb.overridesDefaultId);
const Editor = draftDef?.editor;
return (
<>
@@ -279,78 +257,80 @@ export function KeybindingsDialog({
</DialogHeader>
<div className="flex flex-col gap-4 mt-1 max-h-[60vh] overflow-y-auto">
<div className="flex flex-col gap-1.5">
<span className="text-xs font-semibold text-muted-foreground">
{t("newUi.sidebar.keybindings.defaultsHeading")}
</span>
{BUILT_IN_DEFAULTS.map((def) => {
const override = bindings.find(
(kb) => kb.overridesDefaultId === def.id,
);
return (
<div
key={def.id}
className="flex items-center justify-between gap-2 border border-border bg-background px-2.5 py-2"
>
<div className="flex flex-col min-w-0">
<span className="text-xs font-mono">
{override
? formatCombo(override.combo)
: formatCombo(def.combo)}
</span>
<span className="text-xs text-muted-foreground truncate">
{t(def.descriptionKey)}
</span>
{defaults.length > 0 && (
<div className="flex flex-col gap-1.5">
<span className="text-xs font-semibold text-muted-foreground">
{t("newUi.sidebar.keybindings.defaultsHeading")}
</span>
{defaults.map((def) => {
const override = bindings.find(
(kb) => kb.overridesDefaultId === def.id,
);
return (
<div
key={def.id}
className="flex items-center justify-between gap-2 border border-border bg-background px-2.5 py-2"
>
<div className="flex flex-col min-w-0">
<span className="text-xs font-mono">
{override
? formatCombo(override.combo)
: formatCombo(def.combo)}
</span>
<span className="text-xs text-muted-foreground truncate">
{t(def.descriptionKey)}
</span>
</div>
<div className="flex items-center gap-1 shrink-0">
{override ? (
<>
<span className="text-[10px] font-bold text-accent-brand border border-accent-brand/40 px-1">
{t("newUi.sidebar.keybindings.customizedBadge")}
</span>
<Button
variant="ghost"
size="icon"
className="size-7"
onClick={() => openEditForm(override)}
>
<Pencil className="size-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="size-7 text-muted-foreground hover:text-destructive"
onClick={() => handleDelete(override.id)}
title={t(
"newUi.sidebar.keybindings.resetToDefault",
)}
>
<RotateCcw className="size-3.5" />
</Button>
</>
) : (
<>
<span className="text-[10px] text-muted-foreground border border-border px-1">
{t("newUi.sidebar.keybindings.defaultBadge")}
</span>
<Button
variant="ghost"
size="icon"
className="size-7"
onClick={() =>
openOverrideDefaultForm(def.id, def.combo)
}
title={t("newUi.sidebar.keybindings.customize")}
>
<Pencil className="size-3.5" />
</Button>
</>
)}
</div>
</div>
<div className="flex items-center gap-1 shrink-0">
{override ? (
<>
<span className="text-[10px] font-bold text-accent-brand border border-accent-brand/40 px-1">
{t("newUi.sidebar.keybindings.customizedBadge")}
</span>
<Button
variant="ghost"
size="icon"
className="size-7"
onClick={() => openEditForm(override)}
>
<Pencil className="size-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="size-7 text-muted-foreground hover:text-destructive"
onClick={() => handleDelete(override.id)}
title={t(
"newUi.sidebar.keybindings.resetToDefault",
)}
>
<RotateCcw className="size-3.5" />
</Button>
</>
) : (
<>
<span className="text-[10px] text-muted-foreground border border-border px-1">
{t("newUi.sidebar.keybindings.defaultBadge")}
</span>
<Button
variant="ghost"
size="icon"
className="size-7"
onClick={() =>
openOverrideDefaultForm(def.id, def.combo)
}
title={t("newUi.sidebar.keybindings.customize")}
>
<Pencil className="size-3.5" />
</Button>
</>
)}
</div>
</div>
);
})}
</div>
);
})}
</div>
)}
<div className="flex flex-col gap-1.5">
<div className="flex items-center justify-between">
@@ -372,9 +352,7 @@ export function KeybindingsDialog({
</span>
) : (
customOnlyBindings.map((kb) => {
const orphaned =
kb.action.type === "runSnippet" &&
!snippets.some((s) => String(s.id) === kb.action.snippetId);
const Summary = actionById.get(kb.action.type)?.summary;
return (
<div
key={kb.id}
@@ -385,16 +363,12 @@ export function KeybindingsDialog({
{formatCombo(kb.combo)}
</span>
<span className="text-xs text-muted-foreground truncate">
{t(ACTION_LABEL_KEYS[kb.action.type])}
{orphaned && (
<span className="text-destructive">
{actionLabel(kb.action.type)}
{Summary && (
<>
{" "}
(
{t(
"newUi.sidebar.keybindings.orphanedSnippetWarning",
)}
)
</span>
<Summary action={kb.action} onChange={() => {}} />
</>
)}
</span>
</div>
@@ -482,104 +456,22 @@ export function KeybindingsDialog({
{t("newUi.sidebar.keybindings.actionLabel")}
</label>
<select
value={actionType}
onChange={(e) =>
setActionType(e.target.value as KeybindingActionType)
}
value={draft.type}
onChange={(e) => setDraft({ type: e.target.value })}
className="h-8 border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
>
<option value="copy">
{t("newUi.sidebar.keybindings.actionCopy")}
</option>
<option value="paste">
{t("newUi.sidebar.keybindings.actionPaste")}
</option>
<option value="sendControlCode">
{t("newUi.sidebar.keybindings.actionSendControlCode")}
</option>
<option value="sendText">
{t("newUi.sidebar.keybindings.actionSendText")}
</option>
<option value="runSnippet">
{t("newUi.sidebar.keybindings.actionRunSnippet")}
</option>
<option value="nextTab">
{t("newUi.sidebar.keybindings.actionNextTab")}
</option>
<option value="previousTab">
{t("newUi.sidebar.keybindings.actionPreviousTab")}
</option>
<option value="openCommandPalette">
{t("newUi.sidebar.keybindings.actionOpenCommandPalette")}
</option>
<option value="reconnectSession">
{t("newUi.sidebar.keybindings.actionReconnectSession")}
</option>
{!draftDef && draft.type && (
<option value={draft.type}>{actionLabel(draft.type)}</option>
)}
{actions.map((action) => (
<option key={action.id} value={action.id}>
{t(action.labelKey)}
</option>
))}
</select>
{actionType === "paste" && (
<span className="text-xs text-muted-foreground">
{t("newUi.sidebar.keybindings.clipboardPermissionNote")}
</span>
)}
</div>
{actionType === "sendControlCode" && (
<div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold">
{t("newUi.sidebar.keybindings.controlCodeLabel")}
</label>
<Input
value={controlCode}
maxLength={1}
onChange={(e) => setControlCode(e.target.value)}
placeholder="w"
className="w-16 font-mono"
/>
</div>
)}
{actionType === "sendText" && (
<div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold">
{t("newUi.sidebar.keybindings.textLabel")}
</label>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
className="w-full h-24 px-3 py-2 text-xs bg-background border border-border text-foreground placeholder:text-muted-foreground resize-none outline-none focus:ring-1 focus:ring-ring font-mono"
/>
<label className="flex items-center gap-2 text-xs text-muted-foreground">
<input
type="checkbox"
checked={appendEnter}
onChange={(e) => setAppendEnter(e.target.checked)}
/>
{t("newUi.sidebar.keybindings.appendEnterLabel")}
</label>
</div>
)}
{actionType === "runSnippet" && (
<div className="flex flex-col gap-1.5">
<label className="text-xs font-semibold">
{t("newUi.sidebar.keybindings.snippetLabel")}
</label>
<select
value={snippetId}
onChange={(e) => setSnippetId(e.target.value)}
className="h-8 border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
>
<option value="">
{t("newUi.sidebar.keybindings.selectSnippetPlaceholder")}
</option>
{snippets.map((s) => (
<option key={s.id} value={String(s.id)}>
{s.name}
</option>
))}
</select>
</div>
)}
{Editor && <Editor action={draft} onChange={setDraft} />}
</div>
<div className="flex items-center justify-end gap-2 mt-2">
<Button variant="ghost" onClick={() => setFormOpen(false)}>
-856
View File
@@ -1,856 +0,0 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ArrowLeft,
Braces,
ChevronDown,
Clock,
CornerDownRight,
GitBranch,
Plus,
Repeat,
Save,
Square,
Terminal as TerminalIcon,
Trash2,
Play,
} from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/button";
import { Input } from "@/components/input";
import { Textarea } from "@/components/textarea";
import { Checkbox } from "@/components/checkbox";
import { EmptyState } from "@/components/empty-state";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/alert-dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/dropdown-menu";
import { getUserPreferences, saveUserPreferences } from "@/api/open-tabs-api";
import { getErrorMessage } from "../lib/error-message.js";
import {
parseTerminalMacros,
runTerminalMacro,
type MacroStep,
type TerminalMacro,
} from "@/lib/terminal-macros";
import type { Tab } from "@/types/ui-types";
const STEP_TYPES = ["send", "wait", "delay", "if", "repeat"] as const;
const STEP_ICONS = {
send: TerminalIcon,
wait: CornerDownRight,
delay: Clock,
if: GitBranch,
repeat: Repeat,
} as const;
function id(): string {
return typeof crypto.randomUUID === "function"
? crypto.randomUUID()
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
}
function createStep(type: MacroStep["type"]): MacroStep {
const stepId = id();
switch (type) {
case "send":
return { id: stepId, type, text: "", pressEnter: true };
case "delay":
return { id: stepId, type, milliseconds: 1000 };
case "wait":
return {
id: stepId,
type,
pattern: "",
isRegex: false,
timeoutMs: 10_000,
onTimeout: "stop",
};
case "if":
return {
id: stepId,
type,
pattern: "",
isRegex: false,
then: [],
else: [],
};
case "repeat":
return { id: stepId, type, count: 2, steps: [] };
}
}
function isValidRegex(pattern: string): boolean {
try {
new RegExp(pattern);
return true;
} catch {
return false;
}
}
/** Milliseconds are an implementation detail, so the editor works in seconds. */
function SecondsInput({
valueMs,
onChangeMs,
minMs,
label,
}: {
valueMs: number;
onChangeMs: (ms: number) => void;
minMs: number;
label: string;
}) {
const { t } = useTranslation();
return (
<label className="flex items-center gap-2 text-xs">
<span className="text-muted-foreground">{label}</span>
<Input
className="h-7 w-20 rounded-none text-xs"
type="number"
min={minMs / 1000}
max={300}
step={0.5}
value={valueMs / 1000}
onChange={(event) => {
const seconds = Number(event.target.value);
onChangeMs(
Number.isFinite(seconds)
? Math.max(minMs, Math.min(300_000, Math.round(seconds * 1000)))
: minMs,
);
}}
/>
<span className="text-muted-foreground">{t("macros.seconds")}</span>
</label>
);
}
/** Shared text/regex matcher field used by both "wait" and "if". */
function MatchField({
pattern,
isRegex,
label,
placeholder,
onChange,
}: {
pattern: string;
isRegex: boolean;
label: string;
placeholder: string;
onChange: (next: { pattern?: string; isRegex?: boolean }) => void;
}) {
const { t } = useTranslation();
const badRegex = isRegex && pattern.length > 0 && !isValidRegex(pattern);
return (
<div className="space-y-1.5">
<div className="text-xs text-muted-foreground">{label}</div>
<Input
className={`h-8 rounded-none font-mono text-xs ${
badRegex ? "border-destructive" : ""
}`}
placeholder={placeholder}
value={pattern}
onChange={(event) => onChange({ pattern: event.target.value })}
/>
{badRegex && (
<div className="text-[11px] text-destructive">
{t("macros.match.invalidRegex")}
</div>
)}
<label className="flex items-center gap-2 text-[11px] text-muted-foreground">
<Checkbox
checked={isRegex}
onCheckedChange={(checked) => onChange({ isRegex: checked === true })}
/>
{t("macros.match.useRegex")}
</label>
</div>
);
}
function AddStepMenu({
onAdd,
compact,
}: {
onAdd: (type: MacroStep["type"]) => void;
compact?: boolean;
}) {
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="outline"
className={`rounded-none ${compact ? "h-7 text-[11px]" : "h-8 w-full text-xs"}`}
>
<Plus className="mr-1 size-3" />
{t("macros.addStep")}
<ChevronDown className="ml-1 size-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-72 rounded-none">
{STEP_TYPES.map((type) => {
const Icon = STEP_ICONS[type];
return (
<DropdownMenuItem
key={type}
className="flex-col items-start gap-0.5 rounded-none"
onSelect={() => onAdd(type)}
>
<span className="flex items-center gap-2 text-xs font-medium">
<Icon className="size-3.5" />
{t(`macros.stepTypes.${type}.label`)}
</span>
<span className="pl-5 text-[11px] leading-snug text-muted-foreground">
{t(`macros.stepTypes.${type}.help`)}
</span>
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}
function StepList({
steps,
onChange,
depth = 0,
}: {
steps: MacroStep[];
onChange: (steps: MacroStep[]) => void;
depth?: number;
}) {
const { t } = useTranslation();
const update = (index: number, step: MacroStep) => {
const next = [...steps];
next[index] = step;
onChange(next);
};
return (
<div className="flex flex-col gap-2">
{steps.map((step, index) => {
const Icon = STEP_ICONS[step.type];
return (
<div key={step.id} className="border border-border bg-muted/20">
<div className="flex items-center gap-2 border-b border-border/60 px-2 py-1.5">
<Icon className="size-3.5 shrink-0 text-muted-foreground" />
<span className="text-xs font-medium">
{t(`macros.stepTypes.${step.type}.label`)}
</span>
<Button
size="icon"
variant="ghost"
className="ml-auto size-6 rounded-none"
aria-label={t("macros.removeStep")}
onClick={() => onChange(steps.filter((_, i) => i !== index))}
>
<Trash2 className="size-3" />
</Button>
</div>
<div className="space-y-2 p-2">
{step.type === "send" && (
<div className="space-y-2">
<Textarea
className="min-h-16 rounded-none font-mono text-xs"
placeholder={t("macros.send.textPlaceholder")}
value={step.text}
onChange={(event) =>
update(index, { ...step, text: event.target.value })
}
/>
<label className="flex items-center gap-2 text-[11px] text-muted-foreground">
<Checkbox
checked={step.pressEnter}
onCheckedChange={(checked) =>
update(index, { ...step, pressEnter: checked === true })
}
/>
{t("macros.send.pressEnter")}
</label>
</div>
)}
{step.type === "delay" && (
<SecondsInput
label={t("macros.delay.duration")}
valueMs={step.milliseconds}
minMs={0}
onChangeMs={(milliseconds) =>
update(index, { ...step, milliseconds })
}
/>
)}
{step.type === "wait" && (
<div className="space-y-2">
<MatchField
pattern={step.pattern}
isRegex={step.isRegex !== false}
label={t("macros.wait.pattern")}
placeholder={t("macros.wait.patternPlaceholder")}
onChange={(next) => update(index, { ...step, ...next })}
/>
<SecondsInput
label={t("macros.wait.timeout")}
valueMs={step.timeoutMs}
minMs={100}
onChangeMs={(timeoutMs) =>
update(index, { ...step, timeoutMs })
}
/>
<div className="space-y-1">
<div className="text-xs text-muted-foreground">
{t("macros.wait.onTimeout")}
</div>
<div className="flex gap-1">
{(["stop", "continue"] as const).map((mode) => (
<Button
key={mode}
size="sm"
variant={
step.onTimeout === mode ? "default" : "outline"
}
className="h-7 flex-1 rounded-none text-[11px]"
onClick={() =>
update(index, { ...step, onTimeout: mode })
}
>
{t(`macros.wait.${mode}`)}
</Button>
))}
</div>
</div>
</div>
)}
{step.type === "if" && (
<div className="space-y-3">
<MatchField
pattern={step.pattern}
isRegex={step.isRegex !== false}
label={t("macros.ifStep.pattern")}
placeholder={t("macros.ifStep.patternPlaceholder")}
onChange={(next) => update(index, { ...step, ...next })}
/>
<div className="border-l-2 border-emerald-500/40 pl-2">
<div className="mb-1 text-[11px] font-medium text-emerald-500">
{t("macros.ifStep.then")}
</div>
<StepList
steps={step.then}
depth={depth + 1}
onChange={(then) => update(index, { ...step, then })}
/>
</div>
<div className="border-l-2 border-amber-500/40 pl-2">
<div className="mb-1 text-[11px] font-medium text-amber-500">
{t("macros.ifStep.else")}
</div>
<StepList
steps={step.else}
depth={depth + 1}
onChange={(elseSteps) =>
update(index, { ...step, else: elseSteps })
}
/>
</div>
</div>
)}
{step.type === "repeat" && (
<div className="space-y-2">
<label className="flex items-center gap-2 text-xs">
<span className="text-muted-foreground">
{t("macros.repeat.count")}
</span>
<Input
className="h-7 w-20 rounded-none text-xs"
type="number"
min={1}
max={100}
value={step.count}
onChange={(event) =>
update(index, {
...step,
count: Math.max(
1,
Math.min(100, Number(event.target.value) || 1),
),
})
}
/>
<span className="text-muted-foreground">
{t("macros.repeat.times")}
</span>
</label>
<div className="border-l-2 border-border pl-2">
<StepList
steps={step.steps}
depth={depth + 1}
onChange={(nested) =>
update(index, { ...step, steps: nested })
}
/>
</div>
</div>
)}
</div>
</div>
);
})}
{depth < 4 && (
<AddStepMenu
compact={depth > 0}
onAdd={(type) => onChange([...steps, createStep(type)])}
/>
)}
</div>
);
}
export function MacrosPanel({
terminalTabs,
activeTabId,
storageMode,
}: {
terminalTabs: Tab[];
activeTabId: string;
storageMode: "local" | "cloud";
}) {
const { t } = useTranslation();
const [macros, setMacros] = useState<TerminalMacro[]>([]);
const [draft, setDraft] = useState<TerminalMacro | null>(null);
const [dirty, setDirty] = useState(false);
const [runningId, setRunningId] = useState<string | null>(null);
const [pendingDelete, setPendingDelete] = useState<TerminalMacro | null>(
null,
);
const [pendingRun, setPendingRun] = useState<TerminalMacro | null>(null);
const abortRef = useRef<AbortController | null>(null);
const confirmedRef = useRef<Set<string>>(new Set());
useEffect(() => {
let cancelled = false;
const load =
storageMode === "local"
? Promise.resolve(
parseTerminalMacros(localStorage.getItem("terminalMacros")),
)
: getUserPreferences().then((preferences) =>
parseTerminalMacros(preferences.terminalMacros),
);
load
.then((loaded) => {
if (!cancelled) setMacros(loaded);
})
.catch(() => {
if (!cancelled) toast.error(t("macros.loadFailed"));
});
return () => {
cancelled = true;
abortRef.current?.abort();
};
}, [storageMode, t]);
const target = useMemo(
() => terminalTabs.find((tab) => tab.id === activeTabId) ?? terminalTabs[0],
[activeTabId, terminalTabs],
);
const updateDraft = useCallback((next: TerminalMacro) => {
setDraft(next);
setDirty(true);
}, []);
const createMacro = () => {
const now = new Date().toISOString();
setDraft({
id: id(),
name: "",
description: "",
steps: [],
createdAt: now,
updatedAt: now,
});
setDirty(true);
};
const persist = async (next: TerminalMacro[]) => {
const serialized = JSON.stringify(next);
if (storageMode === "local")
localStorage.setItem("terminalMacros", serialized);
else await saveUserPreferences({ terminalMacros: serialized });
setMacros(next);
};
const save = async () => {
if (!draft) return;
if (!draft.name.trim()) {
toast.error(t("macros.nameRequired"));
return;
}
const saved: TerminalMacro = {
...draft,
name: draft.name.trim(),
updatedAt: new Date().toISOString(),
};
try {
await persist(
macros.some((macro) => macro.id === saved.id)
? macros.map((macro) => (macro.id === saved.id ? saved : macro))
: [...macros, saved],
);
setDraft(saved);
setDirty(false);
toast.success(t("macros.saved"));
} catch (error) {
toast.error(getErrorMessage(error) || t("macros.saveFailed"));
}
};
const confirmDelete = async () => {
if (!pendingDelete) return;
const next = macros.filter((macro) => macro.id !== pendingDelete.id);
try {
await persist(next);
if (draft?.id === pendingDelete.id) {
setDraft(null);
setDirty(false);
}
toast.success(t("macros.deleted"));
} catch (error) {
toast.error(getErrorMessage(error) || t("macros.saveFailed"));
} finally {
setPendingDelete(null);
}
};
const execute = useCallback(
async (macro: TerminalMacro) => {
const terminal = target?.terminalRef?.current;
if (!terminal?.sendInput || !terminal.subscribeOutput) {
toast.error(t("macros.needTerminal"));
return;
}
const controller = new AbortController();
abortRef.current = controller;
setRunningId(macro.id);
try {
await runTerminalMacro(
macro,
{ send: terminal.sendInput, subscribe: terminal.subscribeOutput },
{ signal: controller.signal },
);
toast.success(t("macros.completed"));
} catch (error) {
if (!(error instanceof DOMException && error.name === "AbortError")) {
toast.error(getErrorMessage(error) || t("macros.failed"));
}
} finally {
abortRef.current = null;
setRunningId(null);
}
},
[target, t],
);
const requestRun = (macro: TerminalMacro) => {
if (!target?.terminalRef?.current?.sendInput) {
toast.error(t("macros.needTerminal"));
return;
}
if (confirmedRef.current.has(macro.id)) {
void execute(macro);
return;
}
setPendingRun(macro);
};
const targetLabel = target?.label ?? t("macros.noTerminal");
return (
<div className="flex h-full min-h-0 flex-col">
<div className="flex items-center gap-2 border-b border-border p-3">
{draft ? (
<Button
size="icon"
variant="ghost"
className="size-7 rounded-none"
aria-label={t("macros.back")}
onClick={() => {
setDraft(null);
setDirty(false);
}}
>
<ArrowLeft className="size-4" />
</Button>
) : (
<Braces className="size-4 shrink-0" />
)}
<div className="min-w-0 flex-1">
<div className="text-sm font-semibold">{t("macros.title")}</div>
<div className="truncate text-[11px] text-muted-foreground">
{t("macros.target")}: {targetLabel}
</div>
{!draft && (
<a
href="https://docs.termix.site/features/terminal/macros"
target="_blank"
rel="noreferrer"
className="text-[10px] text-accent-brand hover:underline"
>
{t("hosts.docsLink")}
</a>
)}
</div>
{!draft && (
<Button
size="icon"
variant="outline"
className="size-7 rounded-none"
aria-label={t("macros.create")}
onClick={createMacro}
>
<Plus className="size-3.5" />
</Button>
)}
</div>
<div className="min-h-0 flex-1 overflow-y-auto p-3">
{!draft ? (
macros.length === 0 ? (
<EmptyState
icon={Braces}
title={t("macros.empty")}
hint={t("macros.emptyHint")}
action={
<Button
size="sm"
variant="outline"
className="mt-1 rounded-none"
onClick={createMacro}
>
<Plus className="mr-1 size-3" />
{t("macros.create")}
</Button>
}
/>
) : (
<div className="flex flex-col gap-1">
{macros.map((macro) => {
const isRunning = runningId === macro.id;
return (
<div
key={macro.id}
className="group flex items-center gap-2 border border-border bg-muted/20 px-2 py-1.5 hover:bg-muted/40"
>
<button
type="button"
className="min-w-0 flex-1 text-left"
onClick={() => {
setDraft(structuredClone(macro));
setDirty(false);
}}
>
<div className="truncate text-xs font-medium">
{macro.name}
</div>
{macro.description && (
<div className="truncate text-[11px] text-muted-foreground">
{macro.description}
</div>
)}
</button>
<Button
size="icon"
variant="ghost"
className="size-6 shrink-0 rounded-none"
aria-label={
isRunning ? t("macros.stop") : t("macros.run")
}
onClick={() =>
isRunning
? abortRef.current?.abort()
: requestRun(macro)
}
>
{isRunning ? (
<Square className="size-3" />
) : (
<Play className="size-3" />
)}
</Button>
</div>
);
})}
</div>
)
) : (
<div className="space-y-3">
<div className="space-y-1.5">
<div className="text-xs text-muted-foreground">
{t("macros.name")}
</div>
<Input
className="h-8 rounded-none text-xs"
value={draft.name}
placeholder={t("macros.namePlaceholder")}
onChange={(event) =>
updateDraft({ ...draft, name: event.target.value })
}
/>
</div>
<div className="space-y-1.5">
<div className="text-xs text-muted-foreground">
{t("macros.descriptionLabel")}
</div>
<Textarea
className="min-h-14 rounded-none text-xs"
value={draft.description ?? ""}
placeholder={t("macros.descriptionPlaceholder")}
onChange={(event) =>
updateDraft({ ...draft, description: event.target.value })
}
/>
</div>
{draft.steps.length === 0 && (
<div className="border border-dashed border-border px-3 py-4 text-center text-[11px] text-muted-foreground">
{t("macros.noSteps")}
</div>
)}
<StepList
steps={draft.steps}
onChange={(steps) => updateDraft({ ...draft, steps })}
/>
<div className="flex items-center gap-2 border-t border-border pt-3">
<Button
size="sm"
variant="destructive"
className="rounded-none"
onClick={() => setPendingDelete(draft)}
>
<Trash2 className="mr-1 size-3" />
{t("macros.delete")}
</Button>
<Button
size="sm"
variant="outline"
className="rounded-none"
onClick={save}
>
<Save className="mr-1 size-3" />
{t("macros.save")}
</Button>
<Button
size="sm"
className="ml-auto rounded-none"
onClick={() =>
runningId === draft.id
? abortRef.current?.abort()
: requestRun(draft)
}
>
{runningId === draft.id ? (
<>
<Square className="mr-1 size-3" />
{t("macros.stop")}
</>
) : (
<>
<Play className="mr-1 size-3" />
{t("macros.run")}
</>
)}
</Button>
</div>
{dirty && (
<div className="text-[11px] text-amber-500">
{t("macros.unsaved")}
</div>
)}
</div>
)}
</div>
<AlertDialog
open={pendingDelete !== null}
onOpenChange={(open) => !open && setPendingDelete(null)}
>
<AlertDialogContent className="rounded-none">
<AlertDialogHeader>
<AlertDialogTitle>{t("macros.deleteTitle")}</AlertDialogTitle>
<AlertDialogDescription>
{t("macros.deleteBody", { name: pendingDelete?.name ?? "" })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="rounded-none">
{t("macros.cancel")}
</AlertDialogCancel>
<AlertDialogAction className="rounded-none" onClick={confirmDelete}>
{t("macros.delete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<AlertDialog
open={pendingRun !== null}
onOpenChange={(open) => !open && setPendingRun(null)}
>
<AlertDialogContent className="rounded-none">
<AlertDialogHeader>
<AlertDialogTitle>{t("macros.confirmRunTitle")}</AlertDialogTitle>
<AlertDialogDescription>
{t("macros.confirmRunBody", {
name: pendingRun?.name ?? "",
host: targetLabel,
})}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="rounded-none">
{t("macros.cancel")}
</AlertDialogCancel>
<AlertDialogAction
className="rounded-none"
onClick={() => {
const macro = pendingRun;
setPendingRun(null);
if (macro) {
confirmedRef.current.add(macro.id);
void execute(macro);
}
}}
>
{t("macros.confirmRunAction")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}
-28
View File
@@ -498,7 +498,6 @@ export function UserProfilePanel({
pinAppRail?: boolean | null;
expandAppRailOnHover?: boolean | null;
showPinAppRailButton?: boolean | null;
confirmSnippetExecution?: boolean | null;
disableUpdateCheck?: boolean | null;
confirmTabClose?: boolean | null;
hiddenRailTabs?: string | null;
@@ -620,13 +619,6 @@ export function UserProfilePanel({
const [showPinAppRailButton, setShowPinAppRailButton] = useState(() =>
readRailPreference("showPinAppRailButton"),
);
// Read value is unused; the setter still backs the cloud-sync/reset/
// snapshot machinery for this localStorage-backed pref below. The
// folder-collapse setting itself now lives in the snippets plugin's own
// user settings.
const [_confirmSnippetExecution, setConfirmSnippetExecution] = useState(
() => localStorage.getItem("confirmSnippetExecution") === "true",
);
const [disableUpdateCheck, setDisableUpdateCheck] = useState(
() => localStorage.getItem("disableUpdateCheck") === "true",
);
@@ -763,8 +755,6 @@ export function UserProfilePanel({
"pinAppRail",
"expandAppRailOnHover",
"showPinAppRailButton",
"snippetShowCommands",
"confirmSnippetExecution",
"disableUpdateCheck",
"confirmTabClose",
"hiddenRailTabs",
@@ -830,13 +820,6 @@ export function UserProfilePanel({
);
window.dispatchEvent(new Event("showPinAppRailButtonChanged"));
}
if (prefs.confirmSnippetExecution != null) {
setConfirmSnippetExecution(prefs.confirmSnippetExecution);
localStorage.setItem(
"confirmSnippetExecution",
String(prefs.confirmSnippetExecution),
);
}
if (prefs.disableUpdateCheck != null) {
setDisableUpdateCheck(prefs.disableUpdateCheck);
localStorage.setItem(
@@ -902,8 +885,6 @@ export function UserProfilePanel({
setShowPinAppRailButton(false);
localStorage.setItem("showPinAppRailButton", "false");
window.dispatchEvent(new Event("showPinAppRailButtonChanged"));
setConfirmSnippetExecution(false);
localStorage.setItem("confirmSnippetExecution", "false");
setDisableUpdateCheck(false);
localStorage.setItem("disableUpdateCheck", "false");
setConfirmTabClose(false);
@@ -924,7 +905,6 @@ export function UserProfilePanel({
pinAppRail: false,
expandAppRailOnHover: true,
showPinAppRailButton: false,
confirmSnippetExecution: false,
disableUpdateCheck: false,
confirmTabClose: false,
hiddenRailTabs: "[]",
@@ -1007,14 +987,6 @@ export function UserProfilePanel({
localStorage.setItem("showPinAppRailButton", String(restoredShowPinButton));
window.dispatchEvent(new Event("showPinAppRailButtonChanged"));
const restoredConfirmSnippet =
restore("confirmSnippetExecution", "false") === "true";
setConfirmSnippetExecution(restoredConfirmSnippet);
localStorage.setItem(
"confirmSnippetExecution",
String(restoredConfirmSnippet),
);
const restoredUpdateCheck =
restore("disableUpdateCheck", "false") === "true";
setDisableUpdateCheck(restoredUpdateCheck);
-2
View File
@@ -4,7 +4,6 @@ export type FieldGroup =
| "tags"
| "proxy"
| "jumpHosts"
| "quickActions"
| "featureFlags"
| "advanced";
@@ -37,7 +36,6 @@ const FIELD_GROUP_KEYS: Record<FieldGroup, string[]> = {
"socks5ProxyChain",
],
jumpHosts: ["jumpHosts"],
quickActions: ["quickActions"],
// Every plugin's host settings, enable switches included.
featureFlags: ["pluginSettings", "forceKeyboardInteractive"],
advanced: [
-1
View File
@@ -71,7 +71,6 @@ export function createQuickConnectHost(input: QuickConnectInput): Host {
ram: null,
lastAccess: new Date().toISOString(),
pin: false,
quickActions: [],
enableSsh: true,
sshPort: input.port,
quickConnectSavable:
-9
View File
@@ -1,5 +1,4 @@
import {
Braces,
Cloud,
KeyRound,
Plug,
@@ -93,14 +92,6 @@ export const RAIL_ITEMS: RailItemDef[] = [
separatorAfter: true,
mobilePrimary: true,
},
{
id: "macros",
icon: Braces,
labelKey: "nav.macros",
separatorAfter: true,
promotable: true,
rightDockable: true,
},
{
id: "sync",
icon: Cloud,
-7
View File
@@ -636,15 +636,8 @@ export function SidebarTree({
hostId: Number(j.hostId),
})),
portKnockSequence: host.portKnockSequence ?? [],
quickActions: (host.quickActions ?? []).map((a) => ({
name: a.name,
snippetId: Number(a.snippetId),
})),
statusCheckEnabled: host.statusCheckEnabled,
statusCheckInterval: host.statusCheckInterval,
terminalConfig: host.terminalConfig?.startupSnippetId
? { startupSnippetId: host.terminalConfig.startupSnippetId }
: null,
sshOptions: host.sshOptions ?? null,
};
const created = await createSSHHost(duplicateHost);
+2 -2
View File
@@ -81,10 +81,10 @@ describe("presetHiddenRailTabs", () => {
it("hides plugin items that do not opt in when the preset asks", () => {
expect(
presetHiddenRailTabs(
{ hiddenTabs: ["macros"], hidePluginItems: true },
{ hiddenTabs: ["quick-connect"], hidePluginItems: true },
items,
),
).toEqual(["macros", "docker-like"]);
).toEqual(["quick-connect", "docker-like"]);
});
it("leaves plugin items alone otherwise", () => {
-185
View File
@@ -1,185 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import {
parseTerminalMacros,
runTerminalMacro,
type MacroStep,
type TerminalMacro,
} from "@/lib/terminal-macros";
function macro(steps: MacroStep[]): TerminalMacro {
return {
id: "macro-1",
name: "Test",
steps,
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
};
}
describe("terminal macro runner", () => {
it("runs send, delay, regex wait, condition, and repeat steps", async () => {
vi.useFakeTimers();
const sent: string[] = [];
let listener = (_data: string) => {};
const run = runTerminalMacro(
macro([
{ id: "send", type: "send", text: "status", pressEnter: true },
{
id: "wait",
type: "wait",
pattern: "READY\\s+42",
timeoutMs: 1000,
onTimeout: "stop",
},
{
id: "if",
type: "if",
pattern: "READY",
then: [
{
id: "repeat",
type: "repeat",
count: 2,
steps: [
{ id: "ok", type: "send", text: "ok", pressEnter: false },
],
},
],
else: [],
},
]),
{
send: (data) => sent.push(data),
subscribe: (next) => {
listener = next;
return () => {};
},
},
);
await vi.advanceTimersByTimeAsync(1);
listener("READY 42");
await run;
expect(sent).toEqual(["status\r", "ok", "ok"]);
vi.useRealTimers();
});
it("stops when a required regex times out", async () => {
vi.useFakeTimers();
const run = runTerminalMacro(
macro([
{
id: "wait",
type: "wait",
pattern: "never",
timeoutMs: 100,
onTimeout: "stop",
},
]),
{ send: () => {}, subscribe: () => () => {} },
);
const rejection = expect(run).rejects.toThrow("Timed out");
await vi.advanceTimersByTimeAsync(101);
await rejection;
vi.useRealTimers();
});
it("sanitizes persisted macro bounds and drops unknown steps", () => {
const parsed = parseTerminalMacros(
JSON.stringify([
{
id: "m",
name: "Macro",
steps: [
{ id: "d", type: "delay", milliseconds: 999_999 },
{ id: "r", type: "repeat", count: 1000, steps: [] },
{ id: "bad", type: "execute-javascript", code: "nope" },
],
},
]),
);
expect(parsed[0].steps).toEqual([
{ id: "d", type: "delay", milliseconds: 300_000 },
{ id: "r", type: "repeat", count: 100, steps: [] },
]);
});
it("matches literal text without regex escaping when isRegex is false", async () => {
vi.useFakeTimers();
const sent: string[] = [];
let listener = (_data: string) => {};
const run = runTerminalMacro(
macro([
{
id: "wait",
type: "wait",
pattern: "progress (1/2)",
isRegex: false,
timeoutMs: 1000,
onTimeout: "stop",
},
{ id: "send", type: "send", text: "done", pressEnter: false },
]),
{
send: (data) => sent.push(data),
subscribe: (next) => {
listener = next;
return () => {};
},
},
);
await vi.advanceTimersByTimeAsync(1);
listener("progress (1/2)");
await run;
expect(sent).toEqual(["done"]);
vi.useRealTimers();
});
it("falls back to literal matching when a regex is invalid", async () => {
vi.useFakeTimers();
const sent: string[] = [];
let listener = (_data: string) => {};
const run = runTerminalMacro(
macro([
{
id: "wait",
type: "wait",
pattern: "oops(",
isRegex: true,
timeoutMs: 1000,
onTimeout: "stop",
},
{ id: "send", type: "send", text: "ok", pressEnter: false },
]),
{
send: (data) => sent.push(data),
subscribe: (next) => {
listener = next;
return () => {};
},
},
);
await vi.advanceTimersByTimeAsync(1);
listener("oops(");
await run;
expect(sent).toEqual(["ok"]);
vi.useRealTimers();
});
it("branches on literal if-step text", async () => {
const sent: string[] = [];
await runTerminalMacro(
macro([
{
id: "if",
type: "if",
pattern: "a.b",
isRegex: false,
then: [{ id: "y", type: "send", text: "yes", pressEnter: false }],
else: [{ id: "n", type: "send", text: "no", pressEnter: false }],
},
]),
{ send: (data) => sent.push(data), subscribe: () => () => {} },
);
// "a.b" as a literal must not match "axb"
expect(sent).toEqual(["no"]);
});
});
@@ -0,0 +1,81 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
GLOBAL_KEYBINDING_EVENT,
SHELL_KEYBINDING_ACTION_IDS,
getKeybindingAction,
listKeybindingActions,
registerKeybindingAction,
resetKeybindingRegistry,
runKeybindingAction,
} from "../../shell/keybinding-registry";
afterEach(() => resetKeybindingRegistry());
describe("keybinding registry", () => {
it("starts with the shell's own actions, all global", () => {
expect(listKeybindingActions().map((action) => action.id)).toEqual(
SHELL_KEYBINDING_ACTION_IDS,
);
expect(getKeybindingAction("nextTab")?.scope).toBe("global");
});
it("runs a shell action by telling the shell", () => {
const listener = vi.fn();
window.addEventListener(GLOBAL_KEYBINDING_EVENT, listener);
try {
expect(runKeybindingAction({ type: "openCommandPalette" })).toBe(true);
expect((listener.mock.calls[0][0] as CustomEvent).detail).toEqual({
type: "openCommandPalette",
});
} finally {
window.removeEventListener(GLOBAL_KEYBINDING_EVENT, listener);
}
});
it("runs a plugin's action with the caller's context, until it is disposed", () => {
const run = vi.fn();
const dispose = registerKeybindingAction({
id: "sample.run",
pluginId: "sample",
labelKey: "sample:run",
scope: "session",
run,
});
const send = vi.fn();
expect(runKeybindingAction({ type: "sample.run", x: "1" }, { send })).toBe(
true,
);
expect(run).toHaveBeenCalledWith({ type: "sample.run", x: "1" }, { send });
dispose();
expect(runKeybindingAction({ type: "sample.run" })).toBe(false);
});
it("answers false for a type nothing running handles", () => {
expect(runKeybindingAction({ type: "gone" })).toBe(false);
});
it("refuses a shell id and a second plugin claiming a type", () => {
expect(() =>
registerKeybindingAction({
id: "nextTab",
pluginId: "sample",
labelKey: "x",
scope: "global",
}),
).toThrow(/belongs to the shell/);
registerKeybindingAction({
id: "sample.run",
pluginId: "sample",
labelKey: "x",
scope: "session",
});
expect(() =>
registerKeybindingAction({
id: "sample.run",
pluginId: "other",
labelKey: "x",
scope: "session",
}),
).toThrow(/already registered/);
});
});
@@ -0,0 +1,61 @@
import { afterEach, describe, expect, it } from "vitest";
import {
filterPaletteItems,
listPaletteGroups,
loadPaletteGroup,
registerPaletteGroup,
resetPaletteGroups,
type PaletteItemDef,
} from "../../shell/palette-registry";
afterEach(() => resetPaletteGroups());
const item = (
id: string,
title: string,
extra: Partial<PaletteItemDef> = {},
): PaletteItemDef => ({ id, title, run: () => {}, ...extra });
describe("palette groups", () => {
it("orders groups and loads their items", async () => {
registerPaletteGroup({
id: "b",
titleKey: "b",
order: 2,
load: () => [item("1", "One")],
});
registerPaletteGroup({
id: "a",
titleKey: "a",
order: 1,
load: async () => [item("2", "Two")],
});
const groups = listPaletteGroups();
expect(groups.map((group) => group.id)).toEqual(["a", "b"]);
expect((await loadPaletteGroup(groups[0])).map((i) => i.id)).toEqual(["2"]);
});
it("answers no items when a loader throws", async () => {
const group = {
id: "broken",
titleKey: "x",
load: async () => {
throw new Error("offline");
},
};
expect(await loadPaletteGroup(group)).toEqual([]);
});
it("matches title, description and keywords, and hides items until typed", () => {
const items = [
item("1", "Restart nginx", { description: "systemctl restart" }),
item("2", "Disk usage", { keywords: ["df"] }),
];
expect(filterPaletteItems(items, "")).toEqual([]);
expect(filterPaletteItems(items, "", true)).toEqual(items);
expect(filterPaletteItems(items, "SYSTEMCTL").map((i) => i.id)).toEqual([
"1",
]);
expect(filterPaletteItems(items, "df").map((i) => i.id)).toEqual(["2"]);
});
});
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff. Show more